Interesting python

💡 一些有趣且鲜为人知的 Python 特性

Run at Python 3.8.11

Bad key

dct = {
   }
dct[5] = "java"
dct[5.0] = "python"

Output:

>>> dct[5.0]
python

>>> dct[5]
python

Remark:

  • Python 字典通过键的值是否相等以及比较哈希值来确定两个键是否相同

  • 具有相同值的不可变对象在 Python 中始终具有相同的哈希值

    >>> 5 == 5.0
    True
    >>> hash(5) == hash(5.0)
    True
    

奇怪的return

def demo():
    try:
        return "try"
    finally:
        return "finally"

Output:

>>> demo()
finally

Remark:

当在 try...finally 语句的 try 中执行 return, breakcontinue 后,finally 子句依然会执行

特殊 for

s = "wtf"
dct = {
   }

for i, dct[i] in enumerate(s):
	pass

Output:

>>> dct
{
   0: 'w', 1: 't', 2: 'f'}

Remark:

Python 语法中对 for 的定义是:

for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]

其中 exprlist 指分配目标,这意味着可迭代对象中的每一项都会执行类似 {exprlist} = {next_value} 的操作,上述代码等效于:

>>> i, dct[i] = (0, 'w')
>>> i, dct[i] = (1, 't')
>>> i, dct[i] = (2, 'f')

执行时机差异

arr = [1, 2, 3]
g = (x for x in arr if arr.count(x) > 0)
arr = [2, 4, 6]

Output:

>>> print(list(g))
[2]

Remarks:

  • 在生成器表达式中,in 子句在声明时执行,而条件子句则是在运行时执行

赋值方式差异

# demo1
lst = [1, 2]
print(id(lst))    # 140444505190976
lst = [3, 4]
print(id(lst))    # 140445040739584

# demo2
lst2 = [1, 2]
print(id(lst2))   # 140444504561920
lst2[:] = [3, 4]
print(id(lst2))   # 140444504561920

Remarks:

  • 在 demo1 中,lst 被绑定到新对象
  • 在 demo2 中,lst2的切片赋值为原地更新

浅拷贝

l = [''] * 3
ll = [l] * 3
print(ll)      # [['', '', ''], ['', '', ''], ['', '', '']]

ll[0][0] = 'x'
print(ll)      # [['x', '', ''], ['x', '', ''], ['x', '', '']]

奇怪的function

lst = [lambda a:a*x for x in range(4)]
res = [func(1) for func in lst]
print(res)

# [3, 3, 3, 3]

Remarks:

  • 当在循环内部定义一个函数时,如果该函数在其主体内部使用了循环变量,则闭包函数将于循环变量绑定,而不是它的值
  • 因此,所有的函数都是使用最后分配给变量的值 (3)来进行计算的

隐式连接

>>> print('wtf''')    # wtf
>>> print("wtf""")    # wtf

>>> print("""wtf")    # raise SyntaxError
>>> print('''wtf')    # raise SyntaxError

Remarks:

  • Python 提供隐式的字符串连接,例:

    >>> print("wtf" "python")  # wtfpython
    
  • '''""" 在 Python 中是一组字符串定界符,Python 解释器在先遇到三个引号的时候会尝试再寻找三个终止引号作为定界符,若不存在则会导致 SyntaxError 异常

What’s wrong with boolean

lst = [1, 1.0, '123', True, False]

int_num = 0
bool_num = 0

for i in lst:
    if isinstance(i, int):
        int_num += 1
    elif isinstance(i, bool):
        bool_num += 1
        
print(int_num)     # 3
print(bool_num)    # 0

Remarks:

  • 布尔是 int 的子类
  • True 的整数值是 1,False 的整数值是 0

丢失的变量

e = 7

try:
	raise Exception()
except Exception as e:
	pass

print(e)   # NameError: name 'e' is not defined

Remarks:
当使用 as 为目标分配异常的时候,将在 except 子句的末尾清除该异常

except Exception as e:
	foo

# 会被翻译为:

except Exception as e:
	try:
		foo
	finally:
		del e

跳过一行

value = 11
valuе = 32

print(value)   # 11

Remarks:

<aside> 💡 如果你想重现的话,最好的方法是直接复制上面的代码片段到你的文件中 </aside>

一些非西方字符虽然看起来和英语字母相同,但会被解释器识别为不同的字母

>>> ord('е') # 西里尔语的 'e' (Ye)
1077

>>> ord('e') # 英文字母 'e'
101

>>> 'е' == 'e'
False

空间移动

# numpy.__version__ 1.21.2

import numpy as np

def energy_send(x):
    # 初始化一个 numpy 数组
    np.array([float(x)])

def energy_receive():
    # 返回一个空的 numpy 数组
    return np.empty((), dtype=np.float).tolist()

energy_send(123.456)
print(energy_receive())     # 123.456

Remarks:

  • 注意在 energy_send 函数中创建的 numpy 数组并没有返回,因此内存空间被释放并可以被重新分配
  • numpy.empty() 直接返回下一段空闲内存,而不重新初始化,而这个内存节点恰好就是刚刚释放的那个(通常情况下,并不绝对)

同人不同命

a = [1, 2]
b = a
a = a + [3]
print(a)    # [1, 2, 3]
print(b)    # [1, 2]

a = [1, 2]
b = a
a += [3]
print(a)   # [1, 2, 3]
print(b)   # [1, 2, 3]

Remarks:

  • a += b 并不总是与 a = a + b 表现相同