Python 生僻知识点汇总
该文大量借鉴了 https://github.com/leisurelicht/wtfpython-cn
□ except ... finally: finally 中的所有逻辑不管整个程序是否正常执行都将被执行,同时 finally 中的 return 将覆盖之前逻辑的执行结果,即使是异常也将被忽略。因此 finally 中建议只添加回收资源相关逻辑
def calculate(division):
try:
return 100 / division
except ZeroDivisionError as e:
raise ValueError("Invalid inputs")
finally:
return 0
print(calculate(0))
'''
最后不会报错,而是正常退出返回 0
'''
□ for ... in:for ... in 操作会优先调用 __iter__ 得到一个迭代器,之后调用 __next__ 去依次访问元素。因此,如果在迭代过程中修改元素内容,由于 __next__ 是按地址位访问元素,因此会出现异常情况。
这里还有一个知识点:可迭代 iterable 包含迭代器 iterator,而迭代器包含生成器 generator。同时只要可以通过 for 进行循环就是 iterable,iterator 就可以通过 next 依次访问元素,这是为唯一有用的遍历 iterator 的方法。下面用一张图简单说明一下。
list_1 = [1, 2, 3, 4]
list_2 = [1, 2, 3, 4]
list_3 = [1, 2, 3, 4]
list_4 = [1, 2, 3, 4]
for idx, item in enumerate(list_1):
del item
for idx, item in enumerate(list_2):
list_2.remove(item)
for idx, item in enumerate(list_3[:]):
list_3.remove(item)
for idx, item in enumerate(list_4):
list_4.pop(idx)
Output:
>>> list_1
[1, 2, 3, 4]
>>> list_2
[2, 4]
>>> list_3
[]
>>> list_4
[2, 4]
'''
del, remove 和 pop 的不同:
del var_name 只是从本地或全局命名空间中删除了 var_name (这就是为什么 list_1 没有受到影响).
remove 会删除第一个匹配到的指定值, 而不是特定的索引, 如果找不到值则抛出 ValueError 异常.
pop 则会删除指定索引处的元素并返回它, 如果指定了无效的索引则抛出 IndexError 异常.
'''
for i in range(4):
print(i)
i = 10
# 在每次迭代开始之前, 迭代器(这里指 range(4)) 生成的下一个元素就被解包并赋值给目标列表的变量(这里指 i)了.
'''
output:
0
1
2
3
'''
□ python =:python 中的赋值操作对于可变对象只是简单的引用赋值,两个引用实际指向同一个内存对象。
list1 = [1, 2, 3]
list2 = list1
list2[0] = 6
'''
list1 = [6, 2, 3]
list2 = [6, 2, 3]
'''
□ list 判空:PEP 8 中建议直接使用 list 和 not list 对列表进行非空和空的判断
□ 使用可变对象作为函数默认参数:参数的默认值在方法定义被执行时就已经设定了,这就意味着默认值只会被设定一次,当函数定义后,每次被调用时都会有"预计算"的过程。此时,如果函数内部修改了可变对象,那么之后的默认输入将是修改后的对象
#!/usr/bin/env python
# coding: utf-8
class Test(Object):
def process_data(self, data=[]):
# 排序
data.sort()
# 追加结束符
data.append("End")
return data
test1 = Test()
print(test1.process_data())
test2 = Test()
print(test2.process_data())
test3 = Test()
print(test3.process_data(data=["Name:123", "Age:34"]))
test4 = Test()
print(test4.process_data())
'''
输出:
['End']
['End', 'End']
["Age:34", "Name:123", 'End']
['End', 'End', 'End']
'''
如果确实需要默认为可变对象,可以使用如下方式
def some_func(default_arg=None):
if default_arg is None:
default_arg = []
default_arg.append("some_string")
return default_arg
□ nonlocal:nonlocal 只能获取到当前作用域外一层的局部变量,其扩张层数只能是1
n = 0
def f():
def inner():
nonlocal n
n = 2
n = 1
print(n)
inner()
print(n)
if __name__ == "__main__":
f()
print()
'''
输出:
1
2
0
'''
□ list 切片:list 在做切片时,stop 应该 start 的逻辑右边,否则输出为空
TempStr = 'Hello World'
print(TempStr[-5:-1])
print(TempStr[-5:0])
print(TempStr[-4:-1])
print(TempStr[-5:])
'''
输出:
Worl
orl
World
'''
□ __all__:__all__ 只能限制 from ... import * 方式导出的对象,此时只有 __all__ 中指定的对象才能被导出。但是通过 from ... import ... 依然可以显式导出所有对象。
□ 闭包值传递:通过闭包传递的值是通过对象地址传递,因此外部修改了传入值,闭包内部的结果也将受影响。但如果在内部直接修改传入值,那么局部作用域中的闭包传入值将失效,可能提示未不复制。此时如果使用 nonlocal 指定闭包传入值,那么是可以实现闭包内部对外部变量的修改的。
#!/usr/bin/env python
# coding: utf-8
def a(s):
def b():
print(i)
# i = 2 # 添加该行,由于指定了局部变量,对应闭包传递过来的对象引用被覆盖,将调试在赋值前引用
# print(i)
i = s
print(i)
return b
if __name__ == '__main__':
tb1 = a(1)
tb1()
tb2 = a(2)
tb1()
tb2()
'''
输出:
1
1
2
1
2
'''
□ 闭包值传递:当在循环内部定义一个函数时, 如果该函数在其主体中使用了循环变量, 则闭包函数将与循环变量绑定, 而不是它的值. 因此, 所有的函数都是使用最后分配给变量的值来进行计算的.
def f():
t = [lambda x: i*x for i in range(4)]
return t
print([M(2) for M in f()])
'''
output:
[6, 6, 6, 6]
'''
'''
这个例子本质不是闭包值传递,而是正常的函数值传递
而由于每次都会创建一个局部变量,不同函数对应的局部变量的内存必然不同,因此不存在影响
'''
funcs = []
for x in range(7):
def some_func(x=x):
return x
funcs.append(some_func)
Output:
>>> funcs_results = [func() for func in funcs]
>>> funcs_results
[0, 1, 2, 3, 4, 5, 6]
□ in 子句的执行时机:在生成器表达式中, in 子句在声明时执行, 而条件子句则是在运行时执行
array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]
print(list(g))
"""
output:
[8]
"""
array_1 = [1,2,3,4]
g1 = (x for x in array_1)
array_1 = [1,2,3,4,5]
print(list(g1))
array_2 = [1,2,3,4]
g2 = (x for x in array_2)
array_2[:] = [1,2,3,4,5]
print(list(g2))
"""
output:
[1,2,3,4]
[1,2,3,4,5]
"""
□ list 对象乘法操作:对于 list 中包含可变对象,使用乘法进行复制,所有复制的可变对象将指向同一个内存空间
# 我们先初始化一个变量row
row = [""]*3 #row i['', '', '']
# 并创建一个变量board
board = [row]*3
>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]
□ is not:is not 是个单独的二元运算符, 与分别使用 is 和 not 不同。如果操作符两侧的变量指向同一个对象, 则 is not 的结果为 False, 否则结果为 True。
□ r 标识的反斜杠字符串:解释器所做的只是简单的改变了反斜杠的行为, 因此会直接放行反斜杠及后一个的字符。此时如果字符串最后是反斜杠,将报错 SyntaxError: EOL while scanning string literal
□ bool 和 int:布尔值是 int 的子类,所以 True 的整数值是 1, 而 False 的整数值是 0
□ 类属性和实例属性:类变量和实例变量在内部是通过类对象的字典来处理(就是 __dict__ 属性). 如果在当前类的字典中找不到的话就去它的父类中寻找。+= 运算符会在原地修改可变对象, 而不是创建新对象. 因此, 在这种情况下, 修改一个实例的属性会影响其他实例和类属性.
class A:
x = 1
class B(A):
pass
class C(A):
pass
Output:
>>> A.x, B.x, C.x
(1, 1, 1)
>>> B.x = 2
>>> A.x, B.x, C.x
(1, 2, 1)
>>> A.x = 3
>>> A.x, B.x, C.x
(3, 2, 3)
>>> a = A()
>>> a.x, A.x
(3, 3)
>>> a.x += 1
>>> a.x, A.x
(4, 3)
class SomeClass:
some_var = 15
some_list = [5]
another_list = [5]
def __init__(self, x):
self.some_var = x + 1
self.some_list = self.some_list + [x]
self.another_list += [x]
Output:
>>> some_obj = SomeClass(420)
>>> some_obj.some_list
[5, 420]
>>> some_obj.another_list
[5, 420]
>>> another_obj = SomeClass(111)
>>> another_obj.some_list
[5, 111]
>>> another_obj.another_list
[5, 420, 111]
>>> another_obj.another_list is SomeClass.another_list
True
>>> another_obj.another_list is some_obj.another_list
True
class Base:
inited = False
@classmethod
def set_inited(cls): # 实际可能传入Derived类
cls.inited = True # 并没有修改Base.inited,而是给Derived添加了成员
class Derived(Base):
pass
x = Derived()
x.set_inited()
if Base.inited:
print("Base is inited") # 不会被执行
□ += 操作符:+= 操作符在原地修改了列表. 元素赋值操作并不工作, 但是当异常抛出时, 元素已经在原地被修改了
'''
对于不可变对象, 这里指tuple, += 并不是原子操作, 而是 extend 和 = 两个动作
这里 = 操作虽然会抛出异常, 但 extend 操作已经修改成功了
'''
some_tuple = ("A", "tuple", "with", "values")
another_tuple = ([1, 2], [3, 4], [5, 6])
>>> some_tuple[2] = "change this"
TypeError: 'tuple' object does not support item assignment
>>> another_tuple[2].append(1000) # 这里不出现错误
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000])
>>> another_tuple[2] += [99, 999]
TypeError: 'tuple' object does not support item assignment
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000, 99, 999])
□ numpy.empty():numpy.empty() 直接返回下一段空闲内存,而不重新初始化。
import numpy as np
def energy_send(x):
# 初始化一个 numpy 数组
np.array([float(x)])
def energy_receive():
# 返回一个空的 numpy 数组
return np.empty((), dtype=np.float).tolist()
Output:
>>> energy_send(123.456)
>>> energy_receive()
123.456
□ 迭代过程中的字典修改:尽量不要在迭代过程中去修改迭代对象,往往结果无法预期。
x = {0: None}
for i in x:
del x[i]
x[i+1] = None
print(i)
Output (Python 3.7):
# 这个结果好像跟字典的自动扩容有关,扩容会导致散列表地址发生变化而中断循环
0
1
2
3
4
□ 生成器表达式和列表递推式的变量作用域:类定义中嵌套的作用域会忽略类内的名称绑定。生成器表达式有它自己的作用域。从 Python 3.X 开始, 列表推导式也有自己的作用域。
# 这次我们先初始化x
x = -1
for x in range(7):
if x == 6:
print(x, ': for x inside loop')
print(x, ': x in global')
Output:
6 : for x inside loop
6 : x in global
x = 1
print([x for x in range(5)])
print(x, ': x in global')
Output (on Python 2.x):
[0, 1, 2, 3, 4]
(4, ': x in global')
# python 3 的列表推导式不再泄露
Output (on Python 3.x):
[0, 1, 2, 3, 4]
1 : x in global
x = 5
class SomeClass:
x = 17
y = (x for i in range(10))
Output:
>>> list(SomeClass.y)[0]
5
x = 5
class SomeClass:
x = 17
y = [x for i in range(10)]
Output (Python 2.x):
>>> SomeClass.y[0]
17
Output (Python 3.x):
>>> SomeClass.y[0]
5
□ else:循环后的 else 子句只会在循环没有触发 break 语句, 正常结束的情况下才会执行。try 之后的 else 子句也被称为 "完成子句", 因为在 try 语句中到达 else 子句意味着try块实际上已成功完。
def does_exists_num(l, to_find):
for num in l:
if num == to_find:
print("Exists!")
break
else:
print("Does not exist")
Output:
>>> some_list = [1, 2, 3, 4, 5]
>>> does_exists_num(some_list, 4)
Exists!
>>> does_exists_num(some_list, -1)
Does not exist
try:
pass
except:
print("Exception occurred!!!")
else:
print("Try block executed successfully...")
Output:
Try block executed successfully...
□ 双下划线私有变量:在 Python 中, 解释器会通过给类中以 __
(双下划线)开头且结尾最多只有一个下划线的类成员名称加上_NameOfTheClass
来修饰(mangles)名称
class Yo(object):
def __init__(self):
self.__honey = True
self.bitch = True
Output:
>>> Yo().bitch
True
>>> Yo().__honey
AttributeError: 'Yo' object has no attribute '__honey'
>>> Yo()._Yo__honey
True