0%

Python, Are you kidding me?

发现了一个有趣的Github仓库——WTF Python,决定有空的时候学习学习。类似的还有WTF JS

Tested on Python 3.8

Strain your brain!

Tricky equivalence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b
False # 注:为复现这个结果,需要在交互式命令行中一行行执行。若执行整个文件,则解释器会自动优化a b为同一个地址。

>>> 5 == 5.0
True
>>> hash(5) == hash(5.0)
True
>>> 5 is 5.0
False (with warning)

>>> x = 1000000
>>> x is 1000000
False (with warning)

因此,不要过度使用isis的作用是判断地址是否相同。当我们创建-销毁一个对象后,再次创建,则CPython可能会给它同样的id.

执行时机差异

1
2
3
4
5
6
array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
array_1 = [1,2,3,4]
g1 = (x for x in array_1)
array_1 = [1,2,3,4,5]

array_2 = [1,2,3,4]
g2 = (x for x in array_2)
array_2[:] = [1,2,3,4,5]

>>> print(list(g1))
[1,2,3,4]

>>> print(list(g2))
[1,2,3,4,5]

新旧对象的差异。

闭包变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
funcs = []
results = []
for x in range(7):
def some_func():
return x
funcs.append(some_func)
results.append(some_func()) # 注意这里函数被执行了

funcs_results = [func() for func in funcs]

>>> results
[0, 1, 2, 3, 4, 5, 6]
>>> funcs_results
[6, 6, 6, 6, 6, 6, 6]

# 再换个例子
>>> powers_of_x = [lambda x: x**i for i in range(10)]
>>> [f(2) for f in powers_of_x]
[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

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

1
2
3
4
5
6
7
8
9
funcs = []
for x in range(7):
def some_func(x=x):
return x
funcs.append(some_func)

>>> funcs_results = [func() for func in funcs]
>>> funcs_results
[0, 1, 2, 3, 4, 5, 6]

可以通过将循环变量作为命名变量传递给函数来获得预期的结果.

赋值语句

我觉的这个自嵌套真的蛮有意思的。

1
2
3
4
a, b = a[b] = {}, 5

>>> a
{5: ({...}, 5)}
  • 根据 Python 语言参考, 赋值语句的形式如下

    1
    (target_list "=")+ (expression_list | yield_expression)

    赋值语句计算表达式列表(expression list)(牢记 这可以是单个表达式或以逗号分隔的列表, 后者返回元组)并将单个结果对象从左到右分配给目标列表中的每一项.

  • (target_list "=")+ 中的 + 意味着可以有一个或多个目标列表. 在这个例子中, 目标列表是 a, ba[b] (注意表达式列表只能有一个, 在我们的例子中是 {}, 5).

  • 表达式列表计算结束后, 将其值自动解包后从左到右分配给目标列表(target list). 因此, 在我们的例子中, 首先将 {}, 5 元组并赋值给 a, b, 然后我们就可以得到 a = {}b = 5.

  • a 被赋值的 {} 是可变对象.

  • 第二个目标列表是 a[b] (你可能觉得这里会报错, 因为在之前的语句中 ab 都还没有被定义. 但是别忘了, 我们刚刚将 a 赋值 {} 且将 b 赋值为 5).

  • 现在, 我们将通过将字典中键 5 的值设置为元组 ({}, 5) 来创建循环引用 (输出中的 {...} 指与 a 引用了相同的对象). 下面是一个更简单的循环引用的例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>> some_list = some_list[0] = [0]
    >>> some_list
    [[...]]
    >>> some_list[0]
    [[...]]
    >>> some_list is some_list[0]
    True
    >>> some_list[0][0][0][0][0][0] == some_list
    True

    我们的例子就是这种情况 (a[b][0]a 是相同的对象)

Watch out for the landmines!

循环变量泄露

1
2
3
4
for x in range(7):
if x == 6:
print(x, ': for x inside loop')
print(x, ': x in global')

for循环的变量会在循环结束后保留。

这…我觉得可能有些隐患。

字符串相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def add_string_with_plus(iters):
s = ""
for i in range(iters):
s += "xyz"
assert len(s) == 3*iters

def add_bytes_with_plus(iters):
s = b""
for i in range(iters):
s += b"xyz"
assert len(s) == 3*iters

def add_string_with_format(iters):
fs = "{}"*iters
s = fs.format(*(["xyz"]*iters))
assert len(s) == 3*iters

def add_string_with_join(iters):
l = []
for i in range(iters):
l.append("xyz")
s = "".join(l)
assert len(s) == 3*iters

def convert_list_to_string(l, iters):
s = "".join(l)
assert len(s) == 3*iters

>>> timeit(add_string_with_plus(100000)) # 执行时间线性增加
100 loops, best of 3: 9.75 ms per loop
>>> timeit(add_bytes_with_plus(100000)) # 二次增加
1000 loops, best of 3: 974 ms per loop
>>> timeit(add_string_with_format(100000)) # 线性增加
100 loops, best of 3: 5.25 ms per loop
>>> timeit(add_string_with_join(100000)) # 线性增加
100 loops, best of 3: 9.85 ms per loop
>>> l = ["xyz"]*100000
>>> timeit(convert_list_to_string(l, 100000)) # 线性增加
1000 loops, best of 3: 723 µs per loop

Python的字符串操作是十分迅速的,在造数据的时候发现甚至比std::string还要好。

让生活更友好

彩蛋

1
2
3
4
5
6
7
8
9
import this

import antigravity

from __future__ import braces

from __future__ import barry_as_FLUFL
>>> "Ruby" <> "Python"
True

每一行都是一个彩蛋。

else 的妙用

1
2
3
4
5
6
7
8
9
10
11
12
13
def does_exists_num(l, to_find):
for num in l:
if num == to_find:
print("Exists!")
break
else:
print("Does not exist")

>>> some_list = [1, 2, 3, 4, 5]
>>> does_exists_num(some_list, 4)
Exists!
>>> does_exists_num(some_list, -1)
Does not exist

循环后的 else 子句只会在循环没有触发 break 语句, 正常结束的情况下才会执行。

1
2
3
4
5
6
7
8
try:
pass
except:
print("Exception occurred!!!")
else:
print("Try block executed successfully...")

Try block executed successfully...

try 之后的 else 子句也被称为 “完成子句”, 因为在 try 语句中到达 else 子句意味着try块实际上已成功完成。

int()与其他语言

1
2
>>> int('١٢٣٤٥٦٧٨٩')
123456789

在 Python 中,十进制字符包括数字字符,以及可用于形成十进制数字的所有字符,例如:U+0660, ARABIC-INDIC DIGIT ZERO. 这有一个关于此的 有趣故事

可惜没有中文,Why? For example, the number 21 isn’t 二一, it’s 二十一.

其他

  • 在 Python 中, 解释器会通过给类中以 __ (双下划线)开头且结尾最多只有一个下划线的类成员名称加上_NameOfTheClass 来修饰(mangles)名称。从而实现“私有成员”。
  • Python默认只支持65536个本地变量。

完结撒花!以后看到有意思的故事还会在这里更新~