1、条件表达式链式比较判断
在Python中,不能直接使用 s[i]!=s[i-1]!=s[i-2] 这种链式比较来判断三个字符是否互不相同。这个表达式实际上会被解释为 (s[i] != s[i-1]) and (s[i-1] != s[i-2]),但这不是您想要的逻辑(即检查当前字符与前两个字符都不同)
2、单元素元组定义
在定义只有一个元素的元组时,末尾的逗号是必需的。例如,(0,) 是一个包含一个元素的元组,而 (0) 只是一个带括号的整数表达式,不是元组。这里需要特别注意不要将列表和元组的语法混淆。
3、深拷贝、浅拷贝、直接引用
#copy是复制列表的值,不影响元素的地址。而直接赋值则会把地址也拷贝去,改一个会变多个列表的值
#但是浅拷贝会随着可变对象的改变而改变,深拷贝不会
L1=[1,2,3,['a','b','c']]
#浅拷贝
L2=L1.copy()
#深拷贝
import copy
L3=copy.deepcopy(L1)
#直接赋值给L4
L4=L1
print(id(L1[0])) #查看非可变对象的id
print(id(L2[0]))
print(id(L3[0]))
print(id(L4[0]))
print('*****************')
print(id(L1[3])) #查看可变对象的id
print(id(L2[3]))
print(id(L3[3]))
print(id(L4[3]))
print('*****************')
#修改值
L1[0]=99
L1[3][0]='A'
print(L1) #L1与L4一样
print(L2) #浅拷贝出了可变对象改变其他不变
print(L3) #深拷贝为新对象
print(L4) #完全与L1是同命相连
print("*****************")
print(id(L1[3]))
print(id(L2[3])) #可变对象地址采用"引用"
print(id(L3[3]))
print(id(L4[3]))
print('*****************')
print(id(L1[0]))
print(id(L2[0])) #非可变对象地址"不引用",这点同深拷贝
print(id(L3[0]))
print(id(L4[0]))
print("**********")
L2[0]=55
print(L2[0])
print(L3[0])
print(id(L2[0]))
print(id(L3[0]))
- L1:原始列表。
- L2:浅拷贝。它复制了列表的结构和元素的引用(对于可变对象)。修改可变对象会影响所有引用该对象的列表。对于不可变对象,浅拷贝会创建新的引用(但在这个例子中,由于整数的小整数池特性,id可能看起来相同)。
- L3:深拷贝。它递归地复制了列表及其所有元素(包括嵌套的可变对象)。修改任何元素都不会影响其他列表。
- L4:直接赋值。它创建了一个新的列表变量,但它引用的是与L1相同的对象。因此,对L4的任何修改(除了重新赋值列表元素本身)都会反映到L1上,反之亦然。
4、关于map函数
map(int, data.split()) 这个表达式并不返回元组(tuple),而是返回一个 map 对象。在 Python 3 中,map 函数返回的是一个迭代器(iterator),它按照指定的函数(在这个例子中是 int 函数)对另一个可迭代对象(在这个例子中是 data.split() 返回的字符串列表)中的每个元素进行转换。
所以直接【print】map函数会得到一个地址 ,所以一般我们会将map对象转换为如列表的序列之后在进行输出等操作。
5、不继承还能实现多态吗?
在Python中,多态通常是通过继承和方法重写来实现的,这是面向对象编程中多态的经典方式。然而,Python的动态类型和鸭子类型特性使得在不继承的情况下也可以实现某种形式的多态。
鸭子类型(Duck Typing)是一种类型检查方式,它并不关心对象的实际类型,而是关注对象是否具有所需的行为(即方法)。如果对象“看起来像鸭子、走路像鸭子、叫声像鸭子”,那么它就可以被视为鸭子。在Python中,这意味着只要对象实现了特定的方法,就可以将其视为该类型的对象,而无需关心其是否从某个特定的类继承。
例如,假设我们有两个不相关的类Dog
和Robot
,它们都实现了一个名为bark
的方法:
class Dog:
def bark(self):
return "Woof!"
class Robot:
def bark(self):
return "Beep beep!"
现在,我们可以编写一个函数,它接受任何具有bark
方法的对象,并调用该方法:
def make_it_bark(barker):
print(barker.bark())
这个函数并不关心barker
对象的实际类型,只要它有一个bark
方法。因此,我们可以将Dog
和Robot
的实例传递给这个函数,并得到不同的输出:
dog = Dog()
robot = Robot()
make_it_bark(dog) # 输出: Woof!
make_it_bark(robot) # 输出: Beep beep!
在这个例子中,虽然没有继承关系,但我们仍然实现了多态的效果:不同的对象对相同的函数调用产生了不同的行为。
然而,需要注意的是,这种多态并不是严格意义上的面向对象编程中的多态,因为它没有利用继承和方法重写来确保接口的一致性。相反,它依赖于对象的动态类型和鸭子类型特性。尽管如此,这种多态在Python中仍然是非常有用和强大的,因为它允许我们以更加灵活和动态的方式来编写代码。
6、私有属性的命名
下列哪个是私有属性的正确命名方式?
a) public_var
b) _private_var
c) __private_var
d) __init_var
在Python中,属性的命名约定对于区分公有(public)和私有(private)属性起着重要作用,尽管Python没有真正的私有属性(因为总是可以通过一些手段访问到它们)。按照Python的命名惯例:
- 公有属性通常以小写字母开头,并且不使用下划线前缀,例如
public_var
。 - 私有属性通常以单个下划线
_
开头,例如_private_var
。这表示该属性是“受保护的”或“不打算被外部访问的”,但它实际上并不是私有的,仍然可以通过对象直接访问。 - 双下划线
__
开头的属性名在Python中有特殊含义。它会触发名称改写(name mangling),使得属性名在类外部难以直接访问。改写后的属性名会在类名后加上一个下划线和原始属性名,例如,在一个名为MyClass
的类中,__private_var
会被改写为_MyClass__private_var
。
选项分析:
a)public_var
- 这通常被视为一个公有属性。
b)_private_var
- 这通常被视为一个私有属性(按照Python的命名惯例),但实际上它是受保护的,不是真正的私有。
c)__private_var
- 这会被Python改写,使得它更难从类外部直接访问,符合私有属性的更严格定义(尽管技术上仍然不是真正的私有)。
d)__init_var
- 这同样会触发名称改写,但由于它通常以__init__
作为特殊方法名(构造函数)的前缀,使用__init_var
可能会引起混淆,因为它看起来像是与初始化相关的某种特殊变量。对于私有属性的正确命名方式,按照Python的命名惯例和意图,最符合私有属性定义的是使用双下划线开头的命名方式,即:
c)
__private_var
然而,需要注意的是,即使使用了双下划线,属性仍然可以通过类名加上改写后的名称来访问,因此它并不是真正的私有。Python的私有性更多的是一种约定俗成的规范,而不是语言强制的。
7、静态方法
在Python中,如何定义一个静态方法?
a) 使用 @staticmethod 装饰器
b) 使用 static 关键字
c) 将方法定义为类方法并使用 @classmethod 装饰器
d) 将方法定义为实例方法并使用 self 参数
在Python中,定义一个静态方法应该使用@staticmethod
装饰器。这个装饰器告诉Python,这个方法既不需要访问类的属性也不需要访问实例的属性,它就像是一个普通的函数,但是被放在了类的命名空间中。
选项分析:
a) 使用 @staticmethod
装饰器 - 这是正确的。这个装饰器用于定义静态方法。
b) 使用 static
关键字 - Python中没有这样的关键字用于定义静态方法。
c) 将方法定义为类方法并使用 @classmethod
装饰器 - 这是定义类方法的方式,而不是静态方法。类方法可以访问类属性,但不能直接访问实例属性(尽管它们可以通过类名间接访问实例属性)。
d) 将方法定义为实例方法并使用 self
参数 - 这是定义实例方法的方式,实例方法可以访问类的属性和实例的属性,并且必须有一个名为self
的参数来引用调用该方法的实例。
因此,正确的答案是:
a) 使用 @staticmethod
装饰器
class MyClass:
@staticmethod
def my_static_method():
print("This is a static method.")
# 调用静态方法,不需要实例化类
MyClass.my_static_method()
在这个例子中,my_static_method
是一个静态方法,它不需要通过类的实例来调用,可以直接通过类名来调用
8、装饰器不能提高代码性能
下列哪个不是装饰器的优点?
a) 增加代码复用
b) 使代码更简洁
c) 提高代码性能
d) 分离关注点
a) 增加代码复用 - 装饰器允许我们定义一个可复用的函数修改逻辑,这个逻辑可以应用于多个函数上,而无需在每个函数内部重复编写相同的代码。因此,装饰器确实有助于增加代码复用性。
b) 使代码更简洁 - 通过使用装饰器,我们可以将那些与函数核心功能不直接相关的附加逻辑(如日志记录、权限检查等)从函数主体中抽离出来,从而简化函数本身的代码,使代码更加清晰和简洁。
c) 提高代码性能 - 这一点并不总是正确的。装饰器本身并不会直接提高代码的性能,相反,由于装饰器在函数调用前后可能会执行一些额外的逻辑,因此在某些情况下,它甚至可能会略微降低性能。然而,如果装饰器被用于实现一些优化逻辑(如缓存结果),那么它也可以间接地提高性能。但一般来说,我们不能简单地将提高代码性能视为装饰器的一个固有优点。
d) 分离关注点 - 装饰器允许我们将那些与函数核心功能不直接相关的逻辑(如日志记录、异常处理等)从函数主体中分离出来,从而帮助我们更好地关注函数的核心功能。这种分离关注点的做法有助于提高代码的可读性和可维护性。
9、装饰器能不能访问被修饰函数的局部变量呢?
下列关于装饰器的说法中,哪个是不正确的?
a) 装饰器可以改变函数的返回值
b) 装饰器可以修改函数的名称
c) 装饰器可以访问被装饰函数的局部变量
d) 装饰器可以在被装饰函数执行前后添加代码
答案: c) 装饰器可以访问被装饰函数的局部变量 (注:装饰器通常不能直接访问被装饰函数的局部变量,除非通过某种方式(如使用闭包)间接访问)
10、判断一个对象是否可迭代
如何判断一个对象是否是可迭代的?
a) 使用isinstance(obj, Iterable)
b) 使用hasattr(obj, '__iter__')
c) 使用hasattr(obj, 'next')
d) 使用callable(obj.__iter__)
a) 使用 isinstance(obj, Iterable)
这是正确的方法之一,但前提是你需要从 collections.abc 模块中导入 Iterable。Iterable 是一个抽象基类,用于检查对象是否实现了迭代协议(即是否定义了 __iter__() 方法或 __getitem__() 方法并支持从0开始的索引)。
b) 使用 hasattr(obj, '__iter__')
这也是一个有效的方法。如果一个对象具有 __iter__() 方法,那么它就是可迭代的。这个方法检查对象是否有名为 __iter__ 的属性(在这种情况下,是一个方法)。
c) 使用 hasattr(obj, 'next')
这是不正确的。虽然早期的Python版本(Python 2及某些旧式Python 3代码)可能使用 next() 函数与迭代器一起工作,但现代Python中迭代器应该实现 __next__() 方法而不是 next()。此外,仅仅因为对象有 __next__() 方法并不意味着它也是可迭代的,除非它同时也有 __iter__() 方法并返回自身(或另一个迭代器)。
d) 使用 callable(obj.__iter__)
这个方法部分正确但不够准确。callable() 函数检查给定的对象是否是可调用的(即是否定义了 __call__() 方法)。然而,仅仅因为 __iter__ 是可调用的,并不意味着对象就是可迭代的,因为 __iter__ 必须返回一个迭代器。此外,如果 obj 没有 __iter__ 属性,直接访问 obj.__iter__ 会引发 AttributeError。正确的方法是使用 hasattr(obj, '__iter__') 来避免这种错误。
11、迭代器只需实现__next__()方法,而可迭代对象需要实现__iter__()方法,该方法返回一个迭代器
- 迭代器:
- 迭代器是一个实现了
__next__()
方法的对象。__next__()
方法应该返回序列中的下一个元素,并在没有更多元素时引发StopIteration
异常。- 迭代器还可以选择性地实现
__iter__()
方法,但这不是必需的。然而,如果迭代器实现了__iter__()
方法,它通常应该返回迭代器自身,以符合迭代器的协议。这样做允许迭代器对象在多个循环或其他迭代上下文中重复使用。- 可迭代对象:
- 可迭代对象是一个实现了
__iter__()
方法的对象。__iter__()
方法应该返回一个迭代器对象,该对象将用于遍历可迭代对象中的元素。- 可迭代对象本身不需要实现
__next__()
方法,因为迭代的工作是由返回的迭代器对象完成的。
12、如何优雅地迭代
for
循环会自动处理迭代器的迭代过程,并且在迭代器耗尽时优雅地停止,不会引发 StopIteration
异常。
13、(x for x in range(10))
表达式
(x for x in range(10))
是一个生成器表达式(generator expression)。生成器表达式是Python中的一种结构,它允许你以一种简洁的方式创建一个迭代器,这个迭代器会按需生成一系列的值,而不是一次性将它们全部存储在内存中。
for x in (x for x in range(10)):
print(x)
14、关于文件的打开方式
如何逐行读取文件的内容?
a) 使用for line in file:
b) 使用while file.readline():
c) 使用file.readlines()然后遍历列表
d) 以上方法都可以
都可以
with open('example.txt', 'r') as file:
for line in file:
# 处理每一行的内容
print(line.strip()) # 去除每行末尾的换行符并打印
# 打开文件
file = open('example.txt', 'r')
try:
# 使用 while 循环和 readline() 方法逐行读取文件内容
while True:
line = file.readline()
# 如果读取到空字符串,表示文件已经读取完毕
if not line:
break
# 去除每行末尾的换行符并打印
print(line.strip())
finally:
# 确保文件被关闭
file.close()
# 打开文件
file = open('example.txt', 'r')
try:
# 一次性读取文件的所有行到一个列表中
lines = file.readlines()
# 遍历列表,逐行处理文件内容
for line in lines:
# 去除每行末尾的换行符并打印
print(line.strip())
finally:
# 确保文件被关闭
file.close()
14、正则表达式的搜索匹配
在Python中,使用哪个函数可以进行正则表达式的搜索匹配?
a) re.match()
b) re.search()
c) re.find()
d) re.locate()
a) re.match():这个函数尝试从字符串的起始位置匹配正则表达式。如果正则表达式在字符串的起始位置没有匹配成功,那么匹配就会失败,即使正则表达式在字符串的其他位置可以匹配成功。
b) re.search():这个函数会在整个字符串中搜索正则表达式的第一个匹配项。它不考虑匹配的位置,只要找到第一个匹配项就会返回。
c) re.find():这不是Python正则表达式模块(re模块)中的一个函数。如果你想在字符串中查找正则表达式的所有匹配项,可以使用re.findall()。
d) re.locate():这同样不是Python正则表达式模块中的一个函数。如果你想获取匹配项的位置,可以使用re.search()或re.match()返回的匹配对象的start()和end()方法。
因此,正确答案是 b) re.search()。
15、包下模块的导入
如何导入包中的一个模块?
a) import package.module
b) from package import module
c) package.module.import()
d) module = package.load('module')
a) import package.module
和
b) from package import module
这两种方式都是合法且常用的从包中导入模块的方法。选择哪种方式取决于你的具体需求和代码风格。如果你需要频繁访问模块中的多个定义,并且不介意可能的命名冲突,那么from package import module可能更方便。如果你想要避免命名冲突,或者只是偶尔需要访问模块中的某个定义,那么import package.module可能更合适。
16、自定义异常的继承
在自定义异常时,应该继承哪个类?
a) Exception
b) BaseException
c) Error
d) StandardError
a,因为b是更顶层的基类,通常用a继承
17、多线程多进程
下列哪个方法用于启动一个新的线程?
a) thread.start_new_thread()
b) threading.Thread.start()
c) thread.run()
d) threading.new_thread()
答案: b) threading.Thread.start() (注:通常是通过创建threading.Thread对象后调用其start方法)
下列哪个方法用于启动一个新的进程?
a) process.start_new_process()
b) multiprocessing.Process.start()
c) multiprocessing.new_process()
d) os.fork()
答案: b) multiprocessing.Process.start() (注:通常是通过创建multiprocessing.Process对象后调用其start方法;os.fork()在Unix/Linux上用于创建进程,但在Windows上不可用,且不是multiprocessing模块的一部分)
多线程共享内存空间,而多进程有独立的内存空间
18、多线程不能充分利用多核cpu的优势
在多线程编程中,由于Python的GIL(全局解释器锁)存在,下列哪种操作可能无法充分利用多核CPU的优势?
a) 密集的I/O操作
b) 密集的计算操作
c) 网络通信
d) 文件读写
答案: b) 密集的计算操作
a) 密集的I/O操作:对于I/O密集型任务,线程在等待外部I/O完成时会释放GIL。这意味着在等待I/O的过程中,其他线程可以获得GIL并执行。因此,在I/O密集型任务中,多线程可以更好地发挥作用,因为线程会在等待I/O时释放GIL,从而允许其他线程执行。所以,密集的I/O操作可以较好地利用多线程环境,尽管存在GIL。
b) 密集的计算操作:对于CPU密集型任务,线程会长时间占用CPU进行计算,而不会释放GIL。这导致其他线程无法获得GIL并执行,从而无法充分利用多核CPU的优势。因此,密集的计算操作在Python多线程环境中可能无法充分利用多核CPU。
c) 网络通信:网络通信通常涉及等待网络响应和数据传输,这些操作属于I/O密集型任务。与密集的I/O操作类似,网络通信中的线程在等待网络响应时会释放GIL,从而允许其他线程执行。因此,网络通信可以较好地适应多线程环境。
d) 文件读写:文件读写操作也属于I/O密集型任务。线程在等待文件读写操作完成时会释放GIL,这使得其他线程可以执行。因此,文件读写操作同样可以较好地利用多线程环境。
19、TCP全连接
下列哪个方法用于建立TCP连接?
a) socket.connect()
b) socket.bind()
c) socket.listen()
d) socket.accept()
a) socket.connect():
这个方法用于客户端主动初始化TCP连接。当客户端需要连接到服务器时,它会使用这个方法,并提供服务器的IP地址和端口号作为参数。因此,socket.connect()是建立TCP连接的关键方法之一,特别是在客户端。
b) socket.bind():
这个方法用于将套接字绑定到指定的地址(IP地址和端口号)。它通常用于服务器端,在服务器创建套接字后,需要将其绑定到一个特定的IP地址和端口号上,以便客户端可以连接到它。然而,socket.bind()本身并不建立连接,它只是为连接做准备。
c) socket.listen():
这个方法使服务器套接字进入被动监听状态,等待客户端的连接请求。它指定了内核为此套接字排队的最大连接个数(backlog)。socket.listen()同样不直接建立连接,而是让服务器准备好接受连接。
d) socket.accept():
这个方法用于服务器端,被动接受一个TCP客户端的连接。当服务器调用socket.accept()时,它会阻塞(等待),直到有一个客户端连接到服务器。一旦有客户端连接,socket.accept()会返回一个新的套接字对象(用于与客户端通信)和客户端的地址信息。
20、UDP(冷知识王者荣耀用的这个方式)
下列哪个方法用于处理UDP连接?
a) socket.stream()
b) socket.datagram()
c) socket.tcp()
d) socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
答案: d) socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
21、socket套接字
Python中,socket模块提供的套接字类型不包括下列哪一项?
a) socket.SOCK_STREAM
b) socket.SOCK_DGRAM
c) socket.SOCK_RAW
d) socket.SOCK_HTTP
a) socket.SOCK_STREAM:
这是基于TCP协议的流式套接字类型。它提供可靠的、面向连接的通信,通过建立双向的字节流进行数据传输,确保数据的顺序和可靠性。因此,socket.SOCK_STREAM是socket模块提供的一种套接字类型。
b) socket.SOCK_DGRAM:
这是基于UDP协议的数据报套接字类型。它提供无连接的、不可靠的通信,以数据包(数据报)的形式进行通信,不保证数据的顺序和可靠性。socket.SOCK_DGRAM同样是socket模块提供的一种套接字类型。
c) socket.SOCK_RAW:
原始套接字允许程序员直接使用IP协议进行通信,而不经过TCP或UDP等传输层协议的封装。这种套接字类型通常用于需要直接操作IP报文头的场景,如实现自定义的网络协议或进行网络诊断等。因此,socket.SOCK_RAW也是socket模块提供的一种套接字类型。
d) socket.SOCK_HTTP:
HTTP(超文本传输协议)是一种应用层协议,它基于TCP协议进行通信。然而,在Python的socket模块中,并没有直接提供名为socket.SOCK_HTTP的套接字类型。socket模块提供的套接字类型主要对应于传输层协议(如TCP和UDP),而不包括应用层协议(如HTTP)。
22、+ 匹配前面的字符一次或多次。例如,ab+ 可以匹配 ab、abb,但不能匹配 a
* 量词(*
)
- 作用:匹配前面的字符零次或多次。
- 示例:
a*
可以匹配空字符串(因为没有出现a
)、a
、aa
、aaa
等。ab*
可以匹配a
(因为b
出现零次)、ab
、abb
、abbb
等。
+ 量词(+
)
- 作用:匹配前面的字符一次或多次。
- 示例:
a+
可以匹配a
、aa
、aaa
等,但不能匹配空字符串(因为至少需要出现一次a
)。ab+
可以匹配ab
、abb
、abbb
等,但不能匹配a
(因为b
至少需要出现一次)。
23、正则表达式中的捕获组 (\w+)
是设计来捕获单词字符的,而不是整个短语
24、python删除文件
25、read、readline、readlines
26、__init__.py
27、python的内存管理
28、PEP
29、if __name__=='__main__'
在Python中,if __name__ == '__main__':
这行代码非常常见,它的作用是指定当该脚本被直接运行时应该执行的代码块。这里的 __name__
是一个特殊的变量,它代表了当前模块的名字。
- 当Python文件被直接运行时,
__name__
的值会被设置为'__main__'
。 - 而当该文件被其他文件导入(作为模块使用)时,
__name__
的值则会被设置为该文件的模块名。
因此,if __name__ == '__main__':
这个条件判断确保了接下来的代码块只有在该文件被直接运行时才会执行,而在被导入为模块时则不会执行。
这种做法有几个好处:
-
代码重用:你可以将常用的函数、类等放在一个Python文件中,然后通过导入这个文件来使用其中的内容,而不需要每次都复制粘贴。
-
避免命名冲突:当你导入一个模块时,模块中的变量名和函数名等都会成为该模块的属性,而不是全局变量。这样可以避免命名冲突。
-
提供示例或测试代码:在模块的底部使用
if __name__ == '__main__':
可以添加一些示例代码或测试代码,这些代码只有在文件被直接运行时才会执行,而在被导入时不会执行。
举个例子:
# mymodule.py
def foo():
print("Hello from foo!")
if __name__ == '__main__':
# 这里的代码只有在mymodule.py被直接运行时才会执行
foo()
print("mymodule is being run directly")
如果你直接运行 mymodule.py
,输出将会是:
Hello from foo!
mymodule is being run directly
但是,如果你从另一个Python文件中导入 mymodule
:
# anothermodule.py
import mymodule
mymodule.foo()
并且运行 anothermodule.py
,输出将会是:
Hello from foo!
注意,mymodule.py
中的 if __name__ == '__main__':
块下的代码没有执行,因为 mymodule.py
是被导入的,而不是直接运行的。
30、for i in range(start,stop,step)中的注意事项(案例引入)
先引出个例子:我们在进行顺序表的去重函数的封装中:
①首先想到的比较简单的方法如下:
#去重
def del_repeat(self):
i=0
while i<self.size:
j=i+1
while j<self.size:
if self.data[i] == self.data[j]:
#按位置删除
self.del_idex(j)
j-=1 #防止漏删
j+=1
i+=1
其中我们如果遇到了相邻的重复项,若不进行 j-=1 ,就会漏删,只有if判断内增加了j-=1的操作才能保证不把相邻项遗漏
但是for循环呢?
②for i in range形式:
def remove_repeat(self):
if self.is_empty() or self.size == 1 :
print('该表不用去重')
else:
for i in range(self.size):
for j in range(i+1,self.size):
if self.data[i] == self.data[j]:
self.del_index(j)
给个测试样例:
我们发现没有去重成功。这是因为对于循环来说在找到重复项后每个元素前移,而我们仍然检测下一位元素,故而有遗漏,那如果加个j-=1呢?
③内层循环检测块加了一个 j-=1
def remove_repeat(self):
if self.is_empty() or self.size == 1 :
print('该表不用去重')
else:
for i in range(self.size):
for j in range(i+1,self.size):
if self.data[i] == self.data[j]:
self.del_index(j)
j-=1
结果呢?继续测试
仍然没有变
这是为什么呢?
不如让我们重新看看for 和 range 是怎么运作的吧
range 返回一个迭代器,for i in range(start,stop,step)
就是把i不断地被range返回的迭代对象赋值,循环操作(stop-start)//step次
🤓☝️那么就好说了,
不论我们在循环内执行语句如何对i如何赋值或者运算,都不影响后续的循环,迭代对象一经确定就不可变。i只会从已经确定的range()内取值。
所以 j-=1 不可行
那有没有办法使得案例的代码即可以用for运行而且实现同样的去重效果呢?
④改变for循环的遍历方向
#去重修改
def remove_repeat(self):
if self.is_empty() or self.size == 1 :
print('该表不用去重')
else:
for i in range(self.size - 1, 0, -1):
for j in range(i):
if self.data[i] == self.data[j]:
self.del_index(i)
看看结果吧:
成功
这里我们将外层循环倒着遍历,内层从头开始遍历前半段,匹配是否有重复,若有删除后半段即下标为i的元素,删除后,元素前移,但是这里前移到 i 位置的元素已经被我们检测过了,所以不会重复也不会影响结果,直到下一次外层遍历。(若是最后一个元素重复而删除了的话,则是self.data[self.size]前移,这个值设定为None,所以也不影响)
这样做有效的避免了对相邻的重复元素遗漏的检测
31、顺序表是逻辑上数据相连,物理内存上也连续存储。
没有逻辑地址上相连的一说
32、如果我要判断p.next是否为None,我是用while p.next==None: 还是while p.next is None:
在Python中,当您想要判断p.next
是否不是None
时,应该使用is not
操作符,而不是!=
。虽然对于None
的比较来说,!=
和is not
在大多数情况下会给出相同的结果,但is not
更加符合Python的惯用法,并且它检查的是对象的身份(即两个变量是否指向同一个对象),这对于None
这样的单例对象来说是特别合适的。
while p.next is not None:
# 在这里处理p,然后移动到下一个节点
p = p.next
这个循环会一直执行,直到p.next
变为None
,此时循环条件变为假,循环结束。在链表遍历的上下文中,这通常意味着您已经到达了链表的末尾。
使用!=
虽然可能在大多数情况下也能工作,但它实际上是在进行值的比较,而不是身份的比较。对于像None
这样的单例对象来说,这种区别可能不重要,但在处理其他类型的对象时,这种区别可能会变得很重要。因此,最好始终使用is
和is not
来进行None
的比较。
33、(延续上一个问题)为什么我给定一个变量的时候我可以直接 i=None 呢?
在Python中,当您给变量赋值None
时,您实际上是在将变量指向Python的特殊单例对象None
。None
是一个特殊的常量,用于表示“没有值”或“空值”。它是Python中的一个内置对象,并且只有一个实例。
当您执行i = None
时,您是在做以下几件事情:
- 创建一个名为
i
的新变量(如果它之前不存在的话)。 - 将变量
i
指向Python中的None
对象。
由于None
是一个单例,这意味着在Python程序中,无论您在哪里创建None
的引用,它们实际上都是指向同一个对象。因此,比较两个变量是否都是None
时,您可以使用is
操作符来检查它们是否指向同一个对象。
a = None
b = None
print(a is b) # 输出: True,因为a和b都指向同一个None对象
print(a == b) # 输出: True,因为==对于None来说也会返回True,但这里是通过值的比较得出的
在这个例子中,a
和b
都被赋值为None
,因此它们指向同一个对象。使用is
操作符来比较它们会返回True
,因为它们在内存中的位置是相同的。而使用==
操作符也会返回True
,因为对于None
来说,==
和is
的行为是一致的。但是,通常推荐使用is
和is not
来比较None
,因为这更符合Python的惯用法,并且清晰地表明了您是在进行身份比较而不是值比较。
34、谈下Python的GIL?
GIL是Python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行Python程序的时候会霸占Python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行
35、链表的head结点可以省略吗
是可以的,头结点是一个虚设的结点,用于方便访问链表,但是省略后若没有其他变量存储位置信息可能会导致链表丢失
36、查找倒数第k个结点(不知道size情况下用快慢指针法)
法一:单链表知道表长
#查找倒数第k个结点
def find_last_k(self,k):
if self.is_empty() or k<1 or k>self.size:
return False
q=self.head
i=1
while True:
if i==self.size-k+1:
return q #这里返回的是结点,要数据的话自行打印.data
q=q.next
i+=1
法二:未知表长
def find_last_k(self, k):
if self.head is None or k < 1:
raise ValueError("Invalid input: k should be a positive integer and list should not be empty.")
if k > self.size:
raise IndexError("k is out of bounds for the list size.")
# 使用快慢指针法
slow = self.head
fast = self.head
# 将快指针向前移动k-1步
for _ in range(k - 1):
if fast.next:
fast = fast.next
else:
raise IndexError("k is out of bounds for the list size.")
# 同时移动快慢指针,直到快指针到达末尾
while fast.next:
slow = slow.next
fast = fast.next
return slow # 返回倒数第k个节点
37、银行家舍入和标准四舍五入,以及如何用f-string实现
标准四舍五入
标准四舍五入是我们日常生活中最常见的一种舍入方式。它的规则很简单:
- 如果需要舍入的数字小于5,则直接舍去。
- 如果需要舍入的数字大于或等于5,则进位。
例如,对于数字2.345,如果我们想要保留两位小数,那么根据标准四舍五入规则,它会被舍入为2.35,因为第三位小数是5,根据规则需要进位。
银行家舍入
银行家舍入,又称四舍六入五取偶(或四舍六入五留双)法,是一种在某些特定领域(如金融、计算机科学)中更常用的舍入方法。它的规则如下:
- 如果需要舍入的数字小于5,则直接舍去。
- 如果需要舍入的数字大于5,则进位。
- 如果需要舍入的数字等于5,则需要查看5前面的数字:
- 如果5前面是奇数,则进位。
- 如果5前面是偶数(包括0,因为0也是偶数),则舍去5。
- 如果5的后面还有不为0的任何数,则无论5的前面是奇数还是偶数,均应进位。
标准四舍五入可以用round()实现:
value = 3.14159
rounded_value = round(value, 2) # 保留两位小数
print(rounded_value) # 输出: 3.14
或者用f-string实现,在Python的f-string中,当你使用格式说明符来指定保留的小数位数时,数字是通过四舍五入到特定位来格式化的。f-string不会直接“截断”小数部分到特定位,而是会进行标准的四舍五入操作。
value = 3.14159
formatted_value = f"{value:.2f}" # 保留两位小数
print(formatted_value) # 输出: 3.14
如果要保留到特定位呢(我不要四舍五入了)?
- 将浮点数乘以10的指定次方(以移动小数点到您想要截断的位置)。
- 使用
math.floor()
向下取整(或int(x//1)
,其中x
是步骤1的结果,但在这个特定情况下,math.floor()
可能更清晰)。- 再将结果除以10的指定次方(以将小数点移回原位)。
38、python3.0在算术运算上的小问题
r=float(input())
print(f'{2*r:.4f} {2*r*3.14159:.4f} {3.14159*r*r:.4f}')
在该计算圆的各项属性的代码中,计算面积的公式pi*r**2最好写为pi*r*r,因为这个版本解释器可能识别不出来
39、python中的浮点数
在Python中,浮点数默认使用双精度(double precision)格式,也就是符合IEEE 754标准的64位浮点数。这意味着,当你声明或使用浮点数时,它们通常已经是双精度浮点数
a = 3.14 # 双精度浮点数
b = 2.71828 # 双精度浮点数
c = 1e308 # 双精度浮点数,可以表示非常大或非常小的数
x = "3.14"
y = float(x) # y现在是双精度浮点数
python的numpy
库也提供了对浮点数精度的控制,包括单精度(float32)和双精度(float64)。在numpy
中,你可以显式地指定数据类型:
import numpy as np
a = np.float64(3.14) # 双精度浮点数
b = np.array([1.0, 2.0, 3.0], dtype=np.float64) # 包含双精度浮点数的数组
如果你没有使用numpy
或其他科学计算库,并且只是需要普通的浮点数运算,Python内置的float
类型已经足够,并且默认是双精度
40、.ng格式符(去掉不必要的0)
a,b=map(float,input().split()) print(f'{a-(a//b)*b:.7g}')
.2f
(以及类似的.nf
,其中n
是任何正整数)格式说明符在Python的f-string
格式化中用于将数字四舍五入到指定的小数位数,并保留这些小数位作为字符串的一部分
.ng也是先四舍五入后再去0
41、如何实现不断输入多组数据测试程序呢(程序直到错误才停止)
while循环内使用try except处理,有异常break即可
import math
while True:
try:
# 提示用户输入半径(可以输入多个半径,用空格分隔)
user_input = input("请输入球的半径(用空格分隔多个半径,或输入非数字退出):")
# 将输入的字符串按空格分割成多个子字符串,并尝试将它们转换为浮点数
radii = list(map(float, user_input.split()))
# 遍历半径列表,计算并打印每个球的体积
for r in radii:
volume = (4/3) * math.pi * r**3
print(f"半径为 {r} 的球的体积是 {volume:.3f}")
except ValueError:
# 如果转换过程中出现 ValueError 异常(例如,输入包含非数字字符),则退出循环
print("输入错误,程序将停止。")
break
42、如果列表中的元素是自定义对象,如何使 sorted() 函数根据某个属性排序?
A. 使用 key 参数,传递一个函数或 lambda 表达式来指定排序规则
B. 修改自定义对象的 __sort__ 方法
C. 使用 reverse 参数
D. 使用 key 参数,但只能传递字符串类型的字段
A. 使用 key
参数,传递一个函数或 lambda 表达式来指定排序规则。
解释如下:
-
A 选项:这是正确的方法。
sorted()
函数的key
参数允许你指定一个函数,该函数会被用于从每个列表元素中提取一个用于比较的关键字(key)。这个关键字决定了元素的排序顺序。你可以传递一个普通的函数或一个 lambda 表达式作为key
参数。例如,如果你有一个自定义对象Person
,并且你想根据Person
对象的age
属性进行排序,你可以这样做: -
sorted_people = sorted(people, key=lambda person: person.age)
43、sorted()和 list.sort()
list.sort()
是列表的一个方法,它会直接修改原列表,进行原地排序,不返回任何值(或者说返回值为None
)。sorted()
是一个内置函数,它不会修改原列表,而是返回一个新的、已排序的列表。
- 两者都接受
key
、reverse
和(在Python 3.x中已移除的)cmp
参数。key
参数用于指定一个函数,该函数从每个列表元素中提取一个用于比较的关键字(key)。reverse
参数是一个布尔值,如果为True
,则列表元素将被降序排列;如果为False
(默认值),则列表元素将被升序排列。cmp
参数在Python 3.x中已被移除,因为它与函数式编程的风格不符,且可以通过key
参数来实现相同的功能。
44、sorted() 和 list.sort() 都是稳定排序算法,即当两个元素相等时,它们在排序后保持原来的相对位置。
Timsort主打一个稳定排序:结合了直接插入排序和归并排序
45、.编写一个函数,使用 sorted() 对字典按值排序,并返回排序后的列表。
def sort_dict_by_value(d):
return sorted(d.items(), key=lambda x: x[1])
# 示例
d = {'apple': 3, 'banana': 2, 'cherry': 5, 'date': 4}
print(sort_dict_by_value(d)) # 输出: [('banana', 2), ('apple', 3), ('date', 4), ('cherry', 5)]
编写一个函数,使用 sorted() 对列表中的元组按第二个元素进行降序排序。
def sort_tuples_by_second(arr):
return sorted(arr, key=lambda x: x[1], reverse=True)
# 示例
arr = [(1, 4), (2, 2), (3, 6), (4, 3)]
print(sort_tuples_by_second(arr)) # 输出: [(3, 6), (1, 4), (4, 3), (2, 2)]
编写一个函数,使用 sorted() 对字符串列表进行按字母升序排序,并忽略大小写。
def sort_strings(arr):
return sorted(arr, key=str.lower)
# 示例
arr = ['banana', 'Apple', 'cherry', 'date']
print(sort_strings(arr)) # 输出: ['Apple', 'banana', 'cherry', 'date']
46、快速排序的空间复杂度主要由什么决定?
A. 递归栈的深度
B. 数组分区时的临时数组
C. 排序过程中的临时变量
D. 直接操作数组,无需额外空间
47、为什么不能通过先序和后序返推二叉树
缺乏结构信息:
- 先序遍历的顺序是:根节点 -> 左子树 -> 右子树。
- 后序遍历的顺序是:左子树 -> 右子树 -> 根节点。
这两种遍历方式都只提供了节点访问的顺序,而没有提供关于节点之间连接关系的明确信息。特别是,它们都没有明确说明哪些节点是哪些节点的父节点、左子节点或右子节点
48、sep的作用
在Python的
sep
参数用于指定多个值之间的分隔符。默认情况下,如果不提供sep
参数,sep
参数来自定义这个分隔符。
print(1, 2, 3) # 输出: 1 2 3,默认分隔符是空格
print(1, 2, 3, sep='-') # 输出: 1-2-3,分隔符被设置为'-'
print(1, 2, 3, sep=', ') # 输出: 1, 2, 3,分隔符被设置为', '(逗号和空格)
print(1, 2, 3, sep='') # 输出: 123,没有分隔符
49、一行代码创建元素为 'a'-'z' 的列表
l=[chr(i) for i in range(ord('a'), ord('z') + 1)]
ord('a')
返回字符'a'的ASCII码(97),而ord('z')
返回字符'z'的ASCII码(122)。range(ord('a'), ord('z') + 1)
生成一个从97到122的整数序列(包括122),然后chr(i)
将这些整数转换回对应的字符