Python 生僻知识点汇总

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

猜你喜欢

转载自blog.csdn.net/a40850273/article/details/106232621