", line 1
18 | def h(*args,):
19 | ^
20 | SyntaxError: invalid syntax
21 | ```
22 |
23 | #### 💡 解释:
24 |
25 | - 末尾的逗号在函数参数列表最后并不总是合法的
26 | - 在Python中,参数列表里,有一部分使用前导逗号分隔的,有一部分是用后导逗号分隔的(比如`**kwargs`这种参数用前导逗号分隔,正常参数`x`用后导逗号分隔)。而这种情况就会导致有些参数列表里的逗号前后都没有用到,就会产生冲突导致编译失败。
27 | - **注意** 这种尾部逗号的问题已经在Python 3.6中被[修复](https://bugs.python.org/issue9232)了。然后[这里](https://bugs.python.org/issue9232#msg248399)有对各种尾部逗号用法的讨论。
28 |
29 | ---
--------------------------------------------------------------------------------
/individual/47.被修改的类成员.md:
--------------------------------------------------------------------------------
1 | ### ▶ 被修改的类成员 *
2 |
3 | ```py
4 | class Yo(object):
5 | def __init__(self):
6 | self.__honey = True
7 | self.bitch = True
8 | ```
9 |
10 | **Output:**
11 | ```py
12 | >>> Yo().bitch
13 | True
14 | >>> Yo().__honey
15 | AttributeError: 'Yo' object has no attribute '__honey'
16 | >>> Yo()._Yo__honey
17 | True
18 | ```
19 |
20 | 为什么非要`Yo()._Yo__honey`才对? 如果有印度朋友他一定会懂这个梗的(好吧,我猜不会有印度人看一个中文的文章,Yo Yo Honey Singh是一个印度著名rapper,这就是梗,也是为什么这个类要起名Yo,哈哈).
21 |
22 | #### 💡 解释:
23 |
24 | * [名称混淆](https://en.wikipedia.org/wiki/Name_mangling)是一种用来避免命名空间下命名冲突的技术。
25 | * 在Python里,如果某个类成员的名称以`__`(两个下划线)开头,并且不以两个以上的下划线结尾(这就意味着你想定义一个私有变量,但是Python是没有私有变量的),那么编辑器就会自动在这个成员名称之前加上`_NameOfTheClass`(在这里就是`_Yo`),来防止子类不小心访问修改到这个成员属性。
26 | * 所以,当要访问`__honey`这个成员属性的时候,我们需要在前面加上`_Yo`来进行访问。
27 |
28 | ---
--------------------------------------------------------------------------------
/individual/35.相同的操作,不同的结果.md:
--------------------------------------------------------------------------------
1 | ### ▶ 相同的操作,不同的结果
2 |
3 | 1\.
4 | ```py
5 | a = [1, 2, 3, 4]
6 | b = a
7 | a = a + [5, 6, 7, 8]
8 | ```
9 |
10 | **Output:**
11 | ```py
12 | >>> a
13 | [1, 2, 3, 4, 5, 6, 7, 8]
14 | >>> b
15 | [1, 2, 3, 4]
16 | ```
17 |
18 | 2\.
19 | ```py
20 | a = [1, 2, 3, 4]
21 | b = a
22 | a += [5, 6, 7, 8]
23 | ```
24 |
25 | **Output:**
26 | ```py
27 | >>> a
28 | [1, 2, 3, 4, 5, 6, 7, 8]
29 | >>> b
30 | [1, 2, 3, 4, 5, 6, 7, 8]
31 | ```
32 |
33 | #### 💡 解释:
34 |
35 | * `a += b` 和 `a = a + b`的内部处理流程有时候会根据操作数的不同有所不同。比如*`op=`*这种操作符(比如`+=`,`-=`,`*=`这样),针对列表(list)对象这两个表达式就会有不一样的内在表现。
36 |
37 | * 表达式`a = a + [5,6,7,8]`会生成一个新的对象,并且`a`会引用这个新生成的对象,`b`变量不会变(继续引用之前的列表对象`[1,2,3,4]`)。
38 |
39 | * 表达式`a += [5,6,7,8]`就不会生成一个新对象,它实际上是在原有对象的基础上又“扩展了”几个元素,也就是所谓的就地改变(in-place)。所以这个时候`a`,`b`两个变量还是同时引用的一个对象。
40 |
41 | ---
--------------------------------------------------------------------------------
/individual/22.转瞬即空.md:
--------------------------------------------------------------------------------
1 | ### ▶ 转瞬即空
2 |
3 | ```py
4 | some_list = [1, 2, 3]
5 | some_dict = {
6 | "key_1": 1,
7 | "key_2": 2,
8 | "key_3": 3
9 | }
10 |
11 | some_list = some_list.append(4)
12 | some_dict = some_dict.update({"key_4": 4})
13 | ```
14 |
15 | **Output:**
16 | ```py
17 | >>> print(some_list)
18 | None
19 | >>> print(some_dict)
20 | None
21 | ```
22 |
23 | #### 💡 解释
24 |
25 | 大部分修改序列结构对象的方法,比如`list.append`,`dict.update`,`list.sort`等等。都会直接对序列对象本身进行操作,也就是所谓的“就地操作”(in-plcae)并且会返回`None`。这么做是为了提升程序的性能,避免还需要把原有对象拷贝一次。(参考[这里](http://docs.python.org/2/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list))。
26 |
27 | ```py
28 | some_list = [1, 2, 3]
29 | ```
30 |
31 | **Output:**
32 | ```py
33 | >>>some_list2 = some_list.append(4)
34 | >>>some_list2
35 | None
36 | >>>some_list
37 | [1, 2, 3, 4]
38 | ```
39 |
40 | ---
--------------------------------------------------------------------------------
/individual/41.为什么要用`goto`?.md:
--------------------------------------------------------------------------------
1 | ### ▶ 为什么要用`goto`? *
2 |
3 | ```py
4 | from goto import goto, label
5 | for i in range(9):
6 | for j in range(9):
7 | for k in range(9):
8 | print("I'm trapped, please rescue!")
9 | if k == 2:
10 | goto .breakout #从一个深层循环内跳出
11 | label .breakout
12 | print("Freedom!")
13 | ```
14 |
15 | **Output (Python 2.3):**
16 | ```py
17 | I'm trapped, please rescue!
18 | I'm trapped, please rescue!
19 | Freedom!
20 | ```
21 |
22 | #### 💡 解释:
23 | - `goto`模块是作为2004年的愚人节(2004.4.1)玩笑[发布](https://mail.python.org/pipermail/python-announce-list/2004-April/002982.html)的,只有Python 2.3版本有`goto`模块。
24 | - 当前版本的Python是没有这个模块的(如果你用的是conda甚至不能下载到2.3版本的Python)。
25 | - 虽然上面版本可以使用`goto`,但是也请不要使用它。[这里](https://docs.python.org/3/faq/design.html#why-is-there-no-goto)是为什么当前版本没有`goto`模块的原因。
26 |
27 | ---
--------------------------------------------------------------------------------
/individual/36.作用域之外的变量.md:
--------------------------------------------------------------------------------
1 | ### ▶ 作用域之外的变量
2 |
3 | ```py
4 | a = 1
5 | def some_func():
6 | return a
7 |
8 | def another_func():
9 | a += 1
10 | return a
11 | ```
12 |
13 | **Output:**
14 | ```py
15 | >>> some_func()
16 | 1
17 | >>> another_func()
18 | UnboundLocalError: local variable 'a' referenced before assignment
19 | ```
20 |
21 | #### 💡 解释:
22 | * 当你在某个作用域给一个变量赋值的时候,这个变量就会自动变成这个作用域的变量。所以`a`会变成`another_func`这个函数的局部变量,但是又没有在这个函数作用域内正确的初始化`a`变量,结果就发生了异常。
23 | * 这篇[文章](http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html)很好的解释了命名空间和作用域是如何在Python中起作用的(是英文版,等有时间我会翻译一下,如果有热心的同学帮忙就更好啦!)。
24 | * 如果想在局部作用域内使用全局作用域的变量`a`,需要使用`global`关键字。
25 | ```py
26 | def another_func()
27 | global a
28 | a += 1
29 | return a
30 | ```
31 |
32 | **Output:**
33 | ```py
34 | >>> another_func()
35 | 2
36 | ```
37 |
38 | ---
--------------------------------------------------------------------------------
/individual/2.不变的哈希值.md:
--------------------------------------------------------------------------------
1 | ### ▶ 不变的哈希值
2 |
3 | 1\.
4 | ```py
5 | some_dict = {}
6 | some_dict[5.5] = "Ruby"
7 | some_dict[5.0] = "JavaScript"
8 | some_dict[5] = "Python"
9 | ```
10 |
11 | **Output:**
12 | ```py
13 | >>> some_dict[5.5]
14 | "Ruby"
15 | >>> some_dict[5.0]
16 | "Python"
17 | >>> some_dict[5]
18 | "Python"
19 | ```
20 | "Python" 把之前的 "JavaScript" 覆盖掉了吗?
21 |
22 | #### :bulb: 解释
23 |
24 | * Python的字典结构是根据key值的哈希值判断两个key值是否相等的
25 | * 在Python中,不变对象(Immutable objects)的值如果一样,那么它们的哈希值肯定也一样
26 | ```py
27 | >>> 5 == 5.0
28 | True
29 | >>> hash(5) == hash(5.0)
30 | True
31 | ```
32 | **注意:** 有些对象有不同的值,但是它们的哈希值也有可能是一样的(所谓的哈希冲突)
33 | * 当`some_dict[5] = "Python"`这句话执行的时候, "Python"这个字符串就会覆盖掉"JavaScript"这个值,因为在Python看来,`5`和`5.0`的哈希值是一样的,也就是说对于字典结构他们对应的是一个key值。
34 | * 在 StackOverflow 上面有一个[回答](https://stackoverflow.com/a/32211042/4354153)对Python的这个特性解释的很棒。
35 |
36 | ---
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | 欢迎所有的PR(Pull request)。 如果有任何有意思或者更合适的标题可以替换当前例子的现有标题,欢迎随时联系我或者提[issues](https://github.com/true1023/Crazy-Python/issues)告诉我。
2 |
3 | 我的目标是让这个仓库里的例子尽可能的更加易读并且更加有趣。
4 |
5 | 如果你有一个跟重要的更新建议,希望你可以先提交一个[issues](https://github.com/true1023/Crazy-Python/issues), 讨论后再提交一个PR。
6 |
7 | 如果你准备为这个仓库增加一个新的例子,请先把例子提交到[issues](https://github.com/true1023/Crazy-Python/issues),等大家讨论后在提交PR。
8 |
9 | 你可以使用下面的模板来增加新的例子:
10 |
11 |
12 | ### ▶ 这是一个标题 *
13 | 首先是例子的标题,如果某个标题后面带有星号,说明这一段是最新的一个版本加上的。
14 | ```py
15 | # 介绍会在这里写一些初始化代码
16 | # 为下面的神奇时刻做准备...
17 | ```
18 |
19 | **输出 (Python version):**
20 | ```py
21 | >>> python语句,执行某个命令
22 | 一些神奇的输出
23 | ```
24 | (可选): 有可能会介绍一下输出的内容
25 |
26 |
27 | #### 💡 解释:
28 |
29 | * 简短的介绍发生了什么和为什么会产生这些输出。
30 | ```py
31 | 写一些初始化代码
32 | ```
33 | **Output:**
34 | ```py
35 | >>> 触发相应代码 # 这些代码会揭示为何会有上方那些神奇的输出内容
36 | ```
37 |
38 |
--------------------------------------------------------------------------------
/individual/19.不可修改的元组.md:
--------------------------------------------------------------------------------
1 | ### ▶ 不可修改的元组
2 |
3 | ```py
4 | some_tuple = ("A", "tuple", "with", "values")
5 | another_tuple = ([1, 2], [3, 4], [5, 6])
6 | ```
7 |
8 | **Output:**
9 | ```py
10 | >>> some_tuple[2] = "change this"
11 | TypeError: 'tuple' object does not support item assignment
12 | >>> another_tuple[2].append(1000) #这里并没有报错
13 | >>> another_tuple
14 | ([1, 2], [3, 4], [5, 6, 1000])
15 | >>> another_tuple[2] += [99, 999]
16 | TypeError: 'tuple' object does not support item assignment
17 | >>> another_tuple
18 | ([1, 2], [3, 4], [5, 6, 1000, 99, 999])
19 | ```
20 |
21 | 我还以为元组是不可以修改的呢......
22 |
23 | #### 💡 解释:
24 |
25 | * 引用自 https://docs.python.org/2/reference/datamodel.html
26 | > 不可变序列
27 | 一个不可变序列对象在第一次定义后就不再可以修改。(如果这个对象中包含有对其他对象的引用,这些其他对象有可能是可变的或者可修改的;总之,只有直接引用的对象是不可以修改的。)
28 | * `+=`操作符会对左侧操作对象本身进行修改。但是元组是不允许修改的,所以会报错,但是实际上修改的是一个可变的列表(list),元组里保存的只是这个列表的地址,所以最终还是会修改成功。
29 |
30 | ---
--------------------------------------------------------------------------------
/individual/8.三子棋之一步取胜法.md:
--------------------------------------------------------------------------------
1 | ### ▶ 三子棋之一步取胜法
2 |
3 | ```py
4 | # 首先先来初始化一个1*3的一维数组
5 | row = [""]*3 #row i['', '', '']
6 | # 然后再用二维数组模拟一个3*3的棋盘
7 | board = [row]*3
8 | ```
9 |
10 | **Output:**
11 | ```py
12 | >>> board
13 | [['', '', ''], ['', '', ''], ['', '', '']]
14 | >>> board[0]
15 | ['', '', '']
16 | >>> board[0][0]
17 | ''
18 | >>> board[0][0] = "X"
19 | >>> board
20 | [['X', '', ''], ['X', '', ''], ['X', '', '']]
21 | ```
22 |
23 | 我们只赋值了一个“X”为什么会出来三个呢?
24 |
25 | #### 💡 解释:
26 |
27 | 当我们初始化`row`变量的时候,下图显示的是内存中的变化
28 |
29 | 
30 |
31 | 接着当变量`board`通过`[row]*3`初始化后,下图显示了内存的变化(其实最终每一个变量`board[0]`,`board[1]`,`board[2]`都引用了同一个`row`对象的内存地址)
32 |
33 | 
34 |
35 | 我们可以通过不使用`row`变量来阻止这种情况的发生
36 |
37 | ```py
38 | >>> board = [['']*3 for _ in range(3)]
39 | >>> board[0][0] = "X"
40 | >>> board
41 | [['X', '', ''], ['', '', ''], ['', '', '']]
42 | ```
43 |
44 | ---
--------------------------------------------------------------------------------
/individual/28.到底哪里出错了呢?.md:
--------------------------------------------------------------------------------
1 | ### ▶ 到底哪里出错了呢?
2 |
3 | ```py
4 | def square(x):
5 | """
6 | 通过加法运算计算平方的函数
7 | """
8 | sum_so_far = 0
9 | for counter in range(x):
10 | sum_so_far = sum_so_far + x
11 | return sum_so_far
12 | ```
13 |
14 | **Output (Python 2.x):**
15 |
16 | ```py
17 | >>> square(10)
18 | 10
19 | ```
20 |
21 | 结果不应该是100吗?
22 |
23 | **注意:** 如果你不能复现上面例子的结果, 可以试试直接运行[mixed_tabs_and_spaces.py](/mixed_tabs_and_spaces.py) 这个文件。
24 |
25 | #### 💡 解释
26 |
27 | * **不要把空格和制表符混在一起使用!** 例子中`return`语句前面的是一个用制表符"tab"来缩进的,代码的其他地方都是使用“四个空格”作为缩进的。
28 | * Python是这样处理制表符的:
29 | > 首先,制表符(tabs)会被依次替换成(从左至右)1到8个不等的空格直到替换后的空格数满足8的倍数......[出自Python文档](https://docs.python.org/2.0/ref/indentation.html)
30 | * 所以在`square`这个函数最后这个制表符"tab"就被替换成了8个空格,八个空格就是两个缩进,所以就等于把`return`语句缩进到了`for`循环内。
31 | * 这种情况在Python 3中就会直接报错。
32 |
33 | **Output (Python 3.x):**
34 | ```py
35 | TabError: inconsistent use of tabs and spaces in indentation
36 | ```
37 |
38 | ---
--------------------------------------------------------------------------------
/individual/45.这些语句是存在的!.md:
--------------------------------------------------------------------------------
1 | ### ▶ 这些语句是存在的!
2 |
3 | **循环语句对应的`else`语句.** 下面是一个标准的例子:
4 |
5 | ```py
6 | def does_exists_num(l, to_find):
7 | for num in l:
8 | if num == to_find:
9 | print("Exists!")
10 | break
11 | else:
12 | print("Does not exist")
13 | ```
14 |
15 | **Output:**
16 | ```py
17 | >>> some_list = [1, 2, 3, 4, 5]
18 | >>> does_exists_num(some_list, 4)
19 | Exists!
20 | >>> does_exists_num(some_list, -1)
21 | Does not exist
22 | ```
23 |
24 | **异常处理对应的`else`语句** 看例子,
25 |
26 | ```py
27 | try:
28 | pass
29 | except:
30 | print("Exception occurred!!!")
31 | else:
32 | print("Try block executed successfully...")
33 | ```
34 |
35 | **Output:**
36 | ```py
37 | Try block executed successfully...
38 | ```
39 |
40 | #### 💡 解释:
41 | - 当一个`else`语句紧跟在一个循环语句后面的时候,只有当循环语句块内有明确使用`break`退出,否则所有循环结束就会执行`else`语句块。
42 | - 跟在`try`语句块后面的`else`语句,又叫做“完成语句(completion clause)”。意味着如果`try`语句块的内容顺利运行完毕,那么就会进入到`else`语句块。
43 |
44 | ---
--------------------------------------------------------------------------------
/individual/4.鸠占鹊巢.md:
--------------------------------------------------------------------------------
1 | ### ▶ 鸠占鹊巢 *
2 |
3 | ```py
4 | class Crazy:
5 | pass
6 | ```
7 |
8 | **Output:**
9 | ```py
10 | >>> Crazy() == Crazy() # 两个类实例是不同的
11 | False
12 | >>> Crazy() is Crazy() # 它们的id号也是不一样的
13 | False
14 | >>> hash(Crazy()) == hash(Crazy()) # 它们的哈希值按说也应该不一样
15 | True
16 | >>> id(Crazy()) == id(Crazy())
17 | True
18 | ```
19 |
20 | #### 💡 解释:
21 |
22 | * 当`id`函数被调用的时候,Python创建了一个`Crazy`类实例,然后把这个实例传给了`id`函数。然后`id`函数返回这个实例的"id"号(实际上就是这个实例在内存中的地址),接着这个实例就被丢弃并且销毁了。
23 | * 当我们紧接着再做一遍上面的步骤的时候,Python会把同一块内存空间分配给第二次创建的`Crazy`实例。又因为在CPython中`id`函数使用的是内存地址作为返回值,所以就会出现两个对象实例的id号相同的情况了。
24 | * 所以,"对象的id是唯一的"这句话有一个前提条件是"在这个对象的生命周期内"。当这个对象在内存被销毁以后,其他的对象就可以占用它之前所用的内存空间产生一样的id号。
25 | * 但是为什么上面的例子里`is`操作符却产生了`False`? 我们再看一个例子。
26 | ```py
27 | class Crazy(object):
28 | def __init__(self): print("I")
29 | def __del__(self): print("D")
30 | ```
31 |
32 | **Output:**
33 | ```py
34 | >>> Crazy() is Crazy()
35 | I
36 | I
37 | D
38 | D
39 | False
40 | >>> id(Crazy()) == id(Crazy())
41 | I
42 | D
43 | I
44 | D
45 | True
46 | ```
47 | 现在你可以发现, 不同的调用方法会对实例销毁的时间产生不同的影响。
48 |
49 | ---
--------------------------------------------------------------------------------
/individual/37.小心那些链式操作符.md:
--------------------------------------------------------------------------------
1 | ### ▶ 小心那些链式操作符
2 |
3 | ```py
4 | >>> (False == False) in [False] # 没问题,OK?
5 | False
6 | >>> False == (False in [False]) # 这个也OK对吧?
7 | False
8 | >>> False == False in [False] # 这个呢?是不是开始不对了?
9 | True
10 |
11 | >>> True is False == False
12 | False
13 | >>> False is False is False
14 | True
15 |
16 | >>> 1 > 0 < 1
17 | True
18 | >>> (1 > 0) < 1
19 | False
20 | >>> 1 > (0 < 1)
21 | False
22 | ```
23 |
24 | #### 💡 解释:
25 |
26 | 按照 https://docs.python.org/2/reference/expressions.html#not-in 里面说的:
27 |
28 | > 如果 a, b, c, ..., y, z 这些是表达式,op1, op2, ..., opN 这些是比较符,那么 a op1 b op2 c ... y opN z 就等于 a op1 b and b op2 c and ... y opN z, 除非其中有些表达式最多只能计算一次。
29 |
30 | 这种模式运行在像 `a == b == c` 和 `0 <=x <= 100` 这种语句里就会产生很有意思的结果了,就像上边那些例子一样。
31 |
32 | * `False is False is False` 等于 `(False is False) and (False is False)`。
33 | * `True is False == False` 等于 `True is False and False == False` 而且第一部分 (`True is False`) 等于 `False`, 所以整个表达式结果就为 `False`。
34 | * `1 > 0 < 1` 等于 `1 > 0 and 0 < 1` 最后结果等于 `True`。
35 | * 表达式 `(1 > 0) < 1` 等于 `True < 1` 而且
36 | ```py
37 | >>> int(True)
38 | 1
39 | >>> True + 1 #这个跟例子没啥关系,就是想告诉你们布尔值也可以这样玩^_^
40 | 2
41 | ```
42 | 所以, `1 < 1`也等于`False`
43 |
44 | ---
--------------------------------------------------------------------------------
/individual/30.删不掉的对象.md:
--------------------------------------------------------------------------------
1 | ### ▶ 删不掉的对象 *
2 |
3 | ```py
4 | class SomeClass:
5 | def __del__(self):
6 | print("Deleted!")
7 | ```
8 |
9 | **Output:**
10 | 1\.
11 | ```py
12 | >>> x = SomeClass()
13 | >>> y = x
14 | >>> del x # 这里应该输出 "Deleted!"
15 | >>> del y
16 | Deleted!
17 | ```
18 |
19 | 啧.....最后还是输出了的。你可能已经猜到了,我们第一次尝试删除`x`的时候,`__del__`方法并没有被调用。现在我们在上面例子的基础上再加几句代码。
20 |
21 | 2\.
22 | ```py
23 | >>> x = SomeClass()
24 | >>> y = x
25 | >>> del x
26 | >>> y # 检查y是否存在
27 | <__main__.SomeClass instance at 0x7f98a1a67fc8>
28 | >>> del y # 就像上一个例子一样,这里应该输出"Deleted!"
29 | >>> globals() # 嗯......好像并没有输出。让我们检查一下全局变量看看。
30 | Deleted!
31 | {'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None}
32 | ```
33 |
34 | 好吧,最后还是输出了(好奇怪的说):confused:
35 |
36 | #### 💡 解释:
37 | + `del x`并不会直接调用`x.__del__()`。
38 | + 当调用`del x`的时候,Python会把所有x指向的对象的引用数减一(在这里就是前面实例化的对象`SomeClass()`),只有当这个对象的引用数为零的时候,才会调用`__del__()`。
39 | + 在第二个例子中,`y.__del__()`没有被调用时因为前面的语句(`>>> y`)引用了同一个对象(SomeClass()),增加了这个对象的引用数,所以在`del y`以后这个对象的引用数并没有变成零。
40 | + 调用`globals`会检查并销毁没用的引用,这时候针对上面对象(`SomeClass()`)的引用数就变成零了,"Deleted!"就被打印出来了。
41 |
42 | ---
--------------------------------------------------------------------------------
/individual/6.时间的误会.md:
--------------------------------------------------------------------------------
1 | ### ▶ 时间的误会
2 |
3 | 1\.
4 | ```py
5 | array = [1, 8, 15]
6 | g = (x for x in array if array.count(x) > 0)
7 | array = [2, 8, 22]
8 | ```
9 |
10 | **Output:**
11 | ```py
12 | >>> print(list(g))
13 | [8]
14 | ```
15 |
16 | 2\.
17 |
18 | ```py
19 | array_1 = [1,2,3,4]
20 | g1 = (x for x in array_1)
21 | array_1 = [1,2,3,4,5]
22 |
23 | array_2 = [1,2,3,4]
24 | g2 = (x for x in array_2)
25 | array_2[:] = [1,2,3,4,5]
26 | ```
27 |
28 | **Output:**
29 | ```py
30 | >>> print(list(g1))
31 | [1,2,3,4]
32 |
33 | >>> print(list(g2))
34 | [1,2,3,4,5]
35 | ```
36 |
37 | #### 💡 解释
38 |
39 | - 在[生成器](https://wiki.python.org/moin/Generators)表达式中,`in`语句会在声明阶段求值,但是条件判断语句(在这里是`array.count(x) > 0`)会在真正的运行阶段(runtime)求值。
40 | - 在生成器运行之前,`array`已经被重新赋值为`[2, 8, 22]`了,所以这个时候再用`count`函数判断`2`,`8`,`22`在原列表中的数量,只有`8`是数量大于`0`的,所以最后这个生成器只返回了一个`8`是符合条件的。
41 | - 在第二部分中,`g1`,`g2`输出结果不同,是因为对`array_1`和`array_2`的赋值方法不同导致的。
42 | - 在第一个例子中, `array_1`绑定了一个新的列表对象`[1,2,3,4,5]`(可以理解成`array_1`的指针指向了一个新的内存地址),而且在这之前,`in`语句已经在声明时就为`g1`绑定好了旧的列表对象`[1,2,3,4]`(这个就对象也没有随着新对象的赋值而销毁)。所以这时候`g1`和`array_1`是指向不同的对象地址的。
43 | - 在第二个例子中,由于切片化的赋值,`array_2`并没有绑定(指向)新对象,而是将旧的对象`[1,2,3,4]`更新(也就是说旧对象的内存地址被新对象占用了)成了新的对象`[1,2,3,4,5]`。所以,`g2`和`array_2`依旧同时指向一个地址,所以都更新成了新对象`[1,2,3,4,5]`。
44 |
45 | ---
--------------------------------------------------------------------------------
/individual/9.没脑子的函数.md:
--------------------------------------------------------------------------------
1 | ### ▶ 没脑子的函数
2 |
3 | ```py
4 | funcs = []
5 | results = []
6 | for x in range(7):
7 | def some_func():
8 | return x
9 | funcs.append(some_func)
10 | results.append(some_func())
11 |
12 | funcs_results = [func() for func in funcs]
13 | ```
14 |
15 | **Output:**
16 | ```py
17 | >>> results
18 | [0, 1, 2, 3, 4, 5, 6]
19 | >>> funcs_results
20 | [6, 6, 6, 6, 6, 6, 6]
21 | ```
22 | 虽然我们每次把`some_func`函数加入到`funcs`列表里的时候`x`都不一样,但是`funcs`列表里的所有函数都返回了6.
23 |
24 | //下面这段代码也是这样
25 |
26 | ```py
27 | >>> powers_of_x = [lambda x: x**i for i in range(10)]
28 | >>> [f(2) for f in powers_of_x]
29 | [512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
30 | ```
31 |
32 | #### 💡 解释
33 |
34 | - 当我们在一个循环中定义一个函数,并且在函数体中用了循环中的变量时,这个函数只会绑定这个变量本身,并不会绑定当前变量循环到的值。所以最终所有在循环中定义的函数都会使用循环变量最后的值做计算。
35 |
36 | - 如果你想实现心中想的那种效果,可以把循环变量当做一个参数传递进函数体。**为什么这样可以呢?**因为这样在函数作用域内会重新定义一个变量,不是循环里面的那个变量了。
37 |
38 | ```py
39 | funcs = []
40 | for x in range(7):
41 | def some_func(x=x):
42 | return x
43 | funcs.append(some_func)
44 | ```
45 |
46 | **Output:**
47 | ```py
48 | >>> funcs_results = [func() for func in funcs]
49 | >>> funcs_results
50 | [0, 1, 2, 3, 4, 5, 6]
51 | ```
52 |
53 | ---
--------------------------------------------------------------------------------
/individual/5.神奇赋值法.md:
--------------------------------------------------------------------------------
1 | ### ▶ 神奇赋值法
2 |
3 | ```py
4 | some_string = "crazy"
5 | some_dict = {}
6 | for i, some_dict[i] in enumerate(some_string):
7 | pass
8 | ```
9 |
10 | **Output:**
11 | ```py
12 | >>> some_dict # 一个带引索的字典被创建.
13 | {0: 'c', 1: 'r', 2: 'a', 3: 'z', 4: 'y'}
14 | ```
15 |
16 | #### 💡 解释:
17 |
18 | * 一个 `for` 语句在[Python语法](https://docs.python.org/3/reference/grammar.html)中是这么定义的:
19 | ```
20 | for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
21 | ```
22 | `exprlist` 是一组被赋值的变量. 这就等于说这组变量在**每次迭代**开始的时候都会执行一次 `{exprlist} = {next_value}` 。
23 | 下面这个例子很好的解释了上面想要表达的意思:
24 | ```py
25 | for i in range(4):
26 | print(i)
27 | i = 10
28 | ```
29 |
30 | **Output:**
31 | ```
32 | 0
33 | 1
34 | 2
35 | 3
36 | ```
37 |
38 | 是不是以为上面的循环就会执行一次?
39 |
40 | **💡 解释:**
41 | - 在上面这个循环中,`i=10`这个赋值语句不会整个迭代过程产生任何影响。因为在每次迭代开始前,迭代函数(在这里是`range(4)`)都会把下一次的值赋值给目标变量(在这里是`i`)。
42 |
43 | * 再来看上面的例子,`enumerate(some_string)`这个函数会在每次迭代的时候产生两个值,分别是`i`(一个从0开始的索引值)和一个字符(来自`some_string`的值)。然后这两个值会分别赋值给`i`和`some_dict[i]`。把刚才的循环展开来看就像是下面这样:
44 | ```py
45 | >>> i, some_dict[i] = (0, 'c')
46 | >>> i, some_dict[i] = (1, 'r')
47 | >>> i, some_dict[i] = (2, 'a')
48 | >>> i, some_dict[i] = (3, 'z')
49 | >>> i, some_dict[i] = (4, 'y')
50 | >>> some_dict
51 | ```
52 |
53 | ---
--------------------------------------------------------------------------------
/individual/24.神秘的键值转换.md:
--------------------------------------------------------------------------------
1 | ### ▶ 神秘的键值转换 *
2 |
3 | ```py
4 | class SomeClass(str):
5 | pass
6 |
7 | some_dict = {'s':42}
8 | ```
9 |
10 | **Output:**
11 | ```py
12 | >>> type(list(some_dict.keys())[0])
13 | str
14 | >>> s = SomeClass('s')
15 | >>> some_dict[s] = 40
16 | >>> some_dict # expected: Two different keys-value pairs
17 | {'s': 40}
18 | >>> type(list(some_dict.keys())[0])
19 | str
20 | ```
21 |
22 | #### 💡 解释:
23 |
24 | * 类`s`和字符串`s`的哈希值是一样的,因为`SomeClass`继承了`str`类的`__hash__`方法。
25 | * `SomeClass("s") == "s"` 等于`True`,因为`SomeClass`同样继承了`str`类的`__eq__`方法。
26 | * 因为两个对象的哈希值(hash)和值(value)全都相等,所以他们在字典的关键字(key)里是被认为相同的。
27 | * 如果想要打到预期的效果,我们可以重新定义`SomeClass`的`__eq__`方法
28 | ```py
29 | class SomeClass(str):
30 | def __eq__(self, other):
31 | return (
32 | type(self) is SomeClass
33 | and type(other) is SomeClass
34 | and super().__eq__(other)
35 | )
36 |
37 | # 当我们自定义 __eq__ 方法后, Python会停止
38 | # 自动继承 __hash__ 方法, 所以我们同样也需要定义它
39 | __hash__ = str.__hash__
40 |
41 | some_dict = {'s':42}
42 | ```
43 |
44 | **Output:**
45 | ```py
46 | >>> s = SomeClass('s')
47 | >>> some_dict[s] = 40
48 | >>> some_dict
49 | {'s': 40, 's': 42}
50 | >>> keys = list(some_dict.keys())
51 | >>> type(keys[0]), type(keys[1])
52 | (__main__.SomeClass, str)
53 | ```
54 |
55 | ---
--------------------------------------------------------------------------------
/individual/16.站错队的布尔型.md:
--------------------------------------------------------------------------------
1 | ### ▶ 站错队的布尔型
2 |
3 | 1\.
4 | ```py
5 | # 一个计算列表里布尔型和Int型数量的例子
6 | mixed_list = [False, 1.0, "some_string", 3, True, [], False]
7 | integers_found_so_far = 0
8 | booleans_found_so_far = 0
9 |
10 | for item in mixed_list:
11 | if isinstance(item, int):
12 | integers_found_so_far += 1
13 | elif isinstance(item, bool):
14 | booleans_found_so_far += 1
15 | ```
16 |
17 | **Output:**
18 | ```py
19 | >>> booleans_found_so_far
20 | 0
21 | >>> integers_found_so_far
22 | 4
23 | ```
24 |
25 | 2\.
26 | ```py
27 | another_dict = {}
28 | another_dict[True] = "JavaScript"
29 | another_dict[1] = "Ruby"
30 | another_dict[1.0] = "Python"
31 | ```
32 |
33 | **Output:**
34 | ```py
35 | >>> another_dict[True]
36 | "Python"
37 | ```
38 |
39 | 3\.
40 | ```py
41 | >>> some_bool = True
42 | >>> "crazy"*some_bool
43 | 'crazy'
44 | >>> some_bool = False
45 | >>> "crazy"*some_bool
46 | ''
47 | ```
48 |
49 | #### 💡 解释:
50 |
51 | * 布尔型(Booleans)是 `int`类型的一个子类型(bool is instance of int in Python)
52 | ```py
53 | >>> isinstance(True, int)
54 | True
55 | >>> isinstance(False, int)
56 | True
57 | ```
58 |
59 | * `True`的整形值是`1`,`False`的整形值是`0`
60 | ```py
61 | >>> True == 1 == 1.0 and False == 0 == 0.0
62 | True
63 | ```
64 |
65 | * StackOverFlow有针对这个问题背后原理的[解答](https://stackoverflow.com/a/8169049/4354153)。
66 |
67 | ---
--------------------------------------------------------------------------------
/individual/17.类属性与类实例属性.md:
--------------------------------------------------------------------------------
1 | ### ▶ 类属性与类实例属性
2 |
3 | 1\.
4 | ```py
5 | class A:
6 | x = 1
7 |
8 | class B(A):
9 | pass
10 |
11 | class C(A):
12 | pass
13 | ```
14 |
15 | **Ouptut:**
16 | ```py
17 | >>> A.x, B.x, C.x
18 | (1, 1, 1)
19 | >>> B.x = 2
20 | >>> A.x, B.x, C.x
21 | (1, 2, 1)
22 | >>> A.x = 3
23 | >>> A.x, B.x, C.x
24 | (3, 2, 3)
25 | >>> a = A()
26 | >>> a.x, A.x
27 | (3, 3)
28 | >>> a.x += 1
29 | >>> a.x, A.x
30 | (4, 3)
31 | ```
32 |
33 | 2\.
34 | ```py
35 | class SomeClass:
36 | some_var = 15
37 | some_list = [5]
38 | another_list = [5]
39 | def __init__(self, x):
40 | self.some_var = x + 1
41 | self.some_list = self.some_list + [x]
42 | self.another_list += [x]
43 | ```
44 |
45 | **Output:**
46 |
47 | ```py
48 | >>> some_obj = SomeClass(420)
49 | >>> some_obj.some_list
50 | [5, 420]
51 | >>> some_obj.another_list
52 | [5, 420]
53 | >>> another_obj = SomeClass(111)
54 | >>> another_obj.some_list
55 | [5, 111]
56 | >>> another_obj.another_list
57 | [5, 420, 111]
58 | >>> another_obj.another_list is SomeClass.another_list
59 | True
60 | >>> another_obj.another_list is some_obj.another_list
61 | True
62 | ```
63 |
64 | #### 💡 解释:
65 |
66 | * 类里的属性和类实例中的属性是作为一个字典列表对象进行处理的。如果在当前类中没有找到需要调用的属性名,那么就会递归去父类中寻找。
67 | * `+=`操作符会在原有易变类型对象的基础上做改变而不会重新创建一个新的对象。所以在一个类实例中修改属性值,会影响到所有调用这个属性的相关类实例和类。
68 |
69 | ---
--------------------------------------------------------------------------------
/individual/33.小心那些有默认值的可变参数!.md:
--------------------------------------------------------------------------------
1 | ### ▶ 小心那些有默认值的可变参数!
2 |
3 | ```py
4 | def some_func(default_arg=[]):
5 | default_arg.append("some_string")
6 | return default_arg
7 | ```
8 |
9 | **Output:**
10 | ```py
11 | >>> some_func()
12 | ['some_string']
13 | >>> some_func()
14 | ['some_string', 'some_string']
15 | >>> some_func([])
16 | ['some_string']
17 | >>> some_func()
18 | ['some_string', 'some_string', 'some_string']
19 | ```
20 |
21 | #### 💡 解释:
22 |
23 | - 在Python中,如果参数有默认值并且是一个可变对象,那么这个默认的对象会一直存在,并不会随着每次创建实例而重新生成一个新的对象。当我们显式的把`[]`对象传递给`some_func`函数的时候,`default_arg`这个参数的默认值就不会被使用而被新对象替代了,所以这个函数会按照我们预期的那样返回值。
24 |
25 | ```py
26 | def some_func(default_arg=[]):
27 | default_arg.append("some_string")
28 | return default_arg
29 | ```
30 |
31 | **Output:**
32 | ```py
33 | >>> some_func.__defaults__ #__defaults__会把这个函数的默认参数都打印出来
34 | ([],)
35 | >>> some_func()
36 | >>> some_func.__defaults__
37 | (['some_string'],)
38 | >>> some_func()
39 | >>> some_func.__defaults__
40 | (['some_string', 'some_string'],)
41 | >>> some_func([])
42 | >>> some_func.__defaults__
43 | (['some_string', 'some_string'],)
44 | ```
45 |
46 | - 一个常用的用来避免上述bug的做法是这样的,把默认可变参数赋值成`None`,然后在函数体中检查这个参数是否被赋值,如果没有就赋一个默认值:
47 |
48 | ```py
49 | def some_func(default_arg=None):
50 | if not default_arg:
51 | default_arg = []
52 | default_arg.append("some_string")
53 | return default_arg
54 | ```
55 |
56 | ---
--------------------------------------------------------------------------------
/individual/31.删除正在迭代中的列表项.md:
--------------------------------------------------------------------------------
1 | ### ▶ 删除正在迭代中的列表项
2 |
3 | ```py
4 | list_1 = [1, 2, 3, 4]
5 | list_2 = [1, 2, 3, 4]
6 | list_3 = [1, 2, 3, 4]
7 | list_4 = [1, 2, 3, 4]
8 |
9 | for idx, item in enumerate(list_1):
10 | del item
11 |
12 | for idx, item in enumerate(list_2):
13 | list_2.remove(item)
14 |
15 | for idx, item in enumerate(list_3[:]):
16 | list_3.remove(item)
17 |
18 | for idx, item in enumerate(list_4):
19 | list_4.pop(idx)
20 | ```
21 |
22 | **Output:**
23 | ```py
24 | >>> list_1
25 | [1, 2, 3, 4]
26 | >>> list_2
27 | [2, 4]
28 | >>> list_3
29 | []
30 | >>> list_4
31 | [2, 4]
32 | ```
33 |
34 | 知道为什么输出是`[2, 4]`吗?
35 |
36 | #### 💡 解释:
37 |
38 | * 首先,我是不赞成在迭代的过程中修改迭代参数的(就像上面例子里那样)。正确的方法应该是先把迭代的列表拷贝一份,拿拷贝后的列表做迭代,就像上面第三个迭代做的那样(`list_3[:]`)。
39 |
40 | ```py
41 | >>> some_list = [1, 2, 3, 4]
42 | >>> id(some_list)
43 | 139798789457608
44 | >>> id(some_list[:]) # 注意如果用切片法的话,Python会新建一个list对象。
45 | 139798779601192
46 | ```
47 |
48 |
49 | **`del`,`remove`和`pop`的区别:**
50 | * `del var_name` 只会移除本地或者全局命名空间中对`var_name`这个变量的绑定(这就是为什么`list_1`迭代后没有任何改变,因为`list_1`这个变量并没有收到影响)。
51 | * `remove` 会根据值(value)进行匹配并移除第一个匹配上的元素,若没有匹配的元素则抛出`ValueError`异常。
52 | * `pop` 会移除指定索引(index)的元素并且返回这个元素,如果指定的元素不存在则抛出`IndexError`异常。
53 |
54 | **所以为什么会输出`[2, 4]`呢?**
55 | - 首先列表迭代是根据索引值一个一个迭代的,所以当我们把`1`这个元素从`list_2`或者`list_4`中移除了以后,这两个列表就变成了`[2, 3, 4]`。剩下的这几个元素会依次向左移填补刚刚删除了的元素的位置,也就是说,`2`这个元素现在的索引值(index)变成了0,`3`的索引值变成了1。接着在下次迭代的时候,`for`循环访问的索引值已经变成了1(也就是列表里`3`这个元素),所以`2`这个元素就被跳过去(没有删除)。同样的情况每迭代一次都会发生一次。
56 |
57 | ---
--------------------------------------------------------------------------------
/individual/25.看你能不能猜到这个的结果?.md:
--------------------------------------------------------------------------------
1 | ### ▶ 看你能不能猜到这个的结果?
2 |
3 | ```py
4 | a, b = a[b] = {}, 5
5 | ```
6 |
7 | **Output:**
8 | ```py
9 | >>> a
10 | {5: ({...}, 5)}
11 | ```
12 |
13 | #### 💡 解释:
14 |
15 | * 根据Python的赋值语句的[文档](https://docs.python.org/3.7/reference/simple_stmts.html#assignment-statements),赋值语句有如下的格式
16 | ```
17 | (target_list "=")+ (expression_list | yield_expression)
18 | ```
19 | 并且
20 | > 一个赋值语句会对表达式列表(expression_list)进行求值(这个可以只有一个表达式,也可以多个表达式用逗号分开组成表达式列表,后者最终会表现为元组的形式)并且会将单次的求值结果对象依次从左至右赋值给目标列表(target_list)。
21 |
22 | * 在`(target_list "=")+`这个表达式中`+`意味着 **一个或者多个** 目标列表。这上面这个例子中,目标列表是`a,b`和`a[b]`(注意表达式列表永远只有一个,在我们这个例子里是`{}, 5`)。
23 |
24 | * 当表达式计算出来后,它的值会被 **从左至右** 依次赋值给目标列表。所以,在上面的例子里,第一次赋值会把`{}, 5`这个元组赋值给`a, b`这个目标列表,这个时候`a = {}` 并且 `b = 5`。
25 |
26 | * 现在`a`被赋值成了`{}`,一个可变对象(mutable object)。
27 |
28 | * 第二个目标列表是`a[b]`(你可能会认为这种写法会报错,因为我们没有之前并没有定义`a`和`b`,但是没关系,我们刚刚已经给`a`和`b`分别赋值了`{}`和`5`)。
29 |
30 | * 现在我们会把元组`({}, 5)`赋值给字典关键为`5`的字典对象(也就是`a[5]`),同时也因此创建了一个循环引用(circular reference)(`{...}`在输出中代表同一个对象`a`已经被引用了)。下面是一个关于引用循环简单点的例子
31 |
32 | ```py
33 | >>> some_list = some_list[0] = [0]
34 | >>> some_list
35 | [[...]]
36 | >>> some_list[0]
37 | [[...]]
38 | >>> some_list is some_list[0]
39 | True
40 | >>> some_list[0][0][0][0][0][0] == some_list
41 | True
42 | ```
43 | 在我们的例子用也是同样的情况(`a[b][0]`和`a`是指向同一个对象)
44 |
45 | * 所以总结一下,我们可以把上面的列子像如下这样拆分
46 | ```py
47 | a, b = {}, 5
48 | a[b] = a, b
49 | ```
50 | 并且通过`a[b][0]`和`a`是同一个对象来证明确实存在引用循环
51 | ```py
52 | >>> a[b][0] is a
53 | True
54 | ```
55 |
56 | ---
--------------------------------------------------------------------------------
/individual/20.消失的变量e.md:
--------------------------------------------------------------------------------
1 | ### ▶ 消失的变量e
2 |
3 | ```py
4 | e = 7
5 | try:
6 | raise Exception()
7 | except Exception as e:
8 | pass
9 | ```
10 |
11 | **Output (Python 2.x):**
12 | ```py
13 | >>> print(e)
14 | # 什么都没有打印
15 | ```
16 |
17 | **Output (Python 3.x):**
18 | ```py
19 | >>> print(e)
20 | NameError: name 'e' is not defined
21 | ```
22 |
23 | #### 💡 解释:
24 |
25 | * 参考自: https://docs.python.org/3/reference/compound_stmts.html#except
26 |
27 | 当一个异常(exception)被用`as`赋值给一个变量后,这个变量将会在`except`语句块结束后清除。看下面这段
28 |
29 | ```py
30 | except E as N:
31 | foo
32 | ```
33 |
34 | 相当于这样
35 |
36 | ```py
37 | except E as N:
38 | try:
39 | foo
40 | finally:
41 | del N
42 | ```
43 |
44 | 这意味着你最好不要在外面定义一个和"N"重名的变量。这些被赋值的异常变量被清除是因为,Python会将这些变量加入一个异常回溯栈中(traceback,仔细观察每次程序出错控制台输出的错误信息就是从tranceback这个堆栈里面以此取出来的),记录这些异常的具体位置信息并且一直保持这个堆栈的信息直到下一次垃圾回收开始。
45 |
46 | * 这些Python的子句并没有独立的作用域,上面所有的例子都在一个作用域里,所以当变量`e`在`except`子句里面被移除后,就相当于在整个作用域都消失了。但是在函数中情况就和上面不一样了,因为Python函数有自己的“内部”作用域。下面这个例子将说明这种情况:
47 |
48 | ```py
49 | def f(x):
50 | del(x)
51 | print(x)
52 |
53 | x = 5
54 | y = [5, 4, 3]
55 | ```
56 |
57 | **Output:**
58 | ```py
59 | >>>f(x)
60 | UnboundLocalError: local variable 'x' referenced before assignment
61 | >>>f(y)
62 | UnboundLocalError: local variable 'x' referenced before assignment
63 | >>> x
64 | 5
65 | >>> y
66 | [5, 4, 3]
67 | ```
68 |
69 | * 在 Python 2.x 版本里面, 变量`e`会被赋值`Exception()`实例,所以当打印的时候什么都不会打印出来。
70 |
71 | **Output (Python 2.x):**
72 | ```py
73 | >>> e
74 | Exception()
75 | >>> print e
76 | # 什么都没有打印出来
77 | ```
78 |
79 | ---
--------------------------------------------------------------------------------
/individual/34.抓住这些异常!.md:
--------------------------------------------------------------------------------
1 | ### ▶ 抓住这些异常!
2 |
3 | ```py
4 | some_list = [1, 2, 3]
5 | try:
6 | # 这里应该会抛出一个 ``IndexError`` 异常
7 | print(some_list[4])
8 | except IndexError, ValueError:
9 | print("Caught!")
10 |
11 | try:
12 | # 这里会抛出一个 ``ValueError`` 异常
13 | some_list.remove(4)
14 | except IndexError, ValueError:
15 | print("Caught again!")
16 | ```
17 |
18 | **Output (Python 2.x):**
19 | ```py
20 | Caught!
21 |
22 | ValueError: list.remove(x): x not in list
23 | ```
24 |
25 | **Output (Python 3.x):**
26 | ```py
27 | File "", line 3
28 | except IndexError, ValueError:
29 | ^
30 | SyntaxError: invalid syntax
31 | ```
32 |
33 | #### 💡 解释
34 |
35 | * 如果想在`except`语句中同时捕获多个异常类型,需要把这些类型写成元组(tuple)的形式放在第一个参数的位置。第二个参数是一个可选参数,用来绑定具体捕获异常后的对象。比如,
36 | ```py
37 | some_list = [1, 2, 3]
38 | try:
39 | # 这里会产生一个 ``ValueError`` 异常
40 | some_list.remove(4)
41 | except (IndexError, ValueError), e:
42 | print("Caught again!")
43 | print(e)
44 | ```
45 | **Output (Python 2.x):**
46 | ```
47 | Caught again!
48 | list.remove(x): x not in list
49 | ```
50 | **Output (Python 3.x):**
51 | ```py
52 | File "", line 4
53 | except (IndexError, ValueError), e:
54 | ^
55 | IndentationError: unindent does not match any outer indentation level
56 | ```
57 |
58 | * 在Python 3中已经弃用了使用逗号分隔异常类型和异常变量了;而是要使用`as`语法了。比如,
59 | ```py
60 | some_list = [1, 2, 3]
61 | try:
62 | some_list.remove(4)
63 |
64 | except (IndexError, ValueError) as e:
65 | print("Caught again!")
66 | print(e)
67 | ```
68 | **Output:**
69 | ```
70 | Caught again!
71 | list.remove(x): x not in list
72 | ```
73 |
74 | ---
--------------------------------------------------------------------------------
/individual/32.被泄露出去的循环变量!.md:
--------------------------------------------------------------------------------
1 | ### ▶ 被泄露出去的循环变量!
2 |
3 | 1\.
4 | ```py
5 | for x in range(7):
6 | if x == 6:
7 | print(x, ': for x inside loop')
8 | print(x, ': x in global')
9 | ```
10 |
11 | **Output:**
12 | ```py
13 | 6 : for x inside loop
14 | 6 : x in global
15 | ```
16 |
17 | 但是我从来没有在循环外定义一个变量`x`呀!
18 |
19 | 2\.
20 | ```py
21 | # 这回我先初始化一个x变量
22 | x = -1
23 | for x in range(7):
24 | if x == 6:
25 | print(x, ': for x inside loop')
26 | print(x, ': x in global')
27 | ```
28 |
29 | **Output:**
30 | ```py
31 | 6 : for x inside loop
32 | 6 : x in global
33 | ```
34 |
35 | 3\.
36 | ```
37 | x = 1
38 | print([x for x in range(5)])
39 | print(x, ': x in global')
40 | ```
41 |
42 | **Output (Python 2.x):**
43 | ```
44 | [0, 1, 2, 3, 4]
45 | (4, ': x in global')
46 | ```
47 |
48 | **Output (Python 3.x):**
49 | ```
50 | [0, 1, 2, 3, 4]
51 | 1 : x in global
52 | ```
53 |
54 | #### 💡 解释:
55 |
56 | - 在Python里, for循环有它自己的作用域,但是在循环结束后,不会销毁那些循环变量。就算我们提前在全局作用域里定义了变量(就像上面那种情况),也会被循环变量覆盖。
57 |
58 | - 关于为什么在Python2和Python3中有不同的输出,可以在Python3的文档([What's New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html))中找到答案,下面会贴出原文:
59 |
60 | >"List comprehensions no longer support the syntactic form `[... for var in item1, item2, ...]`. Use `[... for var in (item1, item2, ...)]` instead. Also, note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a `list()` constructor, and in particular the loop control variables are no longer leaked into the surrounding scope."
61 |
62 | - 只用看最后一句话就够了**in particular the loop control variables are no longer leaked into the surrounding scope**,翻译过来是**特别是循环控制变量不会再泄露到包围着它的作用域以外了**,还有这个特性是针对构造列表(List comprehensions)说的,不包括正常的for循环。
63 |
64 | ---
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/individual/44.就算Python也知道爱是个复杂的东西.md:
--------------------------------------------------------------------------------
1 | ### ▶ 就算Python也知道爱是个复杂的东西 *
2 |
3 | ```py
4 | import this
5 | ```
6 |
7 | Wait, what's **this**? `this` is love :heart:
8 | (译注:我认为这句话原汁原味的看才能体会其中的乐趣,就不翻译了😊)
9 |
10 | **Output:**
11 | ```
12 | The Zen of Python, by Tim Peters
13 |
14 | Beautiful is better than ugly.
15 | Explicit is better than implicit.
16 | Simple is better than complex.
17 | Complex is better than complicated.
18 | Flat is better than nested.
19 | Sparse is better than dense.
20 | Readability counts.
21 | Special cases aren't special enough to break the rules.
22 | Although practicality beats purity.
23 | Errors should never pass silently.
24 | Unless explicitly silenced.
25 | In the face of ambiguity, refuse the temptation to guess.
26 | There should be one-- and preferably only one --obvious way to do it.
27 | Although that way may not be obvious at first unless you're Dutch.
28 | Now is better than never.
29 | Although never is often better than *right* now.
30 | If the implementation is hard to explain, it's a bad idea.
31 | If the implementation is easy to explain, it may be a good idea.
32 | Namespaces are one honking great idea -- let's do more of those!
33 | ```
34 |
35 | 这些(指上面输出的内容)就是Python之道!
36 |
37 | ```py
38 | >>> love = this
39 | >>> this is love
40 | True
41 | >>> love is True
42 | False
43 | >>> love is False
44 | False
45 | >>> love is not True or False
46 | True
47 | >>> love is not True or False; love is love # 爱(love)是个复杂的东西,不是吗?
48 | True
49 | ```
50 |
51 | #### 💡 解释:
52 |
53 | * `this`模块是一个针对Python之道做阐述的彩蛋([PEP 20](https://www.python.org/dev/peps/pep-0020))。
54 | * 但是这并没有完,如果你看看这个模块的实现代码[this.py](https://hg.python.org/cpython/file/c3896275c0f6/Lib/this.py)。你会发现一个更加有趣的事情,那就是这个代码本身就违反了上面输出的Python之道(当然,我觉得这也是唯一一处违反的地方)。
55 | * 关于`love is not True or False; love is love`这句话,它解释的很对,不是吗?
56 |
57 | ---
--------------------------------------------------------------------------------
/individual/1.善变的字符串.md:
--------------------------------------------------------------------------------
1 | ### ▶ 善变的字符串 *
2 |
3 | 1\.
4 | ```py
5 | >>> a = "crazy_python"
6 | >>> id(a)
7 | 2387669241224
8 | >>> id("crazy" + "_" + "python") # 注意这两个字符串的id号是一样的
9 | 2387669241224
10 | ```
11 |
12 | 2\.
13 | ```py
14 | >>> a = "crazy"
15 | >>> b = "crazy"
16 | >>> a is b
17 | True
18 |
19 | >>> a = "crazy!"
20 | >>> b = "crazy!"
21 | >>> a is b
22 | False
23 |
24 | >>> a, b = "crazy!", "crazy!"
25 | >>> a is b
26 | True
27 | ```
28 |
29 | 3\.
30 | ```py
31 | >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
32 | True
33 | >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
34 | False
35 | ```
36 |
37 | 很不可思议,对吧?
38 |
39 | #### :bulb: 解释:
40 | + 上面这种特性是CPython的一种编译器优化技术(叫做字符串驻留技术)。就是如果将要创建的字符串在之前已经创建过并且驻留在了内存没有释放,那么CPython不会创建一个新的实例,而是会把指针指向已经存在于内存的老的字符串实例。
41 | + 如果一个字符串实例已经驻留在了内存中,那么后续所有跟它值一样的变量都可以将指针指向这个内存中的字符串实例(这样就会节省内存空间)
42 | + 在上面这几段程序代码中,有几段的字符串明显驻留在了内存中供多个变量引用。 决定一个字符串是否会驻留在内存中是由这个字符串的实现方法决定的。下面是一些判断字符串变量是否会驻留内存的方法:
43 | * 所有长度为1或者0的字符串,全部会驻留
44 | * 编译阶段(也就是把源代码编译成.pyc文件的阶段)的字符串被驻留在内存,运行阶段就不会(`'crazy'`会驻留在内存,但是`''.join(['c','r','a','z','y'])`就不会)
45 | * 如果字符串包含除了ASCII字符,数字和下划线以外的字符,那么这个字符串就不会驻留内存。这就是为什么`'crazy!'`赋值给`a`,`b`的时候得到的结果是False,因为`!`感叹号。CPython中对这个特点的具体实现请参考[这里](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)
46 |
47 | + 当变量`a`和`b`在同一行赋值`"crazy!"`的时候,Python的解释器会创建一个新的字符串实例,然后把这两个变量同时指向这一个实例。但是如果你用两行实现赋值的话,Python解释器就不会知道已经有一个`'crazy!'`的字符串实例存在了(因为根据上面的规则,`'crazy!'`实例不会进行内存驻留供后面的语句引用)。这是一种专门在交互环境下编译器的一种优化方法。
48 | + 常量折叠是Python实现的一种[窥孔优化(Peephole optimization)](https://baike.baidu.com/item/%E7%AA%A5%E5%AD%94%E4%BC%98%E5%8C%96)技术。意思就是`'a'*20`这个语句在编译的时候会自动替换成`'aaaaaaaaaaaaaaaaaaaa'`这个变量,用来减少运行时的运算时钟周期(`'a'*20`需要多执行20次乘法运算)。常量折叠只在字符串长度小于等于20的时候发生(至于为什么?想想如果有一个`'a*10**10'`这样的语句,折叠后需要多大一个`.pyc`才能存下折叠后的字符串啊)。[这里](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288)是实现这种技术的实现代码。
49 |
50 | ---
--------------------------------------------------------------------------------
/individual/51.最后一些小特点集合.md:
--------------------------------------------------------------------------------
1 | ### ▶ 最后一些小特点集合
2 |
3 | * `join()`是一个字符串的函数而不是一个列表附属函数(第一次用的人可能会觉得有点别扭)
4 |
5 | **💡 解释:**
6 | 如果`join()`作为一个字符串的方法,那么它就可以操作所有的可迭代类型(list, tuple, iterators)。但是如果它作为了一个列表(list)的方法,那它同样的也需要为另外那些可迭代类型构造专属的函数内容。
7 |
8 | * 下面这些句子看着奇怪,但是它们确实都是符合语法规则的
9 | + `[] = ()` 在语法上是正确的 (把一个元组赋值给一个列表)
10 | + `'a'[0][0][0][0][0]` 同样也是语法正确的,因为在Python中字符串是一个序列(可迭代的对象就可以使用索引值进行访问里面的元素)。
11 | + `3 --0-- 5 == 8`和`--5 == 5`也都是语法正确的,并且最后结果都为`True`。
12 |
13 | * 给定一个数字类型变量`a`,`++a`和`--a`在Python中都是合法的,但是它们表达的意思和在C语言(或者C++,Java)中是完全不一样的。
14 | ```py
15 | >>> a = 5
16 | >>> a
17 | 5
18 | >>> ++a
19 | 5
20 | >>> --a
21 | 5
22 | ```
23 |
24 | **💡 解释:**
25 | + 在Python语法中,是没有`++`这个操作符的。这个仅仅代表两个单独的`+`操作符而已。
26 | + `++a`在Python中会被解析成`+(+a)`,也就等于`a`。同样的,`--a`也是这种情况。
27 | + 这篇StackOverflow的[文章](https://stackoverflow.com/questions/3654830/why-are-there-no-and-operators-in-python)讨论了Python中没有自增自减符号背后的逻辑关系。
28 |
29 | * 多个Python线程并不会并发执行你的*Python代码*(你没看错,不会!)。也许你想同时开很多个线程然后并发执行你写的Python代码,但是,由于Python中存在一个叫[GIL](https://wiki.python.org/moin/GlobalInterpreterLock)(Global Interpreter Lock)的东西,所以最终你的线程还是会一个接着一个执行。Python的线程执行IO阻塞的任务比较拿手,但是如果想在CPU阻塞的任务中获得真正的并发能力,你需要了解一下Python的[multiprocessing](https://docs.python.org/2/library/multiprocessing.html)模块。
30 |
31 | * 列表切片的时候如果索引值超出界限并不会报错
32 | ```py
33 | >>> some_list = [1, 2, 3, 4, 5]
34 | >>> some_list[111:]
35 | []
36 | ```
37 |
38 | * 在Python3中,`int('١٢٣٤٥٦٧٨٩')`会返回`123456789`。在Python中,十进制字符不仅包含十进制的数字,还包含了大部分可以表示为十进制数字形式的字符,比如,U+0660, ARABIC_INDIC DIGIT ZERO,一种印度阿拉伯语里代表数字0的字符。不过还没有中文的"一,二,三..."或者"壹,贰,叁..."这种,有人说是因为中文在unicode中的编码是不连续的,不过无论如何,现在确实还没有。这里有一篇[小故事](http://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/)是关于这个特性的,英语版,有兴趣的同学可以看看。
39 |
40 | * `'abc'.count('') == 4`。 下面模拟实现了一个相似的`count()`方法,
41 | ```py
42 | def count(s, sub):
43 | result = 0
44 | for i in range(len(s) + 1 - len(sub)):
45 | result += (s[i:i + len(sub)] == sub)
46 | return result
47 | ```
48 | 产生这个结果的原因是子串为空时会匹配上原始字符串长度为0时的字符(总之,看代码你会懂的)。
49 |
50 | ---
--------------------------------------------------------------------------------
/individual/7.特殊的数字们.md:
--------------------------------------------------------------------------------
1 | ### ▶ 特殊的数字们
2 |
3 | 下面这个例子在网上非常的流行。
4 |
5 | ```py
6 | >>> a = 256
7 | >>> b = 256
8 | >>> a is b
9 | True
10 |
11 | >>> a = 257
12 | >>> b = 257
13 | >>> a is b
14 | False
15 |
16 | >>> a = 257; b = 257
17 | >>> a is b
18 | True
19 | ```
20 |
21 | #### 💡 解释:
22 |
23 | **`is`和`==`的区别**
24 |
25 | * `is` 操作符会检查两边的操作数是否引用的是同一个对象(也就是说,会检查两个操作数的id号是否匹配)。
26 | * `==` 操作符会比较两个操作数的值是否一样。
27 | * 所以说`is`是比较引用地址是否相同,`==`是比较值是否相同。下面的例子解释的比较清楚,
28 | ```py
29 | >>> [] == []
30 | True
31 | >>> [] is [] # 这里的两个空list分配了不同的内存地址
32 | False
33 | ```
34 |
35 | **`256` 是一个已经存在于内存的对象 但是 `257` 不是**
36 |
37 | 当你启动一个Python解释器的时候,数字`-5`到`256`就会自动加载进内存。 这些数字都是一些比较常用的数字,所以Python解释器会把他们提前准备好以备以后使用。
38 |
39 | 下面这段话摘抄自 https://docs.python.org/3/c-api/long.html (已经翻译为中文)
40 | > The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behavior of Python, in this case, is undefined.:-)
41 | >
42 | > 当前的实现方法是,维护一个从-5到256的整数数组,当你使用其中某一个数字的时候,系统会自动为你引用到已经存在的对象上去。我认为应该让它可以改变数字1的值。不过就现在来说,Python还没有这个功能。:-)
43 |
44 | ```py
45 | >>> id(256)
46 | 10922528
47 | >>> a = 256
48 | >>> b = 256
49 | >>> id(a)
50 | 10922528
51 | >>> id(b)
52 | 10922528
53 | >>> id(257)
54 | 140084850247312
55 | >>> x = 257
56 | >>> y = 257
57 | >>> id(x)
58 | 140084850247440
59 | >>> id(y)
60 | 140084850247344
61 | ```
62 |
63 | Python解释器在执行`y = 257`的时候还不能意识到我们之前已经创建过了一个值为`257`的对象,所以它又在内存创建了一个新的对象。
64 |
65 | **当`a`和`b`变量在同一行赋值并且所赋值相等时,它们会引用到同一个对象**
66 |
67 | ```py
68 | >>> a, b = 257, 257
69 | >>> id(a)
70 | 140640774013296
71 | >>> id(b)
72 | 140640774013296
73 | >>> a = 257
74 | >>> b = 257
75 | >>> id(a)
76 | 140640774013392
77 | >>> id(b)
78 | 140640774013488
79 | ```
80 |
81 | * 当a和b在同一行被赋值为`257`的时候, Python解释器会创建一个新的对象,然后把这两个变量同时引用到这个新的对象。如果你分开两行赋值两个变量,那么解释器不会“知道”之前自己已经创建过一个同样的`257`对象了。
82 | * 这是一种专门针对交互式解释器环境的优化机制。 当你在控制面板中敲入两行命令的时候,这两行命令是分开编译的,所以他们也会单独进行优化。如果你准备把这个例子(两行分别赋值的例子)写进`.py`文件然后进行测试,那么你会发现结果跟写在一行是一样的,因为文件里的代码是一次性编译的。
83 |
84 | ---
--------------------------------------------------------------------------------
/individual/49.超长字符串!.md:
--------------------------------------------------------------------------------
1 | ### ▶ 超长字符串!
2 |
3 | ```py
4 | #使用加号连接字符串
5 | def add_string_with_plus(iters):
6 | s = ""
7 | for i in range(iters):
8 | s += "xyz"
9 | assert len(s) == 3*iters
10 |
11 | #使用比特流连接字符串
12 | def add_bytes_with_plus(iters):
13 | s = b""
14 | for i in range(iters):
15 | s += b"xyz"
16 | assert len(s) == 3*iters
17 |
18 | #使用format函数连接字符串
19 | def add_string_with_format(iters):
20 | fs = "{}"*iters
21 | s = fs.format(*(["xyz"]*iters))
22 | assert len(s) == 3*iters
23 |
24 | #使用数组连接字符串
25 | def add_string_with_join(iters):
26 | l = []
27 | for i in range(iters):
28 | l.append("xyz")
29 | s = "".join(l)
30 | assert len(s) == 3*iters
31 |
32 | #将列表转换成字符串
33 | def convert_list_to_string(l, iters):
34 | s = "".join(l)
35 | assert len(s) == 3*iters
36 | ```
37 |
38 | **Output:**
39 | ```py
40 | >>> timeit(add_string_with_plus(10000))
41 | 1000 loops, best of 3: 972 µs per loop
42 | >>> timeit(add_bytes_with_plus(10000))
43 | 1000 loops, best of 3: 815 µs per loop
44 | >>> timeit(add_string_with_format(10000))
45 | 1000 loops, best of 3: 508 µs per loop
46 | >>> timeit(add_string_with_join(10000))
47 | 1000 loops, best of 3: 878 µs per loop
48 | >>> l = ["xyz"]*10000
49 | >>> timeit(convert_list_to_string(l, 10000))
50 | 10000 loops, best of 3: 80 µs per loop
51 | ```
52 |
53 | 让我们把循环次数提升十倍。
54 |
55 | ```py
56 | >>> timeit(add_string_with_plus(100000)) # 执行时间线性增加
57 | 100 loops, best of 3: 9.75 ms per loop
58 | >>> timeit(add_bytes_with_plus(100000)) # 平方式增加
59 | 1000 loops, best of 3: 974 ms per loop
60 | >>> timeit(add_string_with_format(100000)) # 线性增加
61 | 100 loops, best of 3: 5.25 ms per loop
62 | >>> timeit(add_string_with_join(100000)) # 线性增加
63 | 100 loops, best of 3: 9.85 ms per loop
64 | >>> l = ["xyz"]*100000
65 | >>> timeit(convert_list_to_string(l, 100000)) # 线性增加
66 | 1000 loops, best of 3: 723 µs per loop
67 | ```
68 |
69 | #### 💡 解释
70 | - 关于`timeit`模块的用法可以看[这里](https://docs.python.org/3/library/timeit.html)。这是一个用来测量程序块执行时间的模块。
71 | - 在Python中,不要试图用`+`来连接一个长字符串,因为`str`这个类型在Python中是一个不可变类型,这就意味着每次两个字符串对象连接程序都需要从左到右把两个字符串的每一个字符都复制一份。比如你现在要连接四个长度为10的字符串,那么程序需要拷贝 (10+10) + ((10+10)+10) + (((10+10)+10)+10) = 90 个字符,而不是40个。而且程序运行时间会随着你需要连接的字符串数量和字符数量呈平方形式的增长。(请参考`add_bytes_with_plus`函数的执行时间对比)。
72 | - 所以,建议大家使用`.format`或者`%`这种语法(但是,它们在连接短字符串的时候会比`+`号慢一点)。
73 | - 或者,如果已经提前获得了一个可以迭代的对象,则可以使用`''.join(iterable_object)`这种语法把它连接成字符串,这样的速度会比前几个都快。
74 | - 至于为什么`add_string_with_plus`不会像`add_bytes_with_plus`着这样成平方形式的提升执行时间,是因为前面例子里已经解释过的`+=`操作符的优化效果。如果把`s += "xyz"`这句话替换成了`s = s + "x" + "y" + "z"`,那么执行时间还是会呈平方形式的增加。
75 | ```py
76 | def add_string_with_plus(iters):
77 | s = ""
78 | for i in range(iters):
79 | s = s + "x" + "y" + "z"
80 | assert len(s) == 3*iters
81 |
82 | >>> timeit(add_string_with_plus(10000))
83 | 100 loops, best of 3: 9.87 ms per loop
84 | >>> timeit(add_string_with_plus(100000)) # 平方式增加
85 | 1 loops, best of 3: 1.09 s per loop
86 | ```
87 |
88 | ---
--------------------------------------------------------------------------------
/individual/toc.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | *一些有趣的鲜为人知的Python特性集合*
4 |
5 | >无论你是Python新手还是Python老手,我相信,这个系列的文章都会让你获益良多!
6 | >
7 | > 本系列为我在github上的一个项目,由于markdown语法不同所以在简书上做了一些改动并且按章节来发。如果中间有什么错误,请务必留言我会第一时间改正。
8 |
9 | ---
10 |
11 | Python作为一个设计优美的交互式脚本语言,提供了许多人性化的语法。但是也因为这个原因,有些Python的代码片段并不会按照用户想象的那样运行。
12 |
13 | 这一系列文章就让我们总结一下那些Python里反直觉的代码片段,并且深入研究一下其中的运行原理。
14 |
15 | 以后的某些例子可能并不像是标题说的那样....嗯....反直觉,但是它们依旧会带你揭示一些你从来没有意识到的Python语言特性。
16 |
17 | 而且,我发现这也是一种很好的学习编程语言的方法,不过前提是你不要认为这篇文章会告诉你一切,抛砖引玉而已。相信我,你需要的知识都隐藏在互联网的某个角落里。
18 |
19 | 如果你已经写了很久的Python代码,你可以把下面的这些例子当做一个挑战,试一试自己能不能在第一次就猜对结果。也许某些例子里的坑你已经遇到过并且解决了,那么再次看见这个坑的时候我想你会为当时自己的努力而自豪的。😄
20 |
21 | 好了,下面是整个系列的目录,每发布一篇那一篇的超链接就会变得可以点击。
22 |
23 | - [▶ 善变的字符串]
24 | - [▶ 不变的哈希值]
25 | - [▶ 说了要执行就一定会执行!]
26 | - [▶ 鸠占鹊巢]
27 | - [▶ 神奇赋值法]
28 | - [▶ 时间的误会]
29 | - [▶ 特殊的数字们]
30 | - [▶ 三子棋之一步取胜法]
31 | - [▶ 没脑子的函数]
32 | - [▶ `is not ...` 并不是 `is`]
33 | - [▶ 尾部的逗号]
34 | - [▶ 最后一个反斜杠]
35 | - [▶ 纠结的not]
36 | - [▶ 只剩一半的三引号]
37 | - [▶ 消失的午夜零点]
38 | - [▶ 站错队的布尔型]
39 | - [▶ 类属性与类实例属性]
40 | - [▶ None的产生]
41 | - [▶ 不可修改的元组]
42 | - [▶ 消失的变量e]
43 | - [▶ 亦真还假]
44 | - [▶ 转瞬即空]
45 | - [▶ 子类的关系]
46 | - [▶ 神秘的键值转换 *]
47 | - [▶ 看你能不能猜到这个的结果?]
48 | - [▶ 无效的一行?]
49 | - [▶ 移形换位]
50 | - [▶ 到底哪里出错了呢?]
51 | - [▶ 不要在迭代一个字典的时候修改这个字典!]
52 | - [▶ 删不掉的对象]
53 | - [▶ 删除正在迭代中的列表项]
54 | - [▶ 被泄露出去的循环变量!]
55 | - [▶ 小心那些有默认值的可变参数!]
56 | - [▶ 抓住这些异常!]
57 | - [▶ 相同的操作,不同的结果]
58 | - [▶ 作用域之外的变量]
59 | - [▶ 小心那些链式操作符]
60 | - [▶ 被忽略的类变量]
61 | - [▶ 小心处理元组]
62 | - [▶ 人生苦短,我用Python ]
63 | - [▶ 为什么要用`goto`?]
64 | - [▶ 试试用大括号?]
65 | - [▶ 不等号的争议 ]
66 | - [▶ 就算Python也知道爱是个复杂的东西 ]
67 | - [▶ 这些语句是存在的!]
68 | - [▶ 无限 ]
69 | - [▶ 被修改的类成员 ]
70 | - [▶ `+=`更快]
71 | - [▶ 超长字符串!]
72 | - [▶ 字符串到浮点数的转换]
73 | - [▶ 最后一些小特点集合]
74 |
75 |
76 |
77 | # 示例结构说明
78 |
79 | 下面是每个例子里通用的结构说明:
80 |
81 | > ### ▶ 这里是例子的标题 *
82 | > 首先是例子的标题。
83 | > ```py
84 | > # 第一个代码段里面会有一些初始化代码
85 | > # 为后续的输出代码段做准备...
86 | > ```
87 | >
88 | > **Output (Python version):**
89 | > ```py
90 | > >>> python语句,执行某个命令
91 | > 一些输出(可能你想得到,也可能想不到)
92 | > ```
93 | > (可选): 有可能会说明一下上面输出的内容
94 | >
95 | >
96 | > #### 💡 解释:
97 | >
98 | > * 简短的介绍发生了什么和为什么会产生这些输出。
99 | > ```py
100 | > 一些初始化代码
101 | > ```
102 | > **Output:**
103 | > ```py
104 | > >>> 执行代码 # 这些代码会展示为何会有上方那些输出内容
105 | > ```
106 |
107 | **注意:** 所有的代码都是在 Python 3.5.2 环境下测试通过,理论上如果没有特殊声明,可以在所有的Python版本下运行。
108 |
109 | # 使用方法
110 |
111 | 在我看来,为了充分的利用这个仓库里的所有例子,最好的办法就是按照顺序把每个例子挨个看一遍:
112 | - 仔细阅读每个例子的初始化代码。如果你是一个经验丰富的Python程序员,那么大部分时候你都可以知道初始化代码执行后具体会发生什么。
113 | - 阅读输出结果并且,
114 | + 检查输出结果是否和你想的一样
115 | + 确认你是否知道产生这种结果背后的原理,
116 | - 如果不知道,那么请仔细阅读解释章节(如果看完解释还是不懂的话,那就在下面评论吧)
117 | - 如果知道,那么给就自己点个赞,继续看下一个例子
118 |
119 |
120 | # 一些说明
121 |
122 | - 这个系列翻译自 [What the fu*k Python](https://github.com/satwikkansal/wtfpython)
123 | - 如果你有任何好的想法和意见,请务必要分享给我
124 | - 我会在收集并修正一些bug后生成pdf版本的Crazy Python,想要的小伙伴可以[发信](mailto:shi12li12@gmail.com)给我,或者关注公众号**编程行动派**,我也会第一时间在公众号里通知大家
125 | - 转载注明出处
126 |
127 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | 疯狂的Python! 🐍
4 | 一些有趣的鲜为人知的Python特性集合.
5 |
6 | [](http://www.wtfpl.net/)
7 | [](https://gitter.im/Crazy-Python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
8 |
9 |
10 | *本文翻译自[What the f*ck Python!](https://github.com/satwikkansal/wtfpython)*
11 |
12 | ---
13 |
14 | Python作为一个设计优美的交互式脚本语言,提供了许多人性化的语法。但是也因为这个原因,有些Python的代码片段并不会按照用户想象的那样运行。
15 |
16 | 这篇文章就让我们总结一下那些Python里反直觉的代码片段,并且深入研究一下其中的运行原理。
17 |
18 | 下面的某些例子可能并不像是标题说的那样....嗯....反直觉,但是它们依旧会带你揭示一些你从来没有意识到的Python语言特性。
19 |
20 | 而且,我发现这也是一种很好的学习编程语言的方法,不过前提是你不要认为这篇文章会告诉你一切,抛砖引玉而已。相信我,你需要的知识都隐藏在互联网的某个角落里。
21 |
22 | 如果你已经写了很久的Python代码,你可以把下面的这些例子当做一个挑战,试一试自己能不能在第一次就猜对结果。也许某些例子里的坑你已经遇到过并且解决了,那么再次看见这个坑的时候我想你会为当时自己的努力而自豪的。:sweat_smile:
23 |
24 | 好了,下面是正文!
25 |
26 | 目录
27 |
28 |
29 | - [示例结构说明](#示例结构说明)
30 | - [使用方法](#使用方法)
31 | - [:eyes: 例子](#eyes-例子)
32 | - [第一章: 撕裂大脑](#第一章-撕裂大脑)
33 | - [▶ 善变的字符串 *](#-善变的字符串-)
34 | - [▶ 不变的哈希值](#-不变的哈希值)
35 | - [▶ 说了要执行就一定会执行!](#-说了要执行就一定会执行)
36 | - [▶ 鸠占鹊巢 *](#-鸠占鹊巢-)
37 | - [▶ 神奇赋值法](#-神奇赋值法)
38 | - [▶ 时间的误会](#-时间的误会)
39 | - [▶ 特殊的数字们](#-特殊的数字们)
40 | - [▶ 三子棋之一步取胜法](#-三子棋之一步取胜法)
41 | - [▶ 没脑子的函数](#-没脑子的函数)
42 | - [▶ `is not ...` 并不是 `is (not ...)`](#-is-not--并不是--is-not-)
43 | - [▶ 尾部的逗号](#-尾部的逗号)
44 | - [▶ 最后一个反斜杠](#-最后一个反斜杠)
45 | - [▶ 纠结的not](#-纠结的not)
46 | - [▶ 只剩一半的三引号](#-只剩一半的三引号)
47 | - [▶ 消失的午夜零点](#-消失的午夜零点)
48 | - [▶ 站错队的布尔型](#-站错队的布尔型)
49 | - [▶ 类属性与类实例属性](#-类属性与类实例属性)
50 | - [▶ None的产生](#-none的产生)
51 | - [▶ 不可修改的元组](#-不可修改的元组)
52 | - [▶ 消失的变量e](#-消失的变量e)
53 | - [▶ 亦真还假](#-亦真还假)
54 | - [▶ 转瞬即空](#-转瞬即空)
55 | - [▶ 子类的关系 *](#-子类的关系-)
56 | - [▶ 神秘的键值转换 *](#-神秘的键值转换-)
57 | - [▶ 看你能不能猜到这个的结果?](#-看你能不能猜到这个的结果)
58 | - [第二章: 瞒天过海](#第二章-瞒天过海)
59 | - [▶ 无效的一行?](#-无效的一行)
60 | - [▶ 移形换位 *](#-移形换位-)
61 | - [▶ 到底哪里出错了呢?](#-到底哪里出错了呢)
62 | - [第三章: 隐藏的陷阱](#第三章-隐藏的陷阱)
63 | - [▶ 不要在迭代一个字典的时候修改这个字典!](#-不要在迭代一个字典的时候修改这个字典)
64 | - [▶ 删不掉的对象 *](#-删不掉的对象-)
65 | - [▶ 删除正在迭代中的列表项](#-删除正在迭代中的列表项)
66 | - [▶ 被泄露出去的循环变量!](#-被泄露出去的循环变量)
67 | - [▶ 小心那些有默认值的可变参数!](#-小心那些有默认值的可变参数)
68 | - [▶ 抓住这些异常!](#-抓住这些异常)
69 | - [▶ 相同的操作,不同的结果](#-相同的操作不同的结果)
70 | - [▶ 作用域之外的变量](#-作用域之外的变量)
71 | - [▶ 小心那些链式操作符](#-小心那些链式操作符)
72 | - [▶ 被忽略的类变量](#-被忽略的类变量)
73 | - [▶ 小心处理元组](#-小心处理元组)
74 | - [第四章: 一起来找些有趣的东西!](#第四章-一起来找些有趣的东西)
75 | - [▶ 人生苦短,我用Python *](#-人生苦短我用python-)
76 | - [▶ 为什么要用`goto`? *](#-为什么要用goto-)
77 | - [▶ 试试用大括号? *](#-试试用大括号-)
78 | - [▶ 不等号的争议 *](#-不等号的争议-)
79 | - [▶ 就算Python也知道爱是个复杂的东西 *](#-就算python也知道爱是个复杂的东西-)
80 | - [▶ 这些语句是存在的!](#-这些语句是存在的)
81 | - [▶ 无限(Inpinity) *](#-无限inpinity-)
82 | - [▶ 被修改的类成员 *](#-被修改的类成员-)
83 | - [第五章: 杂项](#第五章-杂项)
84 | - [▶ `+=`更快](#-更快)
85 | - [▶ 超长字符串!](#-超长字符串)
86 | - [▶ 字符串到浮点数的转换](#-字符串到浮点数的转换)
87 | - [▶ 最后一些小特点集合](#-最后一些小特点集合)
88 | - [贡献](#贡献)
89 | - [感谢](#感谢)
90 | - [:mortar_board: 版权声明](#mortar_board-版权声明)
91 | - [帮助](#帮助)
92 | - [关于pdf出版的问题](#关于pdf出版的问题)
93 |
94 |
95 |
96 | # 示例结构说明
97 |
98 |
99 |
100 | 下面是每个例子里通用的结构说明:
101 |
102 | > ### ▶ 这里是例子的标题 *
103 | > 首先是例子的标题,如果某个标题后面带有星号,说明这是一个新加入的例子。
104 | > ```py
105 | > # 第一个代码段里面会有一些初始化代码
106 | > # 为后续的输出代码段做准备...
107 | > ```
108 | >
109 | > **Output (Python version):**
110 | > ```py
111 | > >>> python语句,执行某个命令
112 | > 一些输出(可能你想得到,也可能想不到)
113 | > ```
114 | > (可选): 有可能会说明一下上面输出的内容
115 | >
116 | >
117 | > #### 💡 解释:
118 | >
119 | > * 简短的介绍发生了什么和为什么会产生这些输出。
120 | > ```py
121 | > 一些初始化代码
122 | > ```
123 | > **Output:**
124 | > ```py
125 | > >>> 执行代码 # 这些代码会展示为何会有上方那些输出内容
126 | > ```
127 |
128 | **注意:** 所有的代码都是在 Python 3.5.2 环境下测试通过,理论上如果没有特殊声明,可以在所有的Python版本下运行。
129 |
130 | # 使用方法
131 |
132 | 在我看来,为了充分的利用这个仓库里的所有例子,最好的办法就是按照顺序把每个例子挨个看一遍:
133 | - 仔细阅读每个例子的初始化代码。如果你是一个经验丰富的Python程序员,那么大部分时候你都可以知道初始化代码执行后具体会发生什么。
134 | - 阅读输出结果并且,
135 | + 检查输出结果是否和你想的一样
136 | + 确认你是否知道产生这种结果背后的原理,
137 | - 如果不知道,那么请仔细阅读解释章节(如果看完解释还是不懂的话,那就提交一个 [issue](https://github.com/true1023/Crazy-Python/issues) 吧)
138 | - 如果知道,那么给就自己点个赞,继续看下一个例子
139 |
140 | # :eyes: 例子
141 |
142 | ## 第一章: 撕裂大脑
143 |
144 | ### ▶ 善变的字符串 *
145 |
146 | 1\.
147 | ```py
148 | >>> a = "crazy_python"
149 | >>> id(a)
150 | 2387669241224
151 | >>> id("crazy" + "_" + "python") # 注意这两个字符串的id号是一样的
152 | 2387669241224
153 | ```
154 |
155 | 2\.
156 | ```py
157 | >>> a = "crazy"
158 | >>> b = "crazy"
159 | >>> a is b
160 | True
161 |
162 | >>> a = "crazy!"
163 | >>> b = "crazy!"
164 | >>> a is b
165 | False
166 |
167 | >>> a, b = "crazy!", "crazy!"
168 | >>> a is b
169 | True
170 | ```
171 |
172 | 3\.
173 | ```py
174 | >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
175 | True
176 | >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
177 | False
178 | ```
179 |
180 | 很不可思议,对吧?
181 |
182 | #### 💡 解释:
183 | + 上面这种特性是CPython的一种编译器优化技术(叫做字符串驻留技术)。就是如果将要创建的字符串在之前已经创建过并且驻留在了内存没有释放,那么CPython不会创建一个新的实例,而是会把指针指向已经存在于内存的老的字符串实例。
184 | + 如果一个字符串实例已经驻留在了内存中,那么后续所有跟它值一样的变量都可以将指针指向这个内存中的字符串实例(这样就会节省内存空间)
185 | + 在上面这几段程序代码中,有几段的字符串明显驻留在了内存中供多个变量引用。 决定一个字符串是否会驻留在内存中是由这个字符串的实现方法决定的。下面是一些判断字符串变量是否会驻留内存的方法:
186 | * 所有长度为1或者0的字符串,全部会驻留
187 | * 编译阶段(也就是把源代码编译成.pyc文件的阶段)的字符串被驻留在内存,运行阶段就不会(`'crazy'`会驻留在内存,但是`''.join(['c','r','a','z','y'])`就不会)
188 | * 如果字符串包含除了ASCII字母,数字和下划线以外的字符,那么这个字符串就不会驻留内存。这就是为什么`'crazy!'`赋值给`a`,`b`的时候得到的结果是False,因为`!`感叹号。CPython中对这个特点的具体实现请参考[这里](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)
189 |
190 | + 当变量`a`和`b`在同一行赋值`"crazy!"`的时候,Python的解释器会创建一个新的字符串实例,然后把这两个变量同时指向这一个实例。但是如果你用两行实现赋值的话,Python解释器就不会知道已经有一个`'crazy!'`的字符串实例存在了(因为根据上面的规则,`'crazy!'`实例不会进行内存驻留供后面的语句引用)。这是一种专门在交互环境下编译器的一种优化方法。
191 | + 常量折叠是Python实现的一种[窥孔优化(Peephole optimization)](https://baike.baidu.com/item/%E7%AA%A5%E5%AD%94%E4%BC%98%E5%8C%96)技术。意思就是`'a'*20`这个语句在编译的时候会自动替换成`'aaaaaaaaaaaaaaaaaaaa'`这个变量,用来减少运行时的运算时钟周期(`'a'*20`需要多执行20次乘法运算)。常量折叠只在字符串长度小于等于20的时候发生(至于为什么?想想如果有一个`'a*10**10'`这样的语句,折叠后需要多大一个`.pyc`才能存下折叠后的字符串啊)。[这里](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288)是实现这种技术的实现代码。
192 |
193 | ---
194 |
195 |
196 | ### ▶ 不变的哈希值
197 |
198 | 1\.
199 | ```py
200 | some_dict = {}
201 | some_dict[5.5] = "Ruby"
202 | some_dict[5.0] = "JavaScript"
203 | some_dict[5] = "Python"
204 | ```
205 |
206 | **Output:**
207 | ```py
208 | >>> some_dict[5.5]
209 | "Ruby"
210 | >>> some_dict[5.0]
211 | "Python"
212 | >>> some_dict[5]
213 | "Python"
214 | ```
215 | "Python" 把之前的 "JavaScript" 覆盖掉了吗?
216 |
217 | #### 💡 解释
218 |
219 | * Python的字典结构是根据key值的哈希值判断两个key值是否相等的
220 | * 在Python中,不变对象(Immutable objects)的值如果一样,那么它们的哈希值肯定也一样
221 | ```py
222 | >>> 5 == 5.0
223 | True
224 | >>> hash(5) == hash(5.0)
225 | True
226 | ```
227 | **注意:** 有些对象有不同的值,但是它们的哈希值也有可能是一样的(所谓的哈希冲突)
228 | * 当`some_dict[5] = "Python"`这句话执行的时候, "Python"这个字符串就会覆盖掉"JavaScript"这个值,因为在Python看来,`5`和`5.0`的哈希值是一样的,也就是说对于字典结构他们对应的是一个key值。
229 | * 在 StackOverflow 上面有一个[回答](https://stackoverflow.com/a/32211042/4354153)对Python的这个特性解释的很棒。
230 |
231 | ---
232 |
233 | ### ▶ 说了要执行就一定会执行!
234 |
235 | ```py
236 | def some_func():
237 | try:
238 | return 'from_try'
239 | finally:
240 | return 'from_finally'
241 | ```
242 |
243 | **Output:**
244 | ```py
245 | >>> some_func()
246 | 'from_finally'
247 | ```
248 |
249 | #### 💡 解释:
250 |
251 | - 当在`try`语句块中遇到`return`,`break`或者`continue`的时候,如果是"try...finlly"语句块,那么在执行完`try`语句块里的内容后,依然会执行`finally`语句块的内容。
252 | - 当`return`语句返回一个值的时候,那么因为在`finally`语句块中的`return`语句是最后执行的,那么返回的值就永远都是`finally`语句块中`return`语句返回的值。
253 |
254 | ---
255 |
256 | ### ▶ 鸠占鹊巢 *
257 |
258 | ```py
259 | class Crazy:
260 | pass
261 | ```
262 |
263 | **Output:**
264 | ```py
265 | >>> Crazy() == Crazy() # 两个类实例是不同的
266 | False
267 | >>> Crazy() is Crazy() # 它们的id号也是不一样的
268 | False
269 | >>> hash(Crazy()) == hash(Crazy()) # 它们的哈希值按说也应该不一样
270 | True
271 | >>> id(Crazy()) == id(Crazy())
272 | True
273 | ```
274 |
275 | #### 💡 解释:
276 |
277 | * 当`id`函数被调用的时候,Python创建了一个`Crazy`类实例,然后把这个实例传给了`id`函数。然后`id`函数返回这个实例的"id"号(实际上就是这个实例在内存中的地址),接着这个实例就被丢弃并且销毁了。
278 | * 当我们紧接着再做一遍上面的步骤的时候,Python会把同一块内存空间分配给第二次创建的`Crazy`实例。又因为在CPython中`id`函数使用的是内存地址作为返回值,所以就会出现两个对象实例的id号相同的情况了。
279 | * 所以,"对象的id是唯一的"这句话有一个前提条件是"在这个对象的生命周期内"。当这个对象在内存被销毁以后,其他的对象就可以占用它之前所用的内存空间产生一样的id号。
280 | * 但是为什么上面的例子里`is`操作符却产生了`False`? 我们再看一个例子。
281 | ```py
282 | class Crazy(object):
283 | def __init__(self): print("I")
284 | def __del__(self): print("D")
285 | ```
286 |
287 | **Output:**
288 | ```py
289 | >>> Crazy() is Crazy()
290 | I
291 | I
292 | D
293 | D
294 | False
295 | >>> id(Crazy()) == id(Crazy())
296 | I
297 | D
298 | I
299 | D
300 | True
301 | ```
302 | 现在你可以发现, 不同的调用方法会对实例销毁的时间产生不同的影响。
303 |
304 | ---
305 |
306 | ### ▶ 神奇赋值法
307 |
308 | ```py
309 | some_string = "crazy"
310 | some_dict = {}
311 | for i, some_dict[i] in enumerate(some_string):
312 | pass
313 | ```
314 |
315 | **Output:**
316 | ```py
317 | >>> some_dict # 一个带引索的字典被创建.
318 | {0: 'c', 1: 'r', 2: 'a', 3: 'z', 4: 'y'}
319 | ```
320 |
321 | #### 💡 解释:
322 |
323 | * 一个 `for` 语句在[Python语法](https://docs.python.org/3/reference/grammar.html)中是这么定义的:
324 | ```
325 | for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
326 | ```
327 | `exprlist` 是一组被赋值的变量. 这就等于说这组变量在**每次迭代**开始的时候都会执行一次 `{exprlist} = {next_value}` 。
328 | 下面这个例子很好的解释了上面想要表达的意思:
329 | ```py
330 | for i in range(4):
331 | print(i)
332 | i = 10
333 | ```
334 |
335 | **Output:**
336 | ```
337 | 0
338 | 1
339 | 2
340 | 3
341 | ```
342 |
343 | 是不是以为上面的循环就会执行一次?
344 |
345 | **💡 解释:**
346 | - 在上面这个循环中,`i=10`这个赋值语句不会整个迭代过程产生任何影响。因为在每次迭代开始前,迭代函数(在这里是`range(4)`)都会把下一次的值赋值给目标变量(在这里是`i`)。
347 |
348 | * 再来看上面的例子,`enumerate(some_string)`这个函数会在每次迭代的时候产生两个值,分别是`i`(一个从0开始的索引值)和一个字符(来自`some_string`的值)。然后这两个值会分别赋值给`i`和`some_dict[i]`。把刚才的循环展开来看就像是下面这样:
349 | ```py
350 | >>> i, some_dict[i] = 'c'
351 | >>> i, some_dict[i] = 'r'
352 | >>> i, some_dict[i] = 'a'
353 | >>> i, some_dict[i] = 'z'
354 | >>> i, some_dict[i] = 'y'
355 | >>> some_dict
356 | ```
357 |
358 | ---
359 |
360 | ### ▶ 时间的误会
361 |
362 | 1\.
363 | ```py
364 | array = [1, 8, 15]
365 | g = (x for x in array if array.count(x) > 0)
366 | array = [2, 8, 22]
367 | ```
368 |
369 | **Output:**
370 | ```py
371 | >>> print(list(g))
372 | [8]
373 | ```
374 |
375 | 2\.
376 |
377 | ```py
378 | array_1 = [1,2,3,4]
379 | g1 = (x for x in array_1)
380 | array_1 = [1,2,3,4,5]
381 |
382 | array_2 = [1,2,3,4]
383 | g2 = (x for x in array_2)
384 | array_2[:] = [1,2,3,4,5]
385 | ```
386 |
387 | **Output:**
388 | ```py
389 | >>> print(list(g1))
390 | [1,2,3,4]
391 |
392 | >>> print(list(g2))
393 | [1,2,3,4,5]
394 | ```
395 |
396 | #### 💡 解释
397 |
398 | - 在[生成器](https://wiki.python.org/moin/Generators)表达式中,`in`语句会在声明阶段求值,但是条件判断语句(在这里是`array.count(x) > 0`)会在真正的运行阶段(runtime)求值。
399 | - 在生成器运行之前,`array`已经被重新赋值为`[2, 8, 22]`了,所以这个时候再用`count`函数判断`2`,`8`,`22`在原列表中的数量,只有`8`是数量大于`0`的,所以最后这个生成器只返回了一个`8`是符合条件的。
400 | - 在第二部分中,`g1`,`g2`输出结果不同,是因为对`array_1`和`array_2`的赋值方法不同导致的。
401 | - 在第一个例子中, `array_1`绑定了一个新的列表对象`[1,2,3,4,5]`(可以理解成`array_1`的指针指向了一个新的内存地址),而且在这之前,`in`语句已经在声明时就为`g1`绑定好了旧的列表对象`[1,2,3,4]`(这个就对象也没有随着新对象的赋值而销毁)。所以这时候`g1`和`array_1`是指向不同的对象地址的。
402 | - 在第二个例子中,由于切片化的赋值,`array_2`并没有绑定(指向)新对象,而是将旧的对象`[1,2,3,4]`更新(也就是说旧对象的内存地址被新对象占用了)成了新的对象`[1,2,3,4,5]`。所以,`g2`和`array_2`依旧同时指向一个地址,所以都更新成了新对象`[1,2,3,4,5]`。
403 |
404 | ---
405 |
406 | ### ▶ 特殊的数字们
407 |
408 | 下面这个例子在网上非常的流行。
409 |
410 | ```py
411 | >>> a = 256
412 | >>> b = 256
413 | >>> a is b
414 | True
415 |
416 | >>> a = 257
417 | >>> b = 257
418 | >>> a is b
419 | False
420 |
421 | >>> a = 257; b = 257
422 | >>> a is b
423 | True
424 | ```
425 |
426 | #### 💡 解释:
427 |
428 | **`is`和`==`的区别**
429 |
430 | * `is` 操作符会检查两边的操作数是否引用的是同一个对象(也就是说,会检查两个操作数的id号是否匹配)。
431 | * `==` 操作符会比较两个操作数的值是否一样。
432 | * 所以说`is`是比较引用地址是否相同,`==`是比较值是否相同。下面的例子解释的比较清楚,
433 | ```py
434 | >>> [] == []
435 | True
436 | >>> [] is [] # 这里的两个空list分配了不同的内存地址
437 | False
438 | ```
439 |
440 | **`256` 是一个已经存在于内存的对象 但是 `257` 不是**
441 |
442 | 当你启动一个Python解释器的时候,数字`-5`到`256`就会自动加载进内存。 这些数字都是一些比较常用的数字,所以Python解释器会把他们提前准备好以备以后使用。
443 |
444 | 下面这段话摘抄自 https://docs.python.org/3/c-api/long.html (已经翻译为中文)
445 | > The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behavior of Python, in this case, is undefined.:-)
446 | >
447 | > 当前的实现方法是,维护一个从-5到256的整数数组,当你使用其中某一个数字的时候,系统会自动为你引用到已经存在的对象上去。我认为应该让它可以改变数字1的值。不过就现在来说,Python还没有这个功能。:-)
448 |
449 | ```py
450 | >>> id(256)
451 | 10922528
452 | >>> a = 256
453 | >>> b = 256
454 | >>> id(a)
455 | 10922528
456 | >>> id(b)
457 | 10922528
458 | >>> id(257)
459 | 140084850247312
460 | >>> x = 257
461 | >>> y = 257
462 | >>> id(x)
463 | 140084850247440
464 | >>> id(y)
465 | 140084850247344
466 | ```
467 |
468 | Python解释器在执行`y = 257`的时候还不能意识到我们之前已经创建过了一个值为`257`的对象,所以它又在内存创建了一个新的对象。
469 |
470 | **当`a`和`b`变量在同一行赋值并且所赋值相等时,它们会引用到同一个对象**
471 |
472 | ```py
473 | >>> a, b = 257, 257
474 | >>> id(a)
475 | 140640774013296
476 | >>> id(b)
477 | 140640774013296
478 | >>> a = 257
479 | >>> b = 257
480 | >>> id(a)
481 | 140640774013392
482 | >>> id(b)
483 | 140640774013488
484 | ```
485 |
486 | * 当a和b在同一行被赋值为`257`的时候, Python解释器会创建一个新的对象,然后把这两个变量同时引用到这个新的对象。如果你分开两行赋值两个变量,那么解释器不会“知道”之前自己已经创建过一个同样的`257`对象了。
487 | * 这是一种专门针对交互式解释器环境的优化机制。 当你在控制面板中敲入两行命令的时候,这两行命令是分开编译的,所以他们也会单独进行优化。如果你准备把这个例子(两行分别赋值的例子)写进`.py`文件然后进行测试,那么你会发现结果跟写在一行是一样的,因为文件里的代码是一次性编译的。
488 |
489 | ---
490 |
491 | ### ▶ 三子棋之一步取胜法
492 |
493 | ```py
494 | # 首先先来初始化一个1*3的一维数组
495 | row = [""]*3 #row i['', '', '']
496 | # 然后再用二维数组模拟一个3*3的棋盘
497 | board = [row]*3
498 | ```
499 |
500 | **Output:**
501 | ```py
502 | >>> board
503 | [['', '', ''], ['', '', ''], ['', '', '']]
504 | >>> board[0]
505 | ['', '', '']
506 | >>> board[0][0]
507 | ''
508 | >>> board[0][0] = "X"
509 | >>> board
510 | [['X', '', ''], ['X', '', ''], ['X', '', '']]
511 | ```
512 |
513 | 我们只赋值了一个“X”为什么会出来三个呢?
514 |
515 | #### 💡 解释:
516 |
517 | 当我们初始化`row`变量的时候,下图显示的是内存中的变化
518 |
519 | 
520 |
521 | 接着当变量`board`通过`[row]*3`初始化后,下图显示了内存的变化(其实最终每一个变量`board[0]`,`board[1]`,`board[2]`都引用了同一个`row`对象的内存地址)
522 |
523 | 
524 |
525 | 我们可以通过不使用`row`变量来阻止这种情况的发生
526 |
527 | ```py
528 | >>> board = [['']*3 for _ in range(3)]
529 | >>> board[0][0] = "X"
530 | >>> board
531 | [['X', '', ''], ['', '', ''], ['', '', '']]
532 | ```
533 |
534 | ---
535 |
536 | ### ▶ 没脑子的函数
537 |
538 | ```py
539 | funcs = []
540 | results = []
541 | for x in range(7):
542 | def some_func():
543 | return x
544 | funcs.append(some_func)
545 | results.append(some_func())
546 |
547 | funcs_results = [func() for func in funcs]
548 | ```
549 |
550 | **Output:**
551 | ```py
552 | >>> results
553 | [0, 1, 2, 3, 4, 5, 6]
554 | >>> funcs_results
555 | [6, 6, 6, 6, 6, 6, 6]
556 | ```
557 | 虽然我们每次把`some_func`函数加入到`funcs`列表里的时候`x`都不一样,但是`funcs`列表里的所有函数都返回了6.
558 |
559 | //下面这段代码也是这样
560 |
561 | ```py
562 | >>> powers_of_x = [lambda x: x**i for i in range(10)]
563 | >>> [f(2) for f in powers_of_x]
564 | [512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
565 | ```
566 |
567 | #### 💡 解释
568 |
569 | - 当我们在一个循环中定义一个函数,并且在函数体中用了循环中的变量时,这个函数只会绑定这个变量本身,并不会绑定当前变量循环到的值。所以最终所有在循环中定义的函数都会使用循环变量最后的值做计算。
570 |
571 | - 如果你想实现心中想的那种效果,可以把循环变量当做一个参数传递进函数体。**为什么这样可以呢?**因为这样在函数作用域内会重新定义一个变量,不是循环里面的那个变量了。
572 |
573 | ```py
574 | funcs = []
575 | for x in range(7):
576 | def some_func(x=x):
577 | return x
578 | funcs.append(some_func)
579 | ```
580 |
581 | **Output:**
582 | ```py
583 | >>> funcs_results = [func() for func in funcs]
584 | >>> funcs_results
585 | [0, 1, 2, 3, 4, 5, 6]
586 | ```
587 |
588 | ---
589 |
590 | ### ▶ `is not ...` 并不是 `is (not ...)`
591 |
592 | ```py
593 | >>> 'something' is not None
594 | True
595 | >>> 'something' is (not None)
596 | False
597 | ```
598 |
599 | #### 💡 解释
600 |
601 | - `is not` 是一个单独的二元运算符, 和分开使用的`is`和`not`作用是不同的。
602 | - `is not` 只有在两边的操作数相同时(id相同)结果才为`False`,否则为`True`
603 |
604 | ---
605 |
606 | ### ▶ 尾部的逗号
607 |
608 | **Output:**
609 | ```py
610 | >>> def f(x, y,):
611 | ... print(x, y)
612 | ...
613 | >>> def g(x=4, y=5,):
614 | ... print(x, y)
615 | ...
616 | >>> def h(x, **kwargs,):
617 | File "", line 1
618 | def h(x, **kwargs,):
619 | ^
620 | SyntaxError: invalid syntax
621 | >>> def h(*args,):
622 | File "", line 1
623 | def h(*args,):
624 | ^
625 | SyntaxError: invalid syntax
626 | ```
627 |
628 | #### 💡 解释:
629 |
630 | - 末尾的逗号在函数参数列表最后并不总是合法的
631 | - 在Python中,参数列表里,有一部分使用前导逗号分隔的,有一部分是用后导逗号分隔的(比如`**kwargs`这种参数用前导逗号分隔,正常参数`x`用后导逗号分隔)。而这种情况就会导致有些参数列表里的逗号前后都没有用到,就会产生冲突导致编译失败。
632 | - **注意** 这种尾部逗号的问题已经在Python 3.6中被[修复](https://bugs.python.org/issue9232)了。然后[这里](https://bugs.python.org/issue9232#msg248399)有对各种尾部逗号用法的讨论。
633 |
634 | ---
635 |
636 | ### ▶ 最后一个反斜杠
637 |
638 | **Output:**
639 | ```
640 | >>> print("\\ C:\\")
641 | \ C:\
642 | >>> print(r"\ C:")
643 | \ C:
644 | >>> print(r"\ C:\")
645 |
646 | File "", line 1
647 | print(r"\ C:\")
648 | ^
649 | SyntaxError: EOL while scanning string literal
650 | ```
651 |
652 | #### 💡 解释
653 |
654 | - 如果字符串前面声明了`r`,说明后面紧跟着的是一个原始字符串,反斜杠在这种字符串中是没有特殊意义的
655 | ```py
656 | >>> print(repr(r"craz\"y"))
657 | 'craz\\"y'
658 | ```
659 | - 解释器实际上是怎么做的呢,虽然看起来仅仅是改变了反斜杠的转义特性,实际上,它(反斜杠)会把自己和紧跟着自己的下一个字符一起传入到解释器,用来供解释器做判断和转换。这也就是为什么当反斜杠在最后一个字符的时候会报错。
660 |
661 | ---
662 |
663 | ### ▶ 纠结的not
664 |
665 | ```py
666 | x = True
667 | y = False
668 | ```
669 |
670 | **Output:**
671 | ```py
672 | >>> not x == y
673 | True
674 | >>> x == not y
675 | File "", line 1
676 | x == not y
677 | ^
678 | SyntaxError: invalid syntax
679 | ```
680 |
681 | #### 💡 解释:
682 |
683 | * 操作符的优先级会影响表达式的计算顺序,并且在Python里,`==`操作符的优先级要高于`not`操作符。
684 | * 所以`not x == y`等于 `not (x == y)`,又等于`not (True == False)`,最终计算结果就会是`True`。
685 | * 但是`x == not y`会报错是因为这个表达式可以等价于`(x == not) y`,而不是我们第一眼认为的`x == (not y)`。
686 |
687 | ---
688 |
689 | ### ▶ 只剩一半的三引号
690 |
691 | **Output:**
692 | ```py
693 | >>> print('crazypython''')
694 | wtfpython
695 | >>> print("crazypython""")
696 | wtfpython
697 | >>> # 下面的语句将会产生语法错误
698 | >>> # print('''crazypython')
699 | >>> # print("""crazypython")
700 | ```
701 |
702 | #### 💡 解释:
703 | + Python支持隐式的[字符串连接](https://docs.python.org/2/reference/lexical_analysis.html#string-literal-concatenation),比如下面这样,
704 | ```
705 | >>> print("crazy" "python")
706 | crazypython
707 | >>> print("crazy" "") # or "crazy"""
708 | crazy
709 | ```
710 | + 在Python中,`'''` 和 `"""` 也是一种字符串界定符,所以如果Python解释器发现了其中一个,那么就会一直在后面找对称的另一个界定符,这也就是为什么上面例子里注释掉的语句会有语法错误,因为解释器在后面找不到和前面`'''`或`"""`配对的界定符。
711 |
712 | ---
713 |
714 | ### ▶ 消失的午夜零点
715 |
716 | ```py
717 | from datetime import datetime
718 |
719 | midnight = datetime(2018, 1, 1, 0, 0)
720 | midnight_time = midnight.time()
721 |
722 | noon = datetime(2018, 1, 1, 12, 0)
723 | noon_time = noon.time()
724 |
725 | if midnight_time:
726 | print("Time at midnight is", midnight_time)
727 |
728 | if noon_time:
729 | print("Time at noon is", noon_time)
730 | ```
731 |
732 | **Output:**
733 | ```sh
734 | ('Time at noon is', datetime.time(12, 0))
735 | ```
736 | 午夜时间并没有被打印出来
737 |
738 | #### 💡 解释:
739 |
740 | 在Python 3.5以前, 对于被赋值为UTC零点的`datetime.time`对象的布尔值,会被认为是`False`。这是一个在用`if obj:`这种语句的时候经常会忽略的特性,所以我们在写这种`if`语句的时候,要注意判断`obj`是否等于`null`或者空。
741 |
742 | ---
743 |
744 | ### ▶ 站错队的布尔型
745 |
746 | 1\.
747 | ```py
748 | # 一个计算列表里布尔型和Int型数量的例子
749 | mixed_list = [False, 1.0, "some_string", 3, True, [], False]
750 | integers_found_so_far = 0
751 | booleans_found_so_far = 0
752 |
753 | for item in mixed_list:
754 | if isinstance(item, int):
755 | integers_found_so_far += 1
756 | elif isinstance(item, bool):
757 | booleans_found_so_far += 1
758 | ```
759 |
760 | **Output:**
761 | ```py
762 | >>> booleans_found_so_far
763 | 0
764 | >>> integers_found_so_far
765 | 4
766 | ```
767 |
768 | 2\.
769 | ```py
770 | another_dict = {}
771 | another_dict[True] = "JavaScript"
772 | another_dict[1] = "Ruby"
773 | another_dict[1.0] = "Python"
774 | ```
775 |
776 | **Output:**
777 | ```py
778 | >>> another_dict[True]
779 | "Python"
780 | ```
781 |
782 | 3\.
783 | ```py
784 | >>> some_bool = True
785 | >>> "crazy"*some_bool
786 | 'crazy'
787 | >>> some_bool = False
788 | >>> "crazy"*some_bool
789 | ''
790 | ```
791 |
792 | #### 💡 解释:
793 |
794 | * 布尔型(Booleans)是 `int`类型的一个子类型(bool is instance of int in Python)
795 | ```py
796 | >>> isinstance(True, int)
797 | True
798 | >>> isinstance(False, int)
799 | True
800 | ```
801 |
802 | * `True`的整形值是`1`,`False`的整形值是`0`
803 | ```py
804 | >>> True == 1 == 1.0 and False == 0 == 0.0
805 | True
806 | ```
807 |
808 | * StackOverFlow有针对这个问题背后原理的[解答](https://stackoverflow.com/a/8169049/4354153)。
809 |
810 | ---
811 |
812 | ### ▶ 类属性与类实例属性
813 |
814 | 1\.
815 | ```py
816 | class A:
817 | x = 1
818 |
819 | class B(A):
820 | pass
821 |
822 | class C(A):
823 | pass
824 | ```
825 |
826 | **Ouptut:**
827 | ```py
828 | >>> A.x, B.x, C.x
829 | (1, 1, 1)
830 | >>> B.x = 2
831 | >>> A.x, B.x, C.x
832 | (1, 2, 1)
833 | >>> A.x = 3
834 | >>> A.x, B.x, C.x
835 | (3, 2, 3)
836 | >>> a = A()
837 | >>> a.x, A.x
838 | (3, 3)
839 | >>> a.x += 1
840 | >>> a.x, A.x
841 | (4, 3)
842 | ```
843 |
844 | 2\.
845 | ```py
846 | class SomeClass:
847 | some_var = 15
848 | some_list = [5]
849 | another_list = [5]
850 | def __init__(self, x):
851 | self.some_var = x + 1
852 | self.some_list = self.some_list + [x]
853 | self.another_list += [x]
854 | ```
855 |
856 | **Output:**
857 |
858 | ```py
859 | >>> some_obj = SomeClass(420)
860 | >>> some_obj.some_list
861 | [5, 420]
862 | >>> some_obj.another_list
863 | [5, 420]
864 | >>> another_obj = SomeClass(111)
865 | >>> another_obj.some_list
866 | [5, 111]
867 | >>> another_obj.another_list
868 | [5, 420, 111]
869 | >>> another_obj.another_list is SomeClass.another_list
870 | True
871 | >>> another_obj.another_list is some_obj.another_list
872 | True
873 | ```
874 |
875 | #### 💡 解释:
876 |
877 | * 类里的属性和类实例中的属性是作为一个字典列表对象进行处理的。如果在当前类中没有找到需要调用的属性名,那么就会递归去父类中寻找。
878 | * `+=`操作符会在原有易变类型对象的基础上做改变而不会重新创建一个新的对象。所以在一个类实例中修改属性值,会影响到所有调用这个属性的相关类实例和类。
879 |
880 | ---
881 |
882 | ### ▶ None的产生
883 |
884 | ```py
885 | some_iterable = ('a', 'b')
886 |
887 | def some_func(val):
888 | return "something"
889 | ```
890 |
891 | **Output:**
892 | ```py
893 | >>> [x for x in some_iterable]
894 | ['a', 'b']
895 | >>> [(yield x) for x in some_iterable]
896 | at 0x7f70b0a4ad58>
897 | >>> list([(yield x) for x in some_iterable])
898 | ['a', 'b']
899 | >>> list((yield x) for x in some_iterable)
900 | ['a', None, 'b', None]
901 | >>> list(some_func((yield x)) for x in some_iterable)
902 | ['a', 'something', 'b', 'something']
903 | ```
904 |
905 | #### 💡 解释:
906 | - 这是一个CPython中`yield`关键字在列表构造和生成器表达式中使用的bug。已经在Python3.8中修复了。在Python3.7中也会警告过时。具体可以看Python的这篇[bug report](https://bugs.python.org/issue10544)
907 | ---
908 |
909 | ### ▶ 不可修改的元组
910 |
911 | ```py
912 | some_tuple = ("A", "tuple", "with", "values")
913 | another_tuple = ([1, 2], [3, 4], [5, 6])
914 | ```
915 |
916 | **Output:**
917 | ```py
918 | >>> some_tuple[2] = "change this"
919 | TypeError: 'tuple' object does not support item assignment
920 | >>> another_tuple[2].append(1000) #这里并没有报错
921 | >>> another_tuple
922 | ([1, 2], [3, 4], [5, 6, 1000])
923 | >>> another_tuple[2] += [99, 999]
924 | TypeError: 'tuple' object does not support item assignment
925 | >>> another_tuple
926 | ([1, 2], [3, 4], [5, 6, 1000, 99, 999])
927 | ```
928 |
929 | 我还以为元组是不可以修改的呢......
930 |
931 | #### 💡 解释:
932 |
933 | * 引用自 https://docs.python.org/2/reference/datamodel.html
934 | > 不可变序列
935 | 一个不可变序列对象在第一次定义后就不再可以修改。(如果这个对象中包含有对其他对象的引用,这些其他对象有可能是可变的或者可修改的;总之,只有直接引用的对象是不可以修改的。)
936 | * `+=`操作符会对左侧操作对象本身进行修改。但是元组是不允许修改的,所以会报错,但是实际上修改的是一个可变的列表(list),元组里保存的只是这个列表的地址,所以最终还是会修改成功。
937 |
938 | ---
939 |
940 | ### ▶ 消失的变量e
941 |
942 | ```py
943 | e = 7
944 | try:
945 | raise Exception()
946 | except Exception as e:
947 | pass
948 | ```
949 |
950 | **Output (Python 2.x):**
951 | ```py
952 | >>> print(e)
953 | # 什么都没有打印
954 | ```
955 |
956 | **Output (Python 3.x):**
957 | ```py
958 | >>> print(e)
959 | NameError: name 'e' is not defined
960 | ```
961 |
962 | #### 💡 解释:
963 |
964 | * 参考自: https://docs.python.org/3/reference/compound_stmts.html#except
965 |
966 | 当一个异常(exception)被用`as`赋值给一个变量后,这个变量将会在`except`语句块结束后清除。看下面这段
967 |
968 | ```py
969 | except E as N:
970 | foo
971 | ```
972 |
973 | 相当于这样
974 |
975 | ```py
976 | except E as N:
977 | try:
978 | foo
979 | finally:
980 | del N
981 | ```
982 |
983 | 这意味着你最好不要在外面定义一个和"N"重名的变量。这些被赋值的异常变量被清除是因为,Python会将这些变量加入一个异常回溯栈中(traceback,仔细观察每次程序出错控制台输出的错误信息就是从tranceback这个堆栈里面以此取出来的),记录这些异常的具体位置信息并且一直保持这个堆栈的信息直到下一次垃圾回收开始。
984 |
985 | * 这些Python的子句并没有独立的作用域,上面所有的例子都在一个作用域里,所以当变量`e`在`except`子句里面被移除后,就相当于在整个作用域都消失了。但是在函数中情况就和上面不一样了,因为Python函数有自己的“内部”作用域。下面这个例子将说明这种情况:
986 |
987 | ```py
988 | def f(x):
989 | del(x)
990 | print(x)
991 |
992 | x = 5
993 | y = [5, 4, 3]
994 | ```
995 |
996 | **Output:**
997 | ```py
998 | >>>f(x)
999 | UnboundLocalError: local variable 'x' referenced before assignment
1000 | >>>f(y)
1001 | UnboundLocalError: local variable 'x' referenced before assignment
1002 | >>> x
1003 | 5
1004 | >>> y
1005 | [5, 4, 3]
1006 | ```
1007 |
1008 | * 在 Python 2.x 版本里面, 变量`e`会被赋值`Exception()`实例,所以当打印的时候什么都不会打印出来。
1009 |
1010 | **Output (Python 2.x):**
1011 | ```py
1012 | >>> e
1013 | Exception()
1014 | >>> print e
1015 | # 什么都没有打印出来
1016 | ```
1017 |
1018 | ---
1019 |
1020 | ### ▶ 亦真还假
1021 |
1022 | ```py
1023 | True = False
1024 | if True == False:
1025 | print("It's true of false?")
1026 | ```
1027 |
1028 | **Output:**
1029 | ```
1030 | It's true of false?
1031 | ```
1032 |
1033 | #### 💡 解释:
1034 |
1035 | - 一开始, Python是没有`bool`布尔类型的(人们用0代表假用非0代表真)。后来增加了`True`,`False`还有`bool`类型,但是为了向前兼容,所以并没有`True`和`False`的关键字常量,只不过作为一个内部变量出现。
1036 | - Python 3是不向前兼容的,所以终于修复了这个问题,同时也要注意上面的例子在Python 3.X版本下是运行不了的!
1037 |
1038 | ---
1039 |
1040 | ### ▶ 转瞬即空
1041 |
1042 | ```py
1043 | some_list = [1, 2, 3]
1044 | some_dict = {
1045 | "key_1": 1,
1046 | "key_2": 2,
1047 | "key_3": 3
1048 | }
1049 |
1050 | some_list = some_list.append(4)
1051 | some_dict = some_dict.update({"key_4": 4})
1052 | ```
1053 |
1054 | **Output:**
1055 | ```py
1056 | >>> print(some_list)
1057 | None
1058 | >>> print(some_dict)
1059 | None
1060 | ```
1061 |
1062 | #### 💡 解释
1063 |
1064 | 大部分修改序列结构对象的方法,比如`list.append`,`dict.update`,`list.sort`等等。都会直接对序列对象本身进行操作,也就是所谓的“就地操作”(in-plcae)并且会返回`None`。这么做是为了提升程序的性能,避免还需要把原有对象拷贝一次。(参考[这里](http://docs.python.org/2/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list))。
1065 |
1066 | ```py
1067 | some_list = [1, 2, 3]
1068 | ```
1069 |
1070 | **Output:**
1071 | ```py
1072 | >>>some_list2 = some_list.append(4)
1073 | >>>some_list2
1074 | None
1075 | >>>some_list
1076 | [1, 2, 3, 4]
1077 | ```
1078 |
1079 | ---
1080 |
1081 | ### ▶ 子类的关系 *
1082 |
1083 | **Output:**
1084 | ```py
1085 | >>> from collections import Hashable
1086 | >>> issubclass(list, object)
1087 | True
1088 | >>> issubclass(object, Hashable)
1089 | True
1090 | >>> issubclass(list, Hashable)
1091 | False
1092 | ```
1093 |
1094 | 是不是觉得类的继承关系应该是可传递的(transitive)?(比如,`A`是`B`的一个子类,`B`又是`C`的一个子类,那么`A`也"应该"是`C`的子类)
1095 |
1096 | #### 💡 解释:
1097 |
1098 | * 在Python中子类的继承关系不一定是传递的,任何人都可以自定义元类(metaclass)中的`__subclasscheck__`函数(`_subclasscheck__(subclass)`检查subclass是不是调用类的子类)。
1099 | * 当调用`issubclass(cls, Hashable)`的时候,函数只是简单的检查一下`cls`和它继承的类中有没有"`__hash__`"这个方法。
1100 | * 因为`object`是可以被哈希的(也就是说`object`有`__hash__`这个函数),但是`list`是不能被哈希的,所以他们之间打破了传导关系。
1101 | * 如果想看更详尽的解释,[这里](https://www.naftaliharris.com/blog/python-subclass-intransitivity/)有关于Python子类关系传导的详细解释。
1102 |
1103 | ---
1104 |
1105 | ### ▶ 神秘的键值转换 *
1106 |
1107 | ```py
1108 | class SomeClass(str):
1109 | pass
1110 |
1111 | some_dict = {'s':42}
1112 | ```
1113 |
1114 | **Output:**
1115 | ```py
1116 | >>> type(list(some_dict.keys())[0])
1117 | str
1118 | >>> s = SomeClass('s')
1119 | >>> some_dict[s] = 40
1120 | >>> some_dict # expected: Two different keys-value pairs
1121 | {'s': 40}
1122 | >>> type(list(some_dict.keys())[0])
1123 | str
1124 | ```
1125 |
1126 | #### 💡 解释:
1127 |
1128 | * 类`s`和字符串`s`的哈希值是一样的,因为`SomeClass`继承了`str`类的`__hash__`方法。
1129 | * `SomeClass("s") == "s"` 等于`True`,因为`SomeClass`同样继承了`str`类的`__eq__`方法。
1130 | * 因为两个对象的哈希值(hash)和值(value)全都相等,所以他们在字典的关键字(key)里是被认为相同的。
1131 | * 如果想要打到预期的效果,我们可以重新定义`SomeClass`的`__eq__`方法
1132 | ```py
1133 | class SomeClass(str):
1134 | def __eq__(self, other):
1135 | return (
1136 | type(self) is SomeClass
1137 | and type(other) is SomeClass
1138 | and super().__eq__(other)
1139 | )
1140 |
1141 | # 当我们自定义 __eq__ 方法后, Python会停止
1142 | # 自动继承 __hash__ 方法, 所以我们同样也需要定义它
1143 | __hash__ = str.__hash__
1144 |
1145 | some_dict = {'s':42}
1146 | ```
1147 |
1148 | **Output:**
1149 | ```py
1150 | >>> s = SomeClass('s')
1151 | >>> some_dict[s] = 40
1152 | >>> some_dict
1153 | {'s': 40, 's': 42}
1154 | >>> keys = list(some_dict.keys())
1155 | >>> type(keys[0]), type(keys[1])
1156 | (__main__.SomeClass, str)
1157 | ```
1158 |
1159 | ---
1160 |
1161 | ### ▶ 看你能不能猜到这个的结果?
1162 |
1163 | ```py
1164 | a, b = a[b] = {}, 5
1165 | ```
1166 |
1167 | **Output:**
1168 | ```py
1169 | >>> a
1170 | {5: ({...}, 5)}
1171 | ```
1172 |
1173 | #### 💡 解释:
1174 |
1175 | * 根据Python的赋值语句的[文档](https://docs.python.org/3.7/reference/simple_stmts.html#assignment-statements),赋值语句有如下的格式
1176 | ```
1177 | (target_list "=")+ (expression_list | yield_expression)
1178 | ```
1179 | 并且
1180 | > 一个赋值语句会对表达式列表(expression_list)进行求值(这个可以只有一个表达式,也可以多个表达式用逗号分开组成表达式列表,后者最终会表现为元组的形式)并且会将单次的求值结果对象依次从左至右赋值给目标列表(target_list)。
1181 |
1182 | * 在`(target_list "=")+`这个表达式中`+`意味着 **一个或者多个** 目标列表。这上面这个例子中,目标列表是`a,b`和`a[b]`(注意表达式列表永远只有一个,在我们这个例子里是`{}, 5`)。
1183 |
1184 | * 当表达式计算出来后,它的值会被 **从左至右** 依次赋值给目标列表。所以,在上面的例子里,第一次赋值会把`{}, 5`这个元组赋值给`a, b`这个目标列表,这个时候`a = {}` 并且 `b = 5`。
1185 |
1186 | * 现在`a`被赋值成了`{}`,一个可变对象(mutable object)。
1187 |
1188 | * 第二个目标列表是`a[b]`(你可能会认为这种写法会报错,因为我们没有之前并没有定义`a`和`b`,但是没关系,我们刚刚已经给`a`和`b`分别赋值了`{}`和`5`)。
1189 |
1190 | * 现在我们会把元组`({}, 5)`赋值给字典关键为`5`的字典对象(也就是`a[5]`),同时也因此创建了一个循环引用(circular reference)(`{...}`在输出中代表同一个对象`a`已经被引用了)。下面是一个关于引用循环简单点的例子
1191 |
1192 | ```py
1193 | >>> some_list = some_list[0] = [0]
1194 | >>> some_list
1195 | [[...]]
1196 | >>> some_list[0]
1197 | [[...]]
1198 | >>> some_list is some_list[0]
1199 | True
1200 | >>> some_list[0][0][0][0][0][0] == some_list
1201 | True
1202 | ```
1203 | 在我们的例子用也是同样的情况(`a[b][0]`和`a`是指向同一个对象)
1204 |
1205 | * 所以总结一下,我们可以把上面的列子像如下这样拆分
1206 | ```py
1207 | a, b = {}, 5
1208 | a[b] = a, b
1209 | ```
1210 | 并且通过`a[b][0]`和`a`是同一个对象来证明确实存在引用循环
1211 | ```py
1212 | >>> a[b][0] is a
1213 | True
1214 | ```
1215 |
1216 | ---
1217 |
1218 | ---
1219 |
1220 | ## 第二章: 瞒天过海
1221 |
1222 | ### ▶ 无效的一行?
1223 |
1224 | **Output:**
1225 | ```py
1226 | >>> value = 11
1227 | >>> valuе = 32
1228 | >>> value
1229 | 11
1230 | ```
1231 |
1232 | 哈?一脸懵逼是不是(囧)?
1233 |
1234 | **提示:** 最简单的复现上面结果的办法是把上面的赋值语句原封不动的复制粘贴到你的文件或者命令行中执行。
1235 |
1236 | #### 💡 解释
1237 |
1238 | 一些非西方字符集看起来和我们平时用的英文字母的外观是一模一样的,但是在解释器看来就不一样了。
1239 |
1240 | ```py
1241 | >>> ord('е') # 俄语的字母 'e' (Ye)
1242 | 1077
1243 | >>> ord('e') # 拉丁文的字母'e', 也就是我们平时在英语和用标准键盘打出来的字母
1244 | 101
1245 | >>> 'е' == 'e'
1246 | False
1247 |
1248 | >>> value = 42 # 拉丁的 e
1249 | >>> valuе = 23 # 俄语里的 'e', 这里如果是用的Python 2.x,解释器会产生一个`SyntaxError`的异常
1250 | >>> value
1251 | 42
1252 | ```
1253 |
1254 | 内置函数`ord()`可以返回对应字符在Unicode编码中的位置([code point](https://en.wikipedia.org/wiki/Code_point)), 因为俄语的字符'e'和拉丁语的字符'e'的位置是不一样的,所以在上面的例子里,解释器会认为两个`value`变量是不同的。
1255 |
1256 | ---
1257 |
1258 | ### ▶ 移形换位 *
1259 |
1260 | ```py
1261 | import numpy as np
1262 |
1263 | def energy_send(x):
1264 | # 初始化一个numpy数组
1265 | np.array([float(x)])
1266 |
1267 | def energy_receive():
1268 | # 返回一个空numpy数组
1269 | return np.empty((), dtype=np.float).tolist()
1270 | ```
1271 |
1272 | **Output:**
1273 | ```py
1274 | >>> energy_send(123.456)
1275 | >>> energy_receive()
1276 | 123.456
1277 | ```
1278 |
1279 | 是不是很神奇?
1280 |
1281 | #### 💡 解释:
1282 |
1283 | * 因为在`energy_send`函数中创建的numpy数组并没有通过`return`返回赋给任何一个变量,所以带边这个数组的那一块内存空间是可以自由分配的。
1284 | * `numpy.empty()` 会返回最近的一块还没有被使用的内存块,并且不会对这个内存块进行初始化。这块内存块恰恰就是上一个函数刚刚释放的那块内存(大部分时间是这样的,但并不是绝对的)。
1285 |
1286 | ---
1287 |
1288 | ### ▶ 到底哪里出错了呢?
1289 |
1290 | ```py
1291 | def square(x):
1292 | """
1293 | 通过加法运算计算平方的函数
1294 | """
1295 | sum_so_far = 0
1296 | for counter in range(x):
1297 | sum_so_far = sum_so_far + x
1298 | return sum_so_far
1299 | ```
1300 |
1301 | **Output (Python 2.x):**
1302 |
1303 | ```py
1304 | >>> square(10)
1305 | 10
1306 | ```
1307 |
1308 | 结果不应该是100吗?
1309 |
1310 | **注意:** 如果你不能复现上面例子的结果, 可以试试直接运行[mixed_tabs_and_spaces.py](/mixed_tabs_and_spaces.py) 这个文件。
1311 |
1312 | #### 💡 解释
1313 |
1314 | * **不要把空格和制表符混在一起使用!** 例子中`return`语句前面的是一个用制表符"tab"来缩进的,代码的其他地方都是使用“四个空格”作为缩进的。
1315 | * Python是这样处理制表符的:
1316 | > 首先,制表符(tabs)会被依次替换成(从左至右)1到8个不等的空格直到替换后的空格数满足8的倍数......[出自Python文档](https://docs.python.org/2.0/ref/indentation.html)
1317 | * 所以在`square`这个函数最后这个制表符"tab"就被替换成了8个空格,八个空格就是两个缩进,所以就等于把`return`语句缩进到了`for`循环内。
1318 | * 这种情况在Python 3中就会直接报错。
1319 |
1320 | **Output (Python 3.x):**
1321 | ```py
1322 | TabError: inconsistent use of tabs and spaces in indentation
1323 | ```
1324 |
1325 | ---
1326 |
1327 | ---
1328 |
1329 |
1330 | ## 第三章: 隐藏的陷阱
1331 |
1332 |
1333 | ### ▶ 不要在迭代一个字典的时候修改这个字典!
1334 |
1335 | ```py
1336 | x = {0: None}
1337 |
1338 | for i in x:
1339 | del x[i]
1340 | x[i+1] = None
1341 | print(i)
1342 | ```
1343 |
1344 | **Output (Python 2.7- Python 3.5):**
1345 |
1346 | ```
1347 | 0
1348 | 1
1349 | 2
1350 | 3
1351 | 4
1352 | 5
1353 | 6
1354 | 7
1355 | ```
1356 |
1357 | 这个迭代会准确运行**八**次停止。
1358 |
1359 | #### 💡 解释:
1360 |
1361 | * 首先我是不赞成在迭代一个字典的时候对这个字典做修改的。
1362 | * 这个迭代正好运行了八次是因为正好到了一个点,到了这个点后,这个字典对象需要重新调整大小用来容纳足够的关键字(keys)(我们在循环中已经连续做了八次删除了,当然需要重新调整字典的大小了)。实际上这个点在哪取决于Python的具体实现细节。
1363 | * 对删除键值的处理和什么时候触发调整字典大小会根据不同Python的版本而有所不同。
1364 |
1365 | ---
1366 |
1367 | ### ▶ 删不掉的对象 *
1368 |
1369 | ```py
1370 | class SomeClass:
1371 | def __del__(self):
1372 | print("Deleted!")
1373 | ```
1374 |
1375 | **Output:**
1376 | 1\.
1377 | ```py
1378 | >>> x = SomeClass()
1379 | >>> y = x
1380 | >>> del x # 这里应该输出 "Deleted!"
1381 | >>> del y
1382 | Deleted!
1383 | ```
1384 |
1385 | 啧.....最后还是输出了的。你可能已经猜到了,我们第一次尝试删除`x`的时候,`__del__`方法并没有被调用。现在我们在上面例子的基础上再加几句代码。
1386 |
1387 | 2\.
1388 | ```py
1389 | >>> x = SomeClass()
1390 | >>> y = x
1391 | >>> del x
1392 | >>> y # 检查y是否存在
1393 | <__main__.SomeClass instance at 0x7f98a1a67fc8>
1394 | >>> del y # 就像上一个例子一样,这里应该输出"Deleted!"
1395 | >>> globals() # 嗯......好像并没有输出。让我们检查一下全局变量看看。
1396 | Deleted!
1397 | {'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None}
1398 | ```
1399 |
1400 | 好吧,最后还是输出了(好奇怪的说):confused:
1401 |
1402 | #### 💡 解释:
1403 | + `del x`并不会直接调用`x.__del__()`。
1404 | + 当调用`del x`的时候,Python会把所有x指向的对象的引用数减一(在这里就是前面实例化的对象`SomeClass()`),只有当这个对象的引用数为零的时候,才会调用`__del__()`。
1405 | + 在第二个例子中,`y.__del__()`没有被调用时因为前面的语句(`>>> y`)引用了同一个对象(SomeClass()),增加了这个对象的引用数,所以在`del y`以后这个对象的引用数并没有变成零。
1406 | + 调用`globals`会检查并销毁没用的引用,这时候针对上面对象(`SomeClass()`)的引用数就变成零了,"Deleted!"就被打印出来了。
1407 |
1408 | ---
1409 |
1410 | ### ▶ 删除正在迭代中的列表项
1411 |
1412 | ```py
1413 | list_1 = [1, 2, 3, 4]
1414 | list_2 = [1, 2, 3, 4]
1415 | list_3 = [1, 2, 3, 4]
1416 | list_4 = [1, 2, 3, 4]
1417 |
1418 | for idx, item in enumerate(list_1):
1419 | del item
1420 |
1421 | for idx, item in enumerate(list_2):
1422 | list_2.remove(item)
1423 |
1424 | for idx, item in enumerate(list_3[:]):
1425 | list_3.remove(item)
1426 |
1427 | for idx, item in enumerate(list_4):
1428 | list_4.pop(idx)
1429 | ```
1430 |
1431 | **Output:**
1432 | ```py
1433 | >>> list_1
1434 | [1, 2, 3, 4]
1435 | >>> list_2
1436 | [2, 4]
1437 | >>> list_3
1438 | []
1439 | >>> list_4
1440 | [2, 4]
1441 | ```
1442 |
1443 | 知道为什么输出是`[2, 4]`吗?
1444 |
1445 | #### 💡 解释:
1446 |
1447 | * 首先,我是不赞成在迭代的过程中修改迭代参数的(就像上面例子里那样)。正确的方法应该是先把迭代的列表拷贝一份,拿拷贝后的列表做迭代,就像上面第三个迭代做的那样(`list_3[:]`)。
1448 |
1449 | ```py
1450 | >>> some_list = [1, 2, 3, 4]
1451 | >>> id(some_list)
1452 | 139798789457608
1453 | >>> id(some_list[:]) # 注意如果用切片法的话,Python会新建一个list对象。
1454 | 139798779601192
1455 | ```
1456 |
1457 |
1458 | **`del`,`remove`和`pop`的区别:**
1459 | * `del var_name` 只会移除本地或者全局命名空间中对`var_name`这个变量的绑定(这就是为什么`list_1`迭代后没有任何改变,因为`list_1`这个变量并没有收到影响)。
1460 | * `remove` 会根据值(value)进行匹配并移除第一个匹配上的元素,若没有匹配的元素则抛出`ValueError`异常。
1461 | * `pop` 会移除指定索引(index)的元素并且返回这个元素,如果指定的元素不存在则抛出`IndexError`异常。
1462 |
1463 | **所以为什么会输出`[2, 4]`呢?**
1464 | - 首先列表迭代是根据索引值一个一个迭代的,所以当我们把`1`这个元素从`list_2`或者`list_4`中移除了以后,这两个列表就变成了`[2, 3, 4]`。剩下的这几个元素会依次向左移填补刚刚删除了的元素的位置,也就是说,`2`这个元素现在的索引值(index)变成了0,`3`的索引值变成了1。接着在下次迭代的时候,`for`循环访问的索引值已经变成了1(也就是列表里`3`这个元素),所以`2`这个元素就被跳过去(没有删除)。同样的情况每迭代一次都会发生一次。
1465 |
1466 | ---
1467 |
1468 | ### ▶ 被泄露出去的循环变量!
1469 |
1470 | 1\.
1471 | ```py
1472 | for x in range(7):
1473 | if x == 6:
1474 | print(x, ': for x inside loop')
1475 | print(x, ': x in global')
1476 | ```
1477 |
1478 | **Output:**
1479 | ```py
1480 | 6 : for x inside loop
1481 | 6 : x in global
1482 | ```
1483 |
1484 | 但是我从来没有在循环外定义一个变量`x`呀!
1485 |
1486 | 2\.
1487 | ```py
1488 | # 这回我先初始化一个x变量
1489 | x = -1
1490 | for x in range(7):
1491 | if x == 6:
1492 | print(x, ': for x inside loop')
1493 | print(x, ': x in global')
1494 | ```
1495 |
1496 | **Output:**
1497 | ```py
1498 | 6 : for x inside loop
1499 | 6 : x in global
1500 | ```
1501 |
1502 | 3\.
1503 | ```
1504 | x = 1
1505 | print([x for x in range(5)])
1506 | print(x, ': x in global')
1507 | ```
1508 |
1509 | **Output (Python 2.x):**
1510 | ```
1511 | [0, 1, 2, 3, 4]
1512 | (4, ': x in global')
1513 | ```
1514 |
1515 | **Output (Python 3.x):**
1516 | ```
1517 | [0, 1, 2, 3, 4]
1518 | 1 : x in global
1519 | ```
1520 |
1521 | #### 💡 解释:
1522 |
1523 | - 在Python里, for循环有它自己的作用域,但是在循环结束后,不会销毁那些循环变量。就算我们提前在全局作用域里定义了变量(就像上面那种情况),也会被循环变量覆盖。
1524 |
1525 | - 关于为什么在Python2和Python3中有不同的输出,可以在Python3的文档([What's New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html))中找到答案,下面会贴出原文:
1526 |
1527 | >"List comprehensions no longer support the syntactic form `[... for var in item1, item2, ...]`. Use `[... for var in (item1, item2, ...)]` instead. Also, note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a `list()` constructor, and in particular the loop control variables are no longer leaked into the surrounding scope."
1528 |
1529 | - 只用看最后一句话就够了**in particular the loop control variables are no longer leaked into the surrounding scope**,翻译过来是**特别是循环控制变量不会再泄露到包围着它的作用域以外了**,还有这个特性是针对构造列表(List comprehensions)说的,不包括正常的for循环。
1530 |
1531 | ---
1532 |
1533 | ### ▶ 小心那些有默认值的可变参数!
1534 |
1535 | ```py
1536 | def some_func(default_arg=[]):
1537 | default_arg.append("some_string")
1538 | return default_arg
1539 | ```
1540 |
1541 | **Output:**
1542 | ```py
1543 | >>> some_func()
1544 | ['some_string']
1545 | >>> some_func()
1546 | ['some_string', 'some_string']
1547 | >>> some_func([])
1548 | ['some_string']
1549 | >>> some_func()
1550 | ['some_string', 'some_string', 'some_string']
1551 | ```
1552 |
1553 | #### 💡 解释:
1554 |
1555 | - 在Python中,如果参数有默认值并且是一个可变对象,那么这个默认的对象会一直存在,并不会随着每次创建实例而重新生成一个新的对象。当我们显式的把`[]`对象传递给`some_func`函数的时候,`default_arg`这个参数的默认值就不会被使用而被新对象替代了,所以这个函数会按照我们预期的那样返回值。
1556 |
1557 | ```py
1558 | def some_func(default_arg=[]):
1559 | default_arg.append("some_string")
1560 | return default_arg
1561 | ```
1562 |
1563 | **Output:**
1564 | ```py
1565 | >>> some_func.__defaults__ #__defaults__会把这个函数的默认参数都打印出来
1566 | ([],)
1567 | >>> some_func()
1568 | >>> some_func.__defaults__
1569 | (['some_string'],)
1570 | >>> some_func()
1571 | >>> some_func.__defaults__
1572 | (['some_string', 'some_string'],)
1573 | >>> some_func([])
1574 | >>> some_func.__defaults__
1575 | (['some_string', 'some_string'],)
1576 | ```
1577 |
1578 | - 一个常用的用来避免上述bug的做法是这样的,把默认可变参数赋值成`None`,然后在函数体中检查这个参数是否被赋值,如果没有就赋一个默认值:
1579 |
1580 | ```py
1581 | def some_func(default_arg=None):
1582 | if not default_arg:
1583 | default_arg = []
1584 | default_arg.append("some_string")
1585 | return default_arg
1586 | ```
1587 |
1588 | ---
1589 |
1590 | ### ▶ 抓住这些异常!
1591 |
1592 | ```py
1593 | some_list = [1, 2, 3]
1594 | try:
1595 | # 这里应该会抛出一个 ``IndexError`` 异常
1596 | print(some_list[4])
1597 | except IndexError, ValueError:
1598 | print("Caught!")
1599 |
1600 | try:
1601 | # 这里会抛出一个 ``ValueError`` 异常
1602 | some_list.remove(4)
1603 | except IndexError, ValueError:
1604 | print("Caught again!")
1605 | ```
1606 |
1607 | **Output (Python 2.x):**
1608 | ```py
1609 | Caught!
1610 |
1611 | ValueError: list.remove(x): x not in list
1612 | ```
1613 |
1614 | **Output (Python 3.x):**
1615 | ```py
1616 | File "", line 3
1617 | except IndexError, ValueError:
1618 | ^
1619 | SyntaxError: invalid syntax
1620 | ```
1621 |
1622 | #### 💡 解释
1623 |
1624 | * 如果想在`except`语句中同时捕获多个异常类型,需要把这些类型写成元组(tuple)的形式放在第一个参数的位置。第二个参数是一个可选参数,用来绑定具体捕获异常后的对象。比如,
1625 | ```py
1626 | some_list = [1, 2, 3]
1627 | try:
1628 | # 这里会产生一个 ``ValueError`` 异常
1629 | some_list.remove(4)
1630 | except (IndexError, ValueError), e:
1631 | print("Caught again!")
1632 | print(e)
1633 | ```
1634 | **Output (Python 2.x):**
1635 | ```
1636 | Caught again!
1637 | list.remove(x): x not in list
1638 | ```
1639 | **Output (Python 3.x):**
1640 | ```py
1641 | File "", line 4
1642 | except (IndexError, ValueError), e:
1643 | ^
1644 | IndentationError: unindent does not match any outer indentation level
1645 | ```
1646 |
1647 | * 在Python 3中已经弃用了使用逗号分隔异常类型和异常变量了;而是要使用`as`语法了。比如,
1648 | ```py
1649 | some_list = [1, 2, 3]
1650 | try:
1651 | some_list.remove(4)
1652 |
1653 | except (IndexError, ValueError) as e:
1654 | print("Caught again!")
1655 | print(e)
1656 | ```
1657 | **Output:**
1658 | ```
1659 | Caught again!
1660 | list.remove(x): x not in list
1661 | ```
1662 |
1663 | ---
1664 |
1665 | ### ▶ 相同的操作,不同的结果
1666 |
1667 | 1\.
1668 | ```py
1669 | a = [1, 2, 3, 4]
1670 | b = a
1671 | a = a + [5, 6, 7, 8]
1672 | ```
1673 |
1674 | **Output:**
1675 | ```py
1676 | >>> a
1677 | [1, 2, 3, 4, 5, 6, 7, 8]
1678 | >>> b
1679 | [1, 2, 3, 4]
1680 | ```
1681 |
1682 | 2\.
1683 | ```py
1684 | a = [1, 2, 3, 4]
1685 | b = a
1686 | a += [5, 6, 7, 8]
1687 | ```
1688 |
1689 | **Output:**
1690 | ```py
1691 | >>> a
1692 | [1, 2, 3, 4, 5, 6, 7, 8]
1693 | >>> b
1694 | [1, 2, 3, 4, 5, 6, 7, 8]
1695 | ```
1696 |
1697 | #### 💡 解释:
1698 |
1699 | * `a += b` 和 `a = a + b`的内部处理流程有时候会根据操作数的不同有所不同。比如*`op=`*这种操作符(比如`+=`,`-=`,`*=`这样),针对列表(list)对象这两个表达式就会有不一样的内在表现。
1700 |
1701 | * 表达式`a = a + [5,6,7,8]`会生成一个新的对象,并且`a`会引用这个新生成的对象,`b`变量不会变(继续引用之前的列表对象`[1,2,3,4]`)。
1702 |
1703 | * 表达式`a += [5,6,7,8]`就不会生成一个新对象,它实际上是在原有对象的基础上又“扩展了”几个元素,也就是所谓的就地改变(in-place)。所以这个时候`a`,`b`两个变量还是同时引用的一个对象。
1704 |
1705 | ---
1706 |
1707 | ### ▶ 作用域之外的变量
1708 |
1709 | ```py
1710 | a = 1
1711 | def some_func():
1712 | return a
1713 |
1714 | def another_func():
1715 | a += 1
1716 | return a
1717 | ```
1718 |
1719 | **Output:**
1720 | ```py
1721 | >>> some_func()
1722 | 1
1723 | >>> another_func()
1724 | UnboundLocalError: local variable 'a' referenced before assignment
1725 | ```
1726 |
1727 | #### 💡 解释:
1728 | * 当你在某个作用域给一个变量赋值的时候,这个变量就会自动变成这个作用域的变量。所以`a`会变成`another_func`这个函数的局部变量,但是又没有在这个函数作用域内正确的初始化`a`变量,结果就发生了异常。
1729 | * 这篇[文章](http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html)很好的解释了命名空间和作用域是如何在Python中起作用的(是英文版,等有时间我会翻译一下,如果有热心的同学帮忙就更好啦!)。
1730 | * 如果想在局部作用域内使用全局作用域的变量`a`,需要使用`global`关键字。
1731 | ```py
1732 | def another_func()
1733 | global a
1734 | a += 1
1735 | return a
1736 | ```
1737 |
1738 | **Output:**
1739 | ```py
1740 | >>> another_func()
1741 | 2
1742 | ```
1743 |
1744 | ---
1745 |
1746 | ### ▶ 小心那些链式操作符
1747 |
1748 | ```py
1749 | >>> (False == False) in [False] # 没问题,OK?
1750 | False
1751 | >>> False == (False in [False]) # 这个也OK对吧?
1752 | False
1753 | >>> False == False in [False] # 这个呢?是不是开始不对了?
1754 | True
1755 |
1756 | >>> True is False == False
1757 | False
1758 | >>> False is False is False
1759 | True
1760 |
1761 | >>> 1 > 0 < 1
1762 | True
1763 | >>> (1 > 0) < 1
1764 | False
1765 | >>> 1 > (0 < 1)
1766 | False
1767 | ```
1768 |
1769 | #### 💡 解释:
1770 |
1771 | 按照 https://docs.python.org/2/reference/expressions.html#not-in 里面说的:
1772 |
1773 | > 如果 a, b, c, ..., y, z 这些是表达式,op1, op2, ..., opN 这些是比较符,那么 a op1 b op2 c ... y opN z 就等于 a op1 b and b op2 c and ... y opN z, 除非其中有些表达式最多只能计算一次。
1774 |
1775 | 这种模式运行在像 `a == b == c` 和 `0 <=x <= 100` 这种语句里就会产生很有意思的结果了,就像上边那些例子一样。
1776 |
1777 | * `False is False is False` 等于 `(False is False) and (False is False)`。
1778 | * `True is False == False` 等于 `True is False and False == False` 而且第一部分 (`True is False`) 等于 `False`, 所以整个表达式结果就为 `False`。
1779 | * `1 > 0 < 1` 等于 `1 > 0 and 0 < 1` 最后结果等于 `True`。
1780 | * 表达式 `(1 > 0) < 1` 等于 `True < 1` 而且
1781 | ```py
1782 | >>> int(True)
1783 | 1
1784 | >>> True + 1 #这个跟例子没啥关系,就是想告诉你们布尔值也可以这样玩^_^
1785 | 2
1786 | ```
1787 | 所以, `1 < 1`也等于`False`
1788 |
1789 | ---
1790 |
1791 | ### ▶ 被忽略的类变量
1792 |
1793 | 1\.
1794 | ```py
1795 | x = 5
1796 | class SomeClass:
1797 | x = 17
1798 | y = (x for i in range(10))
1799 | ```
1800 |
1801 | **Output:**
1802 | ```py
1803 | >>> list(SomeClass.y)[0]
1804 | 5
1805 | ```
1806 |
1807 | 2\.
1808 | ```py
1809 | x = 5
1810 | class SomeClass:
1811 | x = 17
1812 | y = [x for i in range(10)]
1813 | ```
1814 |
1815 | **Output (Python 2.x):**
1816 | ```py
1817 | >>> SomeClass.y[0]
1818 | 17
1819 | ```
1820 |
1821 | **Output (Python 3.x):**
1822 | ```py
1823 | >>> SomeClass.y[0]
1824 | 5
1825 | ```
1826 |
1827 | #### 💡 解释
1828 | - 如果一个类定义内嵌套了一个子作用域,那么在这个作用域内部会忽略所有这个类级别的变量绑定。
1829 | - 一个生成器(generator)表达式有自己的作用域
1830 | - 从Python 3.X开始,列表生成式也有自己的作用域了。
1831 |
1832 | 
1833 |
1834 | ---
1835 | ### ▶ 小心处理元组
1836 |
1837 | 1\.
1838 | ```py
1839 | x, y = (0, 1) if True else None, None
1840 | ```
1841 |
1842 | **Output:**
1843 | ```
1844 | >>> x, y # 期待的结果是 (0, 1)
1845 | ((0, 1), None)
1846 | ```
1847 |
1848 | 基本每一个Python程序员都会遇到过下面这种情况。
1849 |
1850 | 2\.
1851 | ```py
1852 | t = ('one', 'two')
1853 | for i in t:
1854 | print(i)
1855 |
1856 | t = ('one')
1857 | for i in t:
1858 | print(i)
1859 |
1860 | t = ()
1861 | print(t)
1862 | ```
1863 |
1864 | **Output:**
1865 | ```py
1866 | one
1867 | two
1868 | o
1869 | n
1870 | e
1871 | tuple()
1872 | ```
1873 |
1874 | #### 💡 解释:
1875 | * 第一个例子里,想要得到期待的结果的写法应该是`x, y = (0, 1) if True else (None, None)`,要显式声明元组。
1876 | * 第二个例子里,想要得到期待的结果的写法应该是 `t = ('one',)` 或者 `t = 'one',`(原来的少了逗号)否则解释器会把变量`t`当做一个`str`字符串处理挨个迭代里面的每一个字符。
1877 | * `()`是一个特殊的标识符专门用来表示空元组(`tuple`)。
1878 |
1879 | ---
1880 |
1881 | ---
1882 |
1883 |
1884 | ## 第四章: 一起来找些有趣的东西!
1885 |
1886 | 这一章包含了一些有趣的但是鲜为人知的彩蛋,大部分的Python新手都很少有人知道,就比如我(当然,现在我知道了)。
1887 |
1888 | ### ▶ 人生苦短,我用Python *
1889 |
1890 | 首先,我先引用下面这个模块,然后......
1891 |
1892 | ```py
1893 | import antigravity
1894 | ```
1895 |
1896 | **Output:**
1897 | 嘘....这个秘密可没几个人知道!
1898 |
1899 | #### 💡 解释:
1900 | + `antigravity` 模块是针对Python开发者的一个小彩蛋。
1901 | + `import antigravity` 会打开一个指向 [classic XKCD comic](http://xkcd.com/353/)的网站,一个关于Python的漫画网站。
1902 | + 而且,这还是一个俄罗斯套娃彩蛋,也就是说**这个彩蛋中还包含着另一个彩蛋**。如果你打开这个模块的[代码](https://github.com/python/cpython/blob/master/Lib/antigravity.py#L7-L17),你会发现代码中还定义了一个实现[geohashing算法](https://xkcd.com/426/)的函数(这个算法是一个利用股票市场数据随机生成经度纬度坐标的算法)。
1903 |
1904 | ---
1905 |
1906 | ### ▶ 为什么要用`goto`? *
1907 |
1908 | ```py
1909 | from goto import goto, label
1910 | for i in range(9):
1911 | for j in range(9):
1912 | for k in range(9):
1913 | print("I'm trapped, please rescue!")
1914 | if k == 2:
1915 | goto .breakout #从一个深层循环内跳出
1916 | label .breakout
1917 | print("Freedom!")
1918 | ```
1919 |
1920 | **Output (Python 2.3):**
1921 | ```py
1922 | I'm trapped, please rescue!
1923 | I'm trapped, please rescue!
1924 | Freedom!
1925 | ```
1926 |
1927 | #### 💡 解释:
1928 | - `goto`模块是作为2004年的愚人节(2004.4.1)玩笑[发布](https://mail.python.org/pipermail/python-announce-list/2004-April/002982.html)的,只有Python 2.3版本有`goto`模块。
1929 | - 当前版本的Python是没有这个模块的(如果你用的是conda甚至不能下载到2.3版本的Python)。
1930 | - 虽然上面版本可以使用`goto`,但是也请不要使用它。[这里](https://docs.python.org/3/faq/design.html#why-is-there-no-goto)是为什么当前版本没有`goto`模块的原因。
1931 |
1932 | ---
1933 |
1934 | ### ▶ 试试用大括号? *
1935 |
1936 | 如果你不太习惯Python中用空格缩进来表示语句段(scopes)的方法,可以通过引用`__future__`模块来使用C语言大括号({})形式的表示方法来表示语句段,
1937 |
1938 | ```py
1939 | from __future__ import braces
1940 | ```
1941 |
1942 | **Output:**
1943 | ```py
1944 | File "some_file.py", line 1
1945 | from __future__ import braces
1946 | SyntaxError: not a chance
1947 | ```
1948 |
1949 | 想用大括号?不可能的,如果你不服气,用Java去。
1950 |
1951 | #### 💡 解释:
1952 | + `__future__`模块一般用来提供未来Python版本才拥有的一些特性。当然,在这个例子里,"future"这个词本身也算是对大括号的嘲讽。
1953 | + 这是一个Python社区针对大括号这个问题设计出来的一个彩蛋。
1954 |
1955 | ---
1956 |
1957 | ### ▶ 不等号的争议 *
1958 |
1959 | **Output (Python 3.x)**
1960 | ```py
1961 | >>> from __future__ import barry_as_FLUFL
1962 | >>> "Ruby" != "Python" # 这句话没有任何争议
1963 | File "some_file.py", line 1
1964 | "Ruby" != "Python"
1965 | ^
1966 | SyntaxError: invalid syntax
1967 |
1968 | >>> "Ruby" <> "Python"
1969 | True
1970 | ```
1971 |
1972 | 直接看解释吧。
1973 |
1974 | #### 💡 解释:
1975 | - 上面的问题起源自一个已经淘汰的Python修正包[PEP-401](https://www.python.org/dev/peps/pep-0401/),发布于2009年的4月1日(明白为什么它会被淘汰了吧)。
1976 | - 下面引用 PEP-401 的内容
1977 | > 为了让你们认识到在Python3.0中 != 这种逻辑不等式是多么的不好用,FLUFL引用会使解释器禁止这种语法只允许 <> 这种便利的逻辑不等式。
1978 |
1979 | ---
1980 |
1981 | ### ▶ 就算Python也知道爱是个复杂的东西 *
1982 |
1983 | ```py
1984 | import this
1985 | ```
1986 |
1987 | Wait, what's **this**? `this` is love :heart:
1988 | (译注:我认为这句话原汁原味的看才能体会其中的乐趣,就不翻译了😊)
1989 |
1990 | **Output:**
1991 | ```
1992 | The Zen of Python, by Tim Peters
1993 |
1994 | Beautiful is better than ugly.
1995 | Explicit is better than implicit.
1996 | Simple is better than complex.
1997 | Complex is better than complicated.
1998 | Flat is better than nested.
1999 | Sparse is better than dense.
2000 | Readability counts.
2001 | Special cases aren't special enough to break the rules.
2002 | Although practicality beats purity.
2003 | Errors should never pass silently.
2004 | Unless explicitly silenced.
2005 | In the face of ambiguity, refuse the temptation to guess.
2006 | There should be one-- and preferably only one --obvious way to do it.
2007 | Although that way may not be obvious at first unless you're Dutch.
2008 | Now is better than never.
2009 | Although never is often better than *right* now.
2010 | If the implementation is hard to explain, it's a bad idea.
2011 | If the implementation is easy to explain, it may be a good idea.
2012 | Namespaces are one honking great idea -- let's do more of those!
2013 | ```
2014 |
2015 | 这些(指上面输出的内容)就是Python之道!
2016 |
2017 | ```py
2018 | >>> love = this
2019 | >>> this is love
2020 | True
2021 | >>> love is True
2022 | False
2023 | >>> love is False
2024 | False
2025 | >>> love is not True or False
2026 | True
2027 | >>> love is not True or False; love is love # 爱(love)是个复杂的东西,不是吗?
2028 | True
2029 | ```
2030 |
2031 | #### 💡 解释:
2032 |
2033 | * `this`模块是一个针对Python之道做阐述的彩蛋([PEP 20](https://www.python.org/dev/peps/pep-0020))。
2034 | * 但是这并没有完,如果你看看这个模块的实现代码[this.py](https://hg.python.org/cpython/file/c3896275c0f6/Lib/this.py)。你会发现一个更加有趣的事情,那就是这个代码本身就违反了上面输出的Python之道(当然,我觉得这也是唯一一处违反的地方)。
2035 | * 关于`love is not True or False; love is love`这句话,它解释的很对,不是吗?
2036 |
2037 | ---
2038 |
2039 | ### ▶ 这些语句是存在的!
2040 |
2041 | **循环语句对应的`else`语句.** 下面是一个标准的例子:
2042 |
2043 | ```py
2044 | def does_exists_num(l, to_find):
2045 | for num in l:
2046 | if num == to_find:
2047 | print("Exists!")
2048 | break
2049 | else:
2050 | print("Does not exist")
2051 | ```
2052 |
2053 | **Output:**
2054 | ```py
2055 | >>> some_list = [1, 2, 3, 4, 5]
2056 | >>> does_exists_num(some_list, 4)
2057 | Exists!
2058 | >>> does_exists_num(some_list, -1)
2059 | Does not exist
2060 | ```
2061 |
2062 | **异常处理对应的`else`语句** 看例子,
2063 |
2064 | ```py
2065 | try:
2066 | pass
2067 | except:
2068 | print("Exception occurred!!!")
2069 | else:
2070 | print("Try block executed successfully...")
2071 | ```
2072 |
2073 | **Output:**
2074 | ```py
2075 | Try block executed successfully...
2076 | ```
2077 |
2078 | #### 💡 解释:
2079 | - 当一个`else`语句紧跟在一个循环语句后面的时候,只有当循环语句块内有明确使用`break`退出,否则所有循环结束就会执行`else`语句块。
2080 | - 跟在`try`语句块后面的`else`语句,又叫做“完成语句(completion clause)”。意味着如果`try`语句块的内容顺利运行完毕,那么就会进入到`else`语句块。
2081 |
2082 | ---
2083 |
2084 | ### ▶ 无限(Inpinity) *
2085 |
2086 | **Output (Python 3.x):**
2087 | ```py
2088 | >>> infinity = float('infinity')
2089 | >>> hash(infinity)
2090 | 314159
2091 | >>> hash(float('-inf'))
2092 | -314159
2093 | ```
2094 |
2095 | #### 💡 解释:
2096 | - 无限(infinity)的哈希值是 10⁵ x π.
2097 | - 有趣的是, 在Python3中,`float('-inf')`的哈希值是"-10⁵ x π", 但是在Python2中是"-10⁵ x e"(正无穷还是一样的哈希值)。
2098 |
2099 | ---
2100 |
2101 | ### ▶ 被修改的类成员 *
2102 |
2103 | ```py
2104 | class Yo(object):
2105 | def __init__(self):
2106 | self.__honey = True
2107 | self.bitch = True
2108 | ```
2109 |
2110 | **Output:**
2111 | ```py
2112 | >>> Yo().bitch
2113 | True
2114 | >>> Yo().__honey
2115 | AttributeError: 'Yo' object has no attribute '__honey'
2116 | >>> Yo()._Yo__honey
2117 | True
2118 | ```
2119 |
2120 | 为什么非要`Yo()._Yo__honey`才对? 如果有印度朋友他一定会懂这个梗的(好吧,我猜不会有印度人看一个中文的文章,Yo Yo Honey Singh是一个印度著名rapper,这就是梗,也是为什么这个类要起名Yo,哈哈).
2121 |
2122 | #### 💡 解释:
2123 |
2124 | * [名称混淆](https://en.wikipedia.org/wiki/Name_mangling)是一种用来避免命名空间下命名冲突的技术。
2125 | * 在Python里,如果某个类成员的名称以`__`(两个下划线)开头,并且不以两个以上的下划线结尾(这就意味着你想定义一个私有变量,但是Python是没有私有变量的),那么编辑器就会自动在这个成员名称之前加上`_NameOfTheClass`(在这里就是`_Yo`),来防止子类不小心访问修改到这个成员属性。
2126 | * 所以,当要访问`__honey`这个成员属性的时候,我们需要在前面加上`_Yo`来进行访问。
2127 |
2128 | ---
2129 |
2130 | ---
2131 |
2132 | ## 第五章: 杂项
2133 |
2134 |
2135 | ### ▶ `+=`更快
2136 |
2137 | ```py
2138 | # 使用"+"连接三个字符串:
2139 | >>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
2140 | 0.25748300552368164
2141 | # 使用"+="连接三个字符串:
2142 | >>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
2143 | 0.012188911437988281
2144 | ```
2145 |
2146 | #### 💡 解释:
2147 | + 当需要连接的字符串超过两个的时候,使用`+=`会比使用`+`更快。(比如上面的例子`s1 += s2 + s3`中的`s1`),因为第一个字符串不会在做链接的时候销毁重建(因为`+=`操作符是就地操作的,上面多次已经提到过这个符号特性)。
2148 |
2149 | ---
2150 |
2151 | ### ▶ 超长字符串!
2152 |
2153 | ```py
2154 | #使用加号连接字符串
2155 | def add_string_with_plus(iters):
2156 | s = ""
2157 | for i in range(iters):
2158 | s += "xyz"
2159 | assert len(s) == 3*iters
2160 |
2161 | #使用比特流连接字符串
2162 | def add_bytes_with_plus(iters):
2163 | s = b""
2164 | for i in range(iters):
2165 | s += b"xyz"
2166 | assert len(s) == 3*iters
2167 |
2168 | #使用format函数连接字符串
2169 | def add_string_with_format(iters):
2170 | fs = "{}"*iters
2171 | s = fs.format(*(["xyz"]*iters))
2172 | assert len(s) == 3*iters
2173 |
2174 | #使用数组连接字符串
2175 | def add_string_with_join(iters):
2176 | l = []
2177 | for i in range(iters):
2178 | l.append("xyz")
2179 | s = "".join(l)
2180 | assert len(s) == 3*iters
2181 |
2182 | #将列表转换成字符串
2183 | def convert_list_to_string(l, iters):
2184 | s = "".join(l)
2185 | assert len(s) == 3*iters
2186 | ```
2187 |
2188 | **Output:**
2189 | ```py
2190 | >>> timeit(add_string_with_plus(10000))
2191 | 1000 loops, best of 3: 972 µs per loop
2192 | >>> timeit(add_bytes_with_plus(10000))
2193 | 1000 loops, best of 3: 815 µs per loop
2194 | >>> timeit(add_string_with_format(10000))
2195 | 1000 loops, best of 3: 508 µs per loop
2196 | >>> timeit(add_string_with_join(10000))
2197 | 1000 loops, best of 3: 878 µs per loop
2198 | >>> l = ["xyz"]*10000
2199 | >>> timeit(convert_list_to_string(l, 10000))
2200 | 10000 loops, best of 3: 80 µs per loop
2201 | ```
2202 |
2203 | 让我们把循环次数提升十倍。
2204 |
2205 | ```py
2206 | >>> timeit(add_string_with_plus(100000)) # 执行时间线性增加
2207 | 100 loops, best of 3: 9.75 ms per loop
2208 | >>> timeit(add_bytes_with_plus(100000)) # 平方式增加
2209 | 1000 loops, best of 3: 974 ms per loop
2210 | >>> timeit(add_string_with_format(100000)) # 线性增加
2211 | 100 loops, best of 3: 5.25 ms per loop
2212 | >>> timeit(add_string_with_join(100000)) # 线性增加
2213 | 100 loops, best of 3: 9.85 ms per loop
2214 | >>> l = ["xyz"]*100000
2215 | >>> timeit(convert_list_to_string(l, 100000)) # 线性增加
2216 | 1000 loops, best of 3: 723 µs per loop
2217 | ```
2218 |
2219 | #### 💡 解释
2220 | - 关于`timeit`模块的用法可以看[这里](https://docs.python.org/3/library/timeit.html)。这是一个用来测量程序块执行时间的模块。
2221 | - 在Python中,不要试图用`+`来连接一个长字符串,因为`str`这个类型在Python中是一个不可变类型,这就意味着每次两个字符串对象连接程序都需要从左到右把两个字符串的每一个字符都复制一份。比如你现在要连接四个长度为10的字符串,那么程序需要拷贝 (10+10) + ((10+10)+10) + (((10+10)+10)+10) = 90 个字符,而不是40个。而且程序运行时间会随着你需要连接的字符串数量和字符数量呈平方形式的增长。(请参考`add_bytes_with_plus`函数的执行时间对比)。
2222 | - 所以,建议大家使用`.format`或者`%`这种语法(但是,它们在连接短字符串的时候会比`+`号慢一点)。
2223 | - 或者,如果已经提前获得了一个可以迭代的对象,则可以使用`''.join(iterable_object)`这种语法把它连接成字符串,这样的速度会比前几个都快。
2224 | - 至于为什么`add_string_with_plus`不会像`add_bytes_with_plus`着这样成平方形式的提升执行时间,是因为前面例子里已经解释过的`+=`操作符的优化效果。如果把`s += "xyz"`这句话替换成了`s = s + "x" + "y" + "z"`,那么执行时间还是会呈平方形式的增加。
2225 | ```py
2226 | def add_string_with_plus(iters):
2227 | s = ""
2228 | for i in range(iters):
2229 | s = s + "x" + "y" + "z"
2230 | assert len(s) == 3*iters
2231 |
2232 | >>> timeit(add_string_with_plus(10000))
2233 | 100 loops, best of 3: 9.87 ms per loop
2234 | >>> timeit(add_string_with_plus(100000)) # 平方式增加
2235 | 1 loops, best of 3: 1.09 s per loop
2236 | ```
2237 |
2238 | ---
2239 |
2240 | ### ▶ 字符串到浮点数的转换
2241 |
2242 | ```py
2243 | a = float('inf')
2244 | b = float('nan')
2245 | c = float('-iNf') #这些字符串是大小写不敏感的
2246 | d = float('nan')
2247 | ```
2248 |
2249 | **Output:**
2250 | ```py
2251 | >>> a
2252 | inf
2253 | >>> b
2254 | nan
2255 | >>> c
2256 | -inf
2257 | >>> float('some_other_string')
2258 | ValueError: could not convert string to float: some_other_string
2259 | >>> a == -c #inf==inf
2260 | True
2261 | >>> None == None # None==None
2262 | True
2263 | >>> b == d #但是 nan!=nan 为真
2264 | False
2265 | >>> 50/a
2266 | 0.0
2267 | >>> a/a
2268 | nan
2269 | >>> 23 + b
2270 | nan
2271 | ```
2272 |
2273 | #### 💡 解释:
2274 |
2275 | `'inf'`和`'nan'`是一种特殊的字符串(大小写不敏感),它们两个都可以强制转换成`float`浮点类型,并且分别代表了数学意义上的"无限"(infinity)和"非数字"(not a number)。
2276 |
2277 | ---
2278 |
2279 | ### ▶ 最后一些小特点集合
2280 |
2281 | * `join()`是一个字符串的函数而不是一个列表附属函数(第一次用的人可能会觉得有点别扭)
2282 |
2283 | **💡 解释:**
2284 | 如果`join()`作为一个字符串的方法,那么它就可以操作所有的可迭代类型(list, tuple, iterators)。但是如果它作为了一个列表(list)的方法,那它同样的也需要为另外那些可迭代类型构造专属的函数内容。
2285 |
2286 | * 下面这些句子看着奇怪,但是它们确实都是符合语法规则的
2287 | + `[] = ()` 在语法上是正确的 (把一个元组赋值给一个列表)
2288 | + `'a'[0][0][0][0][0]` 同样也是语法正确的,因为在Python中字符串是一个序列(可迭代的对象就可以使用索引值进行访问里面的元素)。
2289 | + `3 --0-- 5 == 8`和`--5 == 5`也都是语法正确的,并且最后结果都为`True`。
2290 |
2291 | * 给定一个数字类型变量`a`,`++a`和`--a`在Python中都是合法的,但是它们表达的意思和在C语言(或者C++,Java)中是完全不一样的。
2292 | ```py
2293 | >>> a = 5
2294 | >>> a
2295 | 5
2296 | >>> ++a
2297 | 5
2298 | >>> --a
2299 | 5
2300 | ```
2301 |
2302 | **💡 解释:**
2303 | + 在Python语法中,是没有`++`这个操作符的。这个仅仅代表两个单独的`+`操作符而已。
2304 | + `++a`在Python中会被解析成`+(+a)`,也就等于`a`。同样的,`--a`也是这种情况。
2305 | + 这篇StackOverflow的[文章](https://stackoverflow.com/questions/3654830/why-are-there-no-and-operators-in-python)讨论了Python中没有自增自减符号背后的逻辑关系。
2306 |
2307 | * 多个Python线程并不会并发执行你的*Python代码*(你没看错,不会!)。也许你想同时开很多个线程然后并发执行你写的Python代码,但是,由于Python中存在一个叫[GIL](https://wiki.python.org/moin/GlobalInterpreterLock)(Global Interpreter Lock)的东西,所以最终你的线程还是会一个接着一个执行。Python的线程执行IO阻塞的任务比较拿手,但是如果想在CPU阻塞的任务中获得真正的并发能力,你需要了解一下Python的[multiprocessing](https://docs.python.org/2/library/multiprocessing.html)模块。
2308 |
2309 | * 列表切片的时候如果索引值超出界限并不会报错
2310 | ```py
2311 | >>> some_list = [1, 2, 3, 4, 5]
2312 | >>> some_list[111:]
2313 | []
2314 | ```
2315 |
2316 | * 在Python3中,`int('١٢٣٤٥٦٧٨٩')`会返回`123456789`。在Python中,十进制字符不仅包含十进制的数字,还包含了大部分可以表示为十进制数字形式的字符,比如,U+0660, ARABIC_INDIC DIGIT ZERO,一种印度阿拉伯语里代表数字0的字符。不过还没有中文的"一,二,三..."或者"壹,贰,叁..."这种,有人说是因为中文在unicode中的编码是不连续的,不过无论如何,现在确实还没有。这里有一篇[小故事](http://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/)是关于这个特性的,英语版,有兴趣的同学可以看看。
2317 |
2318 | * `'abc'.count('') == 4`。 下面模拟实现了一个相似的`count()`方法,
2319 | ```py
2320 | def count(s, sub):
2321 | result = 0
2322 | for i in range(len(s) + 1 - len(sub)):
2323 | result += (s[i:i + len(sub)] == sub)
2324 | return result
2325 | ```
2326 | 产生这个结果的原因是子串为空时会匹配上原始字符串长度为0时的字符(总之,看代码你会懂的)。
2327 |
2328 | ---
2329 |
2330 |
2331 | # 贡献
2332 |
2333 | 欢迎任何补丁和修正!详细信息请看 [CONTRIBUTING.md](./CONTRIBUTING.md)
2334 |
2335 | 如果想要参与讨论, 可以选择创建一个新的[issue](https://github.com/true1023/Crazy-Python/issues) 或者加入我创建的gitter[房间](https://gitter.im/Crazy-Python/Lobby)
2336 |
2337 | # 感谢
2338 |
2339 | 这个仓库翻译自 [What the fu*k Python](https://github.com/satwikkansal/wtfpython),迄今为止没谁需要感谢,感谢下原作者[Satwik Kansal](https://github.com/satwikkansal)吧。
2340 |
2341 |
2342 |
2343 | # :mortar_board: 版权声明
2344 |
2345 | [](http://www.wtfpl.net/)
2346 |
2347 | :copyright: [True1023](https://github.com/true1023/)
2348 |
2349 |
2350 |
2351 | ## 帮助
2352 |
2353 | 如果你有任何好的想法和意见,请务必要分享给我。
2354 |
2355 | ## 关于pdf出版的问题
2356 |
2357 | 我会在收集并修正一些bug后生成pdf版本的Crazy Python,想要的小伙伴可以[发信](mailto:shi12li12@gmail.com)给我,或者关注公众号**编程行动派**,我也会第一时间在公众号里通知大家。
2358 |
2359 | 
2360 |
--------------------------------------------------------------------------------
/individual/total.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | 疯狂的Python! 🐍
4 | 一些有趣的鲜为人知的Python特性集合.
5 |
6 | [](http://www.wtfpl.net/)
7 | [](https://gitter.im/Crazy-Python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
8 |
9 |
10 | *本文翻译自[What the f*ck Python!](https://github.com/satwikkansal/wtfpython)*
11 |
12 | ---
13 |
14 | Python作为一个设计优美的交互式脚本语言,提供了许多人性化的语法。但是也因为这个原因,有些Python的代码片段并不会按照用户想象的那样运行。
15 |
16 | 这篇文章就让我们总结一下那些Python里反直觉的代码片段,并且深入研究一下其中的运行原理。
17 |
18 | 下面的某些例子可能并不像是标题说的那样....嗯....反直觉,但是它们依旧会带你揭示一些你从来没有意识到的Python语言特性。
19 |
20 | 而且,我发现这也是一种很好的学习编程语言的方法,不过前提是你不要认为这篇文章会告诉你一切,抛砖引玉而已。相信我,你需要的知识都隐藏在互联网的某个角落里。
21 |
22 | 如果你已经写了很久的Python代码,你可以把下面的这些例子当做一个挑战,试一试自己能不能在第一次就猜对结果。也许某些例子里的坑你已经遇到过并且解决了,那么再次看见这个坑的时候我想你会为当时自己的努力而自豪的。:sweat_smile:
23 |
24 | 好了,下面是正文!
25 |
26 | 目录
27 |
28 |
29 | - [示例结构说明](#示例结构说明)
30 | - [使用方法](#使用方法)
31 | - [:eyes: 例子](#eyes-例子)
32 | - [第一章: 撕裂大脑](#第一章-撕裂大脑)
33 | - [▶ 善变的字符串 *](#▶-善变的字符串-)
34 | - [▶ 不变的哈希值](#▶-不变的哈希值)
35 | - [▶ 说了要执行就一定会执行!](#▶-说了要执行就一定会执行)
36 | - [▶ 鸠占鹊巢 *](#▶-鸠占鹊巢-)
37 | - [▶ 神奇赋值法](#▶-神奇赋值法)
38 | - [▶ 时间的误会](#▶-时间的误会)
39 | - [▶ 特殊的数字们](#▶-特殊的数字们)
40 | - [▶ 三子棋之一步取胜法](#▶-三子棋之一步取胜法)
41 | - [▶ 没脑子的函数](#▶-没脑子的函数)
42 | - [▶ `is not ...` 并不是 `is (not ...)`](#▶-is-not--并不是--is-not-)
43 | - [▶ 尾部的逗号](#▶-尾部的逗号)
44 | - [▶ 最后一个反斜杠](#▶-最后一个反斜杠)
45 | - [▶ 纠结的not](#▶-纠结的not)
46 | - [▶ 只剩一半的三引号](#▶-只剩一半的三引号)
47 | - [▶ 消失的午夜零点](#▶-消失的午夜零点)
48 | - [▶ 站错队的布尔型](#▶-站错队的布尔型)
49 | - [▶ 类属性与类实例属性](#▶-类属性与类实例属性)
50 | - [▶ None的产生](#▶-none的产生)
51 | - [▶ 不可修改的元组](#▶-不可修改的元组)
52 | - [▶ 消失的变量e](#▶-消失的变量e)
53 | - [▶ 亦真还假](#▶-亦真还假)
54 | - [▶ 转瞬即空](#▶-转瞬即空)
55 | - [▶ 子类的关系 *](#▶-子类的关系-)
56 | - [▶ 神秘的键值转换 *](#▶-神秘的键值转换-)
57 | - [▶ 看你能不能猜到这个的结果?](#▶-看你能不能猜到这个的结果)
58 | - [第二章: 瞒天过海](#第二章-瞒天过海)
59 | - [▶ 无效的一行?](#▶-无效的一行)
60 | - [▶ 移形换位 *](#▶-移形换位-)
61 | - [▶ 到底哪里出错了呢?](#▶-到底哪里出错了呢)
62 | - [第三章: 隐藏的陷阱](#第三章-隐藏的陷阱)
63 | - [▶ 不要在迭代一个字典的时候修改这个字典!](#▶-不要在迭代一个字典的时候修改这个字典)
64 | - [▶ 删不掉的对象 *](#▶-删不掉的对象-)
65 | - [▶ 删除正在迭代中的列表项](#▶-删除正在迭代中的列表项)
66 | - [▶ 被泄露出去的循环变量!](#▶-被泄露出去的循环变量)
67 | - [▶ 小心那些有默认值的可变参数!](#▶-小心那些有默认值的可变参数)
68 | - [▶ 抓住这些异常!](#▶-抓住这些异常)
69 | - [▶ 相同的操作,不同的结果](#▶-相同的操作不同的结果)
70 | - [▶ 作用域之外的变量](#▶-作用域之外的变量)
71 | - [▶ 小心那些链式操作符](#▶-小心那些链式操作符)
72 | - [▶ 被忽略的类变量](#▶-被忽略的类变量)
73 | - [▶ 小心处理元组](#▶-小心处理元组)
74 | - [第四章: 一起来找些有趣的东西!](#第四章-一起来找些有趣的东西)
75 | - [▶ 人生苦短,我用Python *](#▶-人生苦短我用python-)
76 | - [▶ 为什么要用`goto`? *](#▶-为什么要用goto-)
77 | - [▶ 试试用大括号? *](#▶-试试用大括号-)
78 | - [▶ 不等号的争议 *](#▶-不等号的争议-)
79 | - [▶ 就算Python也知道爱是个复杂的东西 *](#▶-就算python也知道爱是个复杂的东西-)
80 | - [▶ 这些语句是存在的!](#▶-这些语句是存在的)
81 | - [▶ 无限(Inpinity) *](#▶-无限inpinity-)
82 | - [▶ 被修改的类成员 *](#▶-被修改的类成员-)
83 | - [第五章: 杂项](#第五章-杂项)
84 | - [▶ `+=`更快](#▶-更快)
85 | - [▶ 超长字符串!](#▶-超长字符串)
86 | - [▶ 字符串到浮点数的转换](#▶-字符串到浮点数的转换)
87 | - [▶ 最后一些小特点集合](#▶-最后一些小特点集合)
88 | - [贡献](#贡献)
89 | - [感谢](#感谢)
90 | - [:mortar_board: 版权声明](#mortar_board-版权声明)
91 | - [帮助](#帮助)
92 | - [关于pdf出版的问题](#关于pdf出版的问题)
93 |
94 |
95 |
96 | # 示例结构说明
97 |
98 |
99 |
100 | 下面是每个例子里通用的结构说明:
101 |
102 | > ### ▶ 这里是例子的标题 *
103 | > 首先是例子的标题,如果某个标题后面带有星号,说明这是一个新加入的例子。
104 | > ```py
105 | > # 第一个代码段里面会有一些初始化代码
106 | > # 为后续的输出代码段做准备...
107 | > ```
108 | >
109 | > **Output (Python version):**
110 | > ```py
111 | > >>> python语句,执行某个命令
112 | > 一些输出(可能你想得到,也可能想不到)
113 | > ```
114 | > (可选): 有可能会说明一下上面输出的内容
115 | >
116 | >
117 | > #### :bulb: 解释:
118 | >
119 | > * 简短的介绍发生了什么和为什么会产生这些输出。
120 | > ```py
121 | > 一些初始化代码
122 | > ```
123 | > **Output:**
124 | > ```py
125 | > >>> 执行代码 # 这些代码会展示为何会有上方那些输出内容
126 | > ```
127 |
128 | **注意:** 所有的代码都是在 Python 3.5.2 环境下测试通过,理论上如果没有特殊声明,可以在所有的Python版本下运行。
129 |
130 | # 使用方法
131 |
132 | 在我看来,为了充分的利用这个仓库里的所有例子,最好的办法就是按照顺序把每个例子挨个看一遍:
133 | - 仔细阅读每个例子的初始化代码。如果你是一个经验丰富的Python程序员,那么大部分时候你都可以知道初始化代码执行后具体会发生什么。
134 | - 阅读输出结果并且,
135 | + 检查输出结果是否和你想的一样
136 | + 确认你是否知道产生这种结果背后的原理,
137 | - 如果不知道,那么请仔细阅读解释章节(如果看完解释还是不懂的话,那就提交一个 [issue](https://github.com/true1023/Crazy-Python/issues) 吧)
138 | - 如果知道,那么给就自己点个赞,继续看下一个例子
139 |
140 | # :eyes: 例子
141 |
142 | ## 第一章: 撕裂大脑
143 |
144 | ### ▶ 善变的字符串 *
145 |
146 | 1\.
147 | ```py
148 | >>> a = "crazy_python"
149 | >>> id(a)
150 | 2387669241224
151 | >>> id("crazy" + "_" + "python") # 注意这两个字符串的id号是一样的
152 | 2387669241224
153 | ```
154 |
155 | 2\.
156 | ```py
157 | >>> a = "crazy"
158 | >>> b = "crazy"
159 | >>> a is b
160 | True
161 |
162 | >>> a = "crazy!"
163 | >>> b = "crazy!"
164 | >>> a is b
165 | False
166 |
167 | >>> a, b = "crazy!", "crazy!"
168 | >>> a is b
169 | True
170 | ```
171 |
172 | 3\.
173 | ```py
174 | >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
175 | True
176 | >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
177 | False
178 | ```
179 |
180 | 很不可思议,对吧?
181 |
182 | #### :bulb: 解释:
183 | + 上面这种特性是CPython的一种编译器优化技术(叫做字符串驻留技术)。就是如果将要创建的字符串在之前已经创建过并且驻留在了内存没有释放,那么CPython不会创建一个新的实例,而是会把指针指向已经存在于内存的老的字符串实例。
184 | + 如果一个字符串实例已经驻留在了内存中,那么后续所有跟它值一样的变量都可以将指针指向这个内存中的字符串实例(这样就会节省内存空间)
185 | + 在上面这几段程序代码中,有几段的字符串明显驻留在了内存中供多个变量引用。 决定一个字符串是否会驻留在内存中是由这个字符串的实现方法决定的。下面是一些判断字符串变量是否会驻留内存的方法:
186 | * 所有长度为1或者0的字符串,全部会驻留
187 | * 编译阶段(也就是把源代码编译成.pyc文件的阶段)的字符串被驻留在内存,运行阶段就不会(`'crazy'`会驻留在内存,但是`''.join(['c','r','a','z','y'])`就不会)
188 | * 如果字符串包含除了ASCII字符,数字和下划线以外的字符,那么这个字符串就不会驻留内存。这就是为什么`'crazy!'`赋值给`a`,`b`的时候得到的结果是False,因为`!`感叹号。CPython中对这个特点的具体实现请参考[这里](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)
189 |
190 | + 当变量`a`和`b`在同一行赋值`"crazy!"`的时候,Python的解释器会创建一个新的字符串实例,然后把这两个变量同时指向这一个实例。但是如果你用两行实现赋值的话,Python解释器就不会知道已经有一个`'crazy!'`的字符串实例存在了(因为根据上面的规则,`'crazy!'`实例不会进行内存驻留供后面的语句引用)。这是一种专门在交互环境下编译器的一种优化方法。
191 | + 常量折叠是Python实现的一种[窥孔优化(Peephole optimization)](https://baike.baidu.com/item/%E7%AA%A5%E5%AD%94%E4%BC%98%E5%8C%96)技术。意思就是`'a'*20`这个语句在编译的时候会自动替换成`'aaaaaaaaaaaaaaaaaaaa'`这个变量,用来减少运行时的运算时钟周期(`'a'*20`需要多执行20次乘法运算)。常量折叠只在字符串长度小于等于20的时候发生(至于为什么?想想如果有一个`'a*10**10'`这样的语句,折叠后需要多大一个`.pyc`才能存下折叠后的字符串啊)。[这里](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288)是实现这种技术的实现代码。
192 |
193 | ---
194 |
195 |
196 | ### ▶ 不变的哈希值
197 |
198 | 1\.
199 | ```py
200 | some_dict = {}
201 | some_dict[5.5] = "Ruby"
202 | some_dict[5.0] = "JavaScript"
203 | some_dict[5] = "Python"
204 | ```
205 |
206 | **Output:**
207 | ```py
208 | >>> some_dict[5.5]
209 | "Ruby"
210 | >>> some_dict[5.0]
211 | "Python"
212 | >>> some_dict[5]
213 | "Python"
214 | ```
215 | "Python" 把之前的 "JavaScript" 覆盖掉了吗?
216 |
217 | #### :bulb: 解释
218 |
219 | * Python的字典结构是根据key值的哈希值判断两个key值是否相等的
220 | * 在Python中,不变对象(Immutable objects)的值如果一样,那么它们的哈希值肯定也一样
221 | ```py
222 | >>> 5 == 5.0
223 | True
224 | >>> hash(5) == hash(5.0)
225 | True
226 | ```
227 | **注意:** 有些对象有不同的值,但是它们的哈希值也有可能是一样的(所谓的哈希冲突)
228 | * 当`some_dict[5] = "Python"`这句话执行的时候, "Python"这个字符串就会覆盖掉"JavaScript"这个值,因为在Python看来,`5`和`5.0`的哈希值是一样的,也就是说对于字典结构他们对应的是一个key值。
229 | * 在 StackOverflow 上面有一个[回答](https://stackoverflow.com/a/32211042/4354153)对Python的这个特性解释的很棒。
230 |
231 | ---
232 |
233 | ### ▶ 说了要执行就一定会执行!
234 |
235 | ```py
236 | def some_func():
237 | try:
238 | return 'from_try'
239 | finally:
240 | return 'from_finally'
241 | ```
242 |
243 | **Output:**
244 | ```py
245 | >>> some_func()
246 | 'from_finally'
247 | ```
248 |
249 | #### 💡 解释:
250 |
251 | - 当在`try`语句块中遇到`return`,`break`或者`continue`的时候,如果是"try...finlly"语句块,那么在执行完`try`语句块里的内容后,依然会执行`finally`语句块的内容。
252 | - 当`return`语句返回一个值的时候,那么因为在`finally`语句块中的`return`语句是最后执行的,那么返回的值就永远都是`finally`语句块中`return`语句返回的值。
253 |
254 | ---
255 |
256 | ### ▶ 鸠占鹊巢 *
257 |
258 | ```py
259 | class Crazy:
260 | pass
261 | ```
262 |
263 | **Output:**
264 | ```py
265 | >>> Crazy() == Crazy() # 两个类实例是不同的
266 | False
267 | >>> Crazy() is Crazy() # 它们的id号也是不一样的
268 | False
269 | >>> hash(Crazy()) == hash(Crazy()) # 它们的哈希值按说也应该不一样
270 | True
271 | >>> id(Crazy()) == id(Crazy())
272 | True
273 | ```
274 |
275 | #### :bulb: 解释:
276 |
277 | * 当`id`函数被调用的时候,Python创建了一个`Crazy`类实例,然后把这个实例传给了`id`函数。然后`id`函数返回这个实例的"id"号(实际上就是这个实例在内存中的地址),接着这个实例就被丢弃并且销毁了。
278 | * 当我们紧接着再做一遍上面的步骤的时候,Python会把同一块内存空间分配给第二次创建的`Crazy`实例。又因为在CPython中`id`函数使用的是内存地址作为返回值,所以就会出现两个对象实例的id号相同的情况了。
279 | * 所以,"对象的id是唯一的"这句话有一个前提条件是"在这个对象的生命周期内"。当这个对象在内存被销毁以后,其他的对象就可以占用它之前所用的内存空间产生一样的id号。
280 | * 但是为什么上面的例子里`is`操作符却产生了`False`? 我们再看一个例子。
281 | ```py
282 | class Crazy(object):
283 | def __init__(self): print("I")
284 | def __del__(self): print("D")
285 | ```
286 |
287 | **Output:**
288 | ```py
289 | >>> Crazy() is Crazy()
290 | I
291 | I
292 | D
293 | D
294 | False
295 | >>> id(Crazy()) == id(Crazy())
296 | I
297 | D
298 | I
299 | D
300 | True
301 | ```
302 | 现在你可以发现, 不同的调用方法会对实例销毁的时间产生不同的影响。
303 |
304 | ---
305 |
306 | ### ▶ 神奇赋值法
307 |
308 | ```py
309 | some_string = "crazy"
310 | some_dict = {}
311 | for i, some_dict[i] in enumerate(some_string):
312 | pass
313 | ```
314 |
315 | **Output:**
316 | ```py
317 | >>> some_dict # 一个带引索的字典被创建.
318 | {0: 'c', 1: 'r', 2: 'a', 3: 'z', 4: 'y'}
319 | ```
320 |
321 | #### :bulb: 解释:
322 |
323 | * 一个 `for` 语句在[Python语法](https://docs.python.org/3/reference/grammar.html)中是这么定义的:
324 | ```
325 | for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
326 | ```
327 | `exprlist` 是一组被赋值的变量. 这就等于说这组变量在**每次迭代**开始的时候都会执行一次 `{exprlist} = {next_value}` 。
328 | 下面这个例子很好的解释了上面想要表达的意思:
329 | ```py
330 | for i in range(4):
331 | print(i)
332 | i = 10
333 | ```
334 |
335 | **Output:**
336 | ```
337 | 0
338 | 1
339 | 2
340 | 3
341 | ```
342 |
343 | 是不是以为上面的循环就会执行一次?
344 |
345 | **:bulb: 解释:**
346 | - 在上面这个循环中,`i=10`这个赋值语句不会整个迭代过程产生任何影响。因为在每次迭代开始前,迭代函数(在这里是`range(4)`)都会把下一次的值赋值给目标变量(在这里是`i`)。
347 |
348 | * 再来看上面的例子,`enumerate(some_string)`这个函数会在每次迭代的时候产生两个值,分别是`i`(一个从0开始的索引值)和一个字符(来自`some_string`的值)。然后这两个值会分别赋值给`i`和`some_dict[i]`。把刚才的循环展开来看就像是下面这样:
349 | ```py
350 | >>> i, some_dict[i] = (0, 'c')
351 | >>> i, some_dict[i] = (1, 'r')
352 | >>> i, some_dict[i] = (2, 'a')
353 | >>> i, some_dict[i] = (3, 'z')
354 | >>> i, some_dict[i] = (4, 'y')
355 | >>> some_dict
356 | ```
357 |
358 | ---
359 |
360 | ### ▶ 时间的误会
361 |
362 | 1\.
363 | ```py
364 | array = [1, 8, 15]
365 | g = (x for x in array if array.count(x) > 0)
366 | array = [2, 8, 22]
367 | ```
368 |
369 | **Output:**
370 | ```py
371 | >>> print(list(g))
372 | [8]
373 | ```
374 |
375 | 2\.
376 |
377 | ```py
378 | array_1 = [1,2,3,4]
379 | g1 = (x for x in array_1)
380 | array_1 = [1,2,3,4,5]
381 |
382 | array_2 = [1,2,3,4]
383 | g2 = (x for x in array_2)
384 | array_2[:] = [1,2,3,4,5]
385 | ```
386 |
387 | **Output:**
388 | ```py
389 | >>> print(list(g1))
390 | [1,2,3,4]
391 |
392 | >>> print(list(g2))
393 | [1,2,3,4,5]
394 | ```
395 |
396 | #### :bulb: 解释
397 |
398 | - 在[生成器](https://wiki.python.org/moin/Generators)表达式中,`in`语句会在声明阶段求值,但是条件判断语句(在这里是`array.count(x) > 0`)会在真正的运行阶段(runtime)求值。
399 | - 在生成器运行之前,`array`已经被重新赋值为`[2, 8, 22]`了,所以这个时候再用`count`函数判断`2`,`8`,`22`在原列表中的数量,只有`8`是数量大于`0`的,所以最后这个生成器只返回了一个`8`是符合条件的。
400 | - 在第二部分中,`g1`,`g2`输出结果不同,是因为对`array_1`和`array_2`的赋值方法不同导致的。
401 | - 在第一个例子中, `array_1`绑定了一个新的列表对象`[1,2,3,4,5]`(可以理解成`array_1`的指针指向了一个新的内存地址),而且在这之前,`in`语句已经在声明时就为`g1`绑定好了旧的列表对象`[1,2,3,4]`(这个就对象也没有随着新对象的赋值而销毁)。所以这时候`g1`和`array_1`是指向不同的对象地址的。
402 | - 在第二个例子中,由于切片化的赋值,`array_2`并没有绑定(指向)新对象,而是将旧的对象`[1,2,3,4]`更新(也就是说旧对象的内存地址被新对象占用了)成了新的对象`[1,2,3,4,5]`。所以,`g2`和`array_2`依旧同时指向一个地址,所以都更新成了新对象`[1,2,3,4,5]`。
403 |
404 | ---
405 |
406 | ### ▶ 特殊的数字们
407 |
408 | 下面这个例子在网上非常的流行。
409 |
410 | ```py
411 | >>> a = 256
412 | >>> b = 256
413 | >>> a is b
414 | True
415 |
416 | >>> a = 257
417 | >>> b = 257
418 | >>> a is b
419 | False
420 |
421 | >>> a = 257; b = 257
422 | >>> a is b
423 | True
424 | ```
425 |
426 | #### :bulb: 解释:
427 |
428 | **`is`和`==`的区别**
429 |
430 | * `is` 操作符会检查两边的操作数是否引用的是同一个对象(也就是说,会检查两个操作数的id号是否匹配)。
431 | * `==` 操作符会比较两个操作数的值是否一样。
432 | * 所以说`is`是比较引用地址是否相同,`==`是比较值是否相同。下面的例子解释的比较清楚,
433 | ```py
434 | >>> [] == []
435 | True
436 | >>> [] is [] # 这里的两个空list分配了不同的内存地址
437 | False
438 | ```
439 |
440 | **`256` 是一个已经存在于内存的对象 但是 `257` 不是**
441 |
442 | 当你启动一个Python解释器的时候,数字`-5`到`256`就会自动加载进内存。 这些数字都是一些比较常用的数字,所以Python解释器会把他们提前准备好以备以后使用。
443 |
444 | 下面这段话摘抄自 https://docs.python.org/3/c-api/long.html (已经翻译为中文)
445 | > The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behavior of Python, in this case, is undefined.:-)
446 | >
447 | > 当前的实现方法是,维护一个从-5到256的整数数组,当你使用其中某一个数字的时候,系统会自动为你引用到已经存在的对象上去。我认为应该让它可以改变数字1的值。不过就现在来说,Python还没有这个功能。:-)
448 |
449 | ```py
450 | >>> id(256)
451 | 10922528
452 | >>> a = 256
453 | >>> b = 256
454 | >>> id(a)
455 | 10922528
456 | >>> id(b)
457 | 10922528
458 | >>> id(257)
459 | 140084850247312
460 | >>> x = 257
461 | >>> y = 257
462 | >>> id(x)
463 | 140084850247440
464 | >>> id(y)
465 | 140084850247344
466 | ```
467 |
468 | Python解释器在执行`y = 257`的时候还不能意识到我们之前已经创建过了一个值为`257`的对象,所以它又在内存创建了一个新的对象。
469 |
470 | **当`a`和`b`变量在同一行赋值并且所赋值相等时,它们会引用到同一个对象**
471 |
472 | ```py
473 | >>> a, b = 257, 257
474 | >>> id(a)
475 | 140640774013296
476 | >>> id(b)
477 | 140640774013296
478 | >>> a = 257
479 | >>> b = 257
480 | >>> id(a)
481 | 140640774013392
482 | >>> id(b)
483 | 140640774013488
484 | ```
485 |
486 | * 当a和b在同一行被赋值为`257`的时候, Python解释器会创建一个新的对象,然后把这两个变量同时引用到这个新的对象。如果你分开两行赋值两个变量,那么解释器不会“知道”之前自己已经创建过一个同样的`257`对象了。
487 | * 这是一种专门针对交互式解释器环境的优化机制。 当你在控制面板中敲入两行命令的时候,这两行命令是分开编译的,所以他们也会单独进行优化。如果你准备把这个例子(两行分别赋值的例子)写进`.py`文件然后进行测试,那么你会发现结果跟写在一行是一样的,因为文件里的代码是一次性编译的。
488 |
489 | ---
490 |
491 | ### ▶ 三子棋之一步取胜法
492 |
493 | ```py
494 | # 首先先来初始化一个1*3的一维数组
495 | row = [""]*3 #row i['', '', '']
496 | # 然后再用二维数组模拟一个3*3的棋盘
497 | board = [row]*3
498 | ```
499 |
500 | **Output:**
501 | ```py
502 | >>> board
503 | [['', '', ''], ['', '', ''], ['', '', '']]
504 | >>> board[0]
505 | ['', '', '']
506 | >>> board[0][0]
507 | ''
508 | >>> board[0][0] = "X"
509 | >>> board
510 | [['X', '', ''], ['X', '', ''], ['X', '', '']]
511 | ```
512 |
513 | 我们只赋值了一个“X”为什么会出来三个呢?
514 |
515 | #### :bulb: 解释:
516 |
517 | 当我们初始化`row`变量的时候,下图显示的是内存中的变化
518 |
519 | 
520 |
521 | 接着当变量`board`通过`[row]*3`初始化后,下图显示了内存的变化(其实最终每一个变量`board[0]`,`board[1]`,`board[2]`都引用了同一个`row`对象的内存地址)
522 |
523 | 
524 |
525 | 我们可以通过不使用`row`变量来阻止这种情况的发生
526 |
527 | ```py
528 | >>> board = [['']*3 for _ in range(3)]
529 | >>> board[0][0] = "X"
530 | >>> board
531 | [['X', '', ''], ['', '', ''], ['', '', '']]
532 | ```
533 |
534 | ---
535 |
536 | ### ▶ 没脑子的函数
537 |
538 | ```py
539 | funcs = []
540 | results = []
541 | for x in range(7):
542 | def some_func():
543 | return x
544 | funcs.append(some_func)
545 | results.append(some_func())
546 |
547 | funcs_results = [func() for func in funcs]
548 | ```
549 |
550 | **Output:**
551 | ```py
552 | >>> results
553 | [0, 1, 2, 3, 4, 5, 6]
554 | >>> funcs_results
555 | [6, 6, 6, 6, 6, 6, 6]
556 | ```
557 | 虽然我们每次把`some_func`函数加入到`funcs`列表里的时候`x`都不一样,但是`funcs`列表里的所有函数都返回了6.
558 |
559 | //下面这段代码也是这样
560 |
561 | ```py
562 | >>> powers_of_x = [lambda x: x**i for i in range(10)]
563 | >>> [f(2) for f in powers_of_x]
564 | [512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
565 | ```
566 |
567 | #### :bulb: 解释
568 |
569 | - 当我们在一个循环中定义一个函数,并且在函数体中用了循环中的变量时,这个函数只会绑定这个变量本身,并不会绑定当前变量循环到的值。所以最终所有在循环中定义的函数都会使用循环变量最后的值做计算。
570 |
571 | - 如果你想实现心中想的那种效果,可以把循环变量当做一个参数传递进函数体。**为什么这样可以呢?**因为这样在函数作用域内会重新定义一个变量,不是循环里面的那个变量了。
572 |
573 | ```py
574 | funcs = []
575 | for x in range(7):
576 | def some_func(x=x):
577 | return x
578 | funcs.append(some_func)
579 | ```
580 |
581 | **Output:**
582 | ```py
583 | >>> funcs_results = [func() for func in funcs]
584 | >>> funcs_results
585 | [0, 1, 2, 3, 4, 5, 6]
586 | ```
587 |
588 | ---
589 |
590 | ### ▶ `is not ...` 并不是 `is (not ...)`
591 |
592 | ```py
593 | >>> 'something' is not None
594 | True
595 | >>> 'something' is (not None)
596 | False
597 | ```
598 |
599 | #### :bulb: 解释
600 |
601 | - `is not` 是一个单独的二元运算符, 和分开使用的`is`和`not`作用是不同的。
602 | - `is not` 只有在两边的操作数相同时(id相同)结果才为`False`,否则为`True`
603 |
604 | ---
605 |
606 | ### ▶ 尾部的逗号
607 |
608 | **Output:**
609 | ```py
610 | >>> def f(x, y,):
611 | ... print(x, y)
612 | ...
613 | >>> def g(x=4, y=5,):
614 | ... print(x, y)
615 | ...
616 | >>> def h(x, **kwargs,):
617 | File "", line 1
618 | def h(x, **kwargs,):
619 | ^
620 | SyntaxError: invalid syntax
621 | >>> def h(*args,):
622 | File "", line 1
623 | def h(*args,):
624 | ^
625 | SyntaxError: invalid syntax
626 | ```
627 |
628 | #### :bulb: 解释:
629 |
630 | - 末尾的逗号在函数参数列表最后并不总是合法的
631 | - 在Python中,参数列表里,有一部分使用前导逗号分隔的,有一部分是用后导逗号分隔的(比如`**kwargs`这种参数用前导逗号分隔,正常参数`x`用后导逗号分隔)。而这种情况就会导致有些参数列表里的逗号前后都没有用到,就会产生冲突导致编译失败。
632 | - **注意** 这种尾部逗号的问题已经在Python 3.6中被[修复](https://bugs.python.org/issue9232)了。然后[这里](https://bugs.python.org/issue9232#msg248399)有对各种尾部逗号用法的讨论。
633 |
634 | ---
635 |
636 | ### ▶ 最后一个反斜杠
637 |
638 | **Output:**
639 | ```
640 | >>> print("\\ C:\\")
641 | \ C:\
642 | >>> print(r"\ C:")
643 | \ C:
644 | >>> print(r"\ C:\")
645 |
646 | File "", line 1
647 | print(r"\ C:\")
648 | ^
649 | SyntaxError: EOL while scanning string literal
650 | ```
651 |
652 | #### :bulb: 解释
653 |
654 | - 如果字符串前面声明了`r`,说明后面紧跟着的是一个原始字符串,反斜杠在这种字符串中是没有特殊意义的
655 | ```py
656 | >>> print(repr(r"craz\"y"))
657 | 'craz\\"y'
658 | ```
659 | - 解释器实际上是怎么做的呢,虽然看起来仅仅是改变了反斜杠的转义特性,实际上,它(反斜杠)会把自己和紧跟着自己的下一个字符一起传入到解释器,用来供解释器做判断和转换。这也就是为什么当反斜杠在最后一个字符的时候会报错。
660 |
661 | ---
662 |
663 | ### ▶ 纠结的not
664 |
665 | ```py
666 | x = True
667 | y = False
668 | ```
669 |
670 | **Output:**
671 | ```py
672 | >>> not x == y
673 | True
674 | >>> x == not y
675 | File "", line 1
676 | x == not y
677 | ^
678 | SyntaxError: invalid syntax
679 | ```
680 |
681 | #### :bulb: 解释:
682 |
683 | * 操作符的优先级会影响表达式的计算顺序,并且在Python里,`==`操作符的优先级要高于`not`操作符。
684 | * 所以`not x == y`等于 `not (x == y)`,又等于`not (True == False)`,最终计算结果就会是`True`。
685 | * 但是`x == not y`会报错是因为这个表达式可以等价于`(x == not) y`,而不是我们第一眼认为的`x == (not y)`。
686 |
687 | ---
688 |
689 | ### ▶ 只剩一半的三引号
690 |
691 | **Output:**
692 | ```py
693 | >>> print('crazypython''')
694 | wtfpython
695 | >>> print("crazypython""")
696 | wtfpython
697 | >>> # 下面的语句将会产生语法错误
698 | >>> # print('''crazypython')
699 | >>> # print("""crazypython")
700 | ```
701 |
702 | #### :bulb: 解释:
703 | + Python支持隐试的[字符串连接](https://docs.python.org/2/reference/lexical_analysis.html#string-literal-concatenation),比如下面这样,
704 | ```
705 | >>> print("crazy" "python")
706 | crazypython
707 | >>> print("crazy" "") # or "crazy"""
708 | crazy
709 | ```
710 | + 在Python中,`'''` 和 `"""` 也是一种字符串界定符,所以如果Python解释器发现了其中一个,那么就会一直在后面找对称的另一个界定符,这也就是为什么上面例子里注释掉的语句会有语法错误,因为解释器在后面找不到和前面`'''`或`"""`配对的界定符。
711 |
712 | ---
713 |
714 | ### ▶ 消失的午夜零点
715 |
716 | ```py
717 | from datetime import datetime
718 |
719 | midnight = datetime(2018, 1, 1, 0, 0)
720 | midnight_time = midnight.time()
721 |
722 | noon = datetime(2018, 1, 1, 12, 0)
723 | noon_time = noon.time()
724 |
725 | if midnight_time:
726 | print("Time at midnight is", midnight_time)
727 |
728 | if noon_time:
729 | print("Time at noon is", noon_time)
730 | ```
731 |
732 | **Output:**
733 | ```sh
734 | ('Time at noon is', datetime.time(12, 0))
735 | ```
736 | 午夜时间并没有被打印出来
737 |
738 | #### :bulb: 解释:
739 |
740 | 在Python 3.5以前, 对于被赋值为UTC零点的`datetime.time`对象的布尔值,会被认为是`False`。这是一个在用`if obj:`这种语句的时候经常会忽略的特性,所以我们在写这种`if`语句的时候,要注意判断`obj`是否等于`null`或者空。
741 |
742 | ---
743 |
744 | ### ▶ 站错队的布尔型
745 |
746 | 1\.
747 | ```py
748 | # 一个计算列表里布尔型和Int型数量的例子
749 | mixed_list = [False, 1.0, "some_string", 3, True, [], False]
750 | integers_found_so_far = 0
751 | booleans_found_so_far = 0
752 |
753 | for item in mixed_list:
754 | if isinstance(item, int):
755 | integers_found_so_far += 1
756 | elif isinstance(item, bool):
757 | booleans_found_so_far += 1
758 | ```
759 |
760 | **Output:**
761 | ```py
762 | >>> booleans_found_so_far
763 | 0
764 | >>> integers_found_so_far
765 | 4
766 | ```
767 |
768 | 2\.
769 | ```py
770 | another_dict = {}
771 | another_dict[True] = "JavaScript"
772 | another_dict[1] = "Ruby"
773 | another_dict[1.0] = "Python"
774 | ```
775 |
776 | **Output:**
777 | ```py
778 | >>> another_dict[True]
779 | "Python"
780 | ```
781 |
782 | 3\.
783 | ```py
784 | >>> some_bool = True
785 | >>> "crazy"*some_bool
786 | 'crazy'
787 | >>> some_bool = False
788 | >>> "crazy"*some_bool
789 | ''
790 | ```
791 |
792 | #### :bulb: 解释:
793 |
794 | * 布尔型(Booleans)是 `int`类型的一个子类型(bool is instance of int in Python)
795 | ```py
796 | >>> isinstance(True, int)
797 | True
798 | >>> isinstance(False, int)
799 | True
800 | ```
801 |
802 | * `True`的整形值是`1`,`False`的整形值是`0`
803 | ```py
804 | >>> True == 1 == 1.0 and False == 0 == 0.0
805 | True
806 | ```
807 |
808 | * StackOverFlow有针对这个问题背后原理的[解答](https://stackoverflow.com/a/8169049/4354153)。
809 |
810 | ---
811 |
812 | ### ▶ 类属性与类实例属性
813 |
814 | 1\.
815 | ```py
816 | class A:
817 | x = 1
818 |
819 | class B(A):
820 | pass
821 |
822 | class C(A):
823 | pass
824 | ```
825 |
826 | **Ouptut:**
827 | ```py
828 | >>> A.x, B.x, C.x
829 | (1, 1, 1)
830 | >>> B.x = 2
831 | >>> A.x, B.x, C.x
832 | (1, 2, 1)
833 | >>> A.x = 3
834 | >>> A.x, B.x, C.x
835 | (3, 2, 3)
836 | >>> a = A()
837 | >>> a.x, A.x
838 | (3, 3)
839 | >>> a.x += 1
840 | >>> a.x, A.x
841 | (4, 3)
842 | ```
843 |
844 | 2\.
845 | ```py
846 | class SomeClass:
847 | some_var = 15
848 | some_list = [5]
849 | another_list = [5]
850 | def __init__(self, x):
851 | self.some_var = x + 1
852 | self.some_list = self.some_list + [x]
853 | self.another_list += [x]
854 | ```
855 |
856 | **Output:**
857 |
858 | ```py
859 | >>> some_obj = SomeClass(420)
860 | >>> some_obj.some_list
861 | [5, 420]
862 | >>> some_obj.another_list
863 | [5, 420]
864 | >>> another_obj = SomeClass(111)
865 | >>> another_obj.some_list
866 | [5, 111]
867 | >>> another_obj.another_list
868 | [5, 420, 111]
869 | >>> another_obj.another_list is SomeClass.another_list
870 | True
871 | >>> another_obj.another_list is some_obj.another_list
872 | True
873 | ```
874 |
875 | #### :bulb: 解释:
876 |
877 | * 类里的属性和类实例中的属性是作为一个字典列表对象进行处理的。如果在当前类中没有找到需要调用的属性名,那么就会递归去父类中寻找。
878 | * `+=`操作符会在原有易变类型对象的基础上做改变而不会重新创建一个新的对象。所以在一个类实例中修改属性值,会影响到所有调用这个属性的相关类实例和类。
879 |
880 | ---
881 |
882 | ### ▶ None的产生
883 |
884 | ```py
885 | some_iterable = ('a', 'b')
886 |
887 | def some_func(val):
888 | return "something"
889 | ```
890 |
891 | **Output:**
892 | ```py
893 | >>> [x for x in some_iterable]
894 | ['a', 'b']
895 | >>> [(yield x) for x in some_iterable]
896 | at 0x7f70b0a4ad58>
897 | >>> list([(yield x) for x in some_iterable])
898 | ['a', 'b']
899 | >>> list((yield x) for x in some_iterable)
900 | ['a', None, 'b', None]
901 | >>> list(some_func((yield x)) for x in some_iterable)
902 | ['a', 'something', 'b', 'something']
903 | ```
904 |
905 | #### :bulb: 解释:
906 | - 这是一个CPython中`yield`关键字在列表构造和生成器表达式中使用的bug。已经在Python3.8中修复了。在Python3.7中也会警告过时。具体可以看Python的这篇[bug report](https://bugs.python.org/issue10544)
907 | ---
908 |
909 | ### ▶ 不可修改的元组
910 |
911 | ```py
912 | some_tuple = ("A", "tuple", "with", "values")
913 | another_tuple = ([1, 2], [3, 4], [5, 6])
914 | ```
915 |
916 | **Output:**
917 | ```py
918 | >>> some_tuple[2] = "change this"
919 | TypeError: 'tuple' object does not support item assignment
920 | >>> another_tuple[2].append(1000) #这里并没有报错
921 | >>> another_tuple
922 | ([1, 2], [3, 4], [5, 6, 1000])
923 | >>> another_tuple[2] += [99, 999]
924 | TypeError: 'tuple' object does not support item assignment
925 | >>> another_tuple
926 | ([1, 2], [3, 4], [5, 6, 1000, 99, 999])
927 | ```
928 |
929 | 我还以为元组是不可以修改的呢......
930 |
931 | #### :bulb: 解释:
932 |
933 | * 引用自 https://docs.python.org/2/reference/datamodel.html
934 | > 不可变序列
935 | 一个不可变序列对象在第一次定义后就不再可以修改。(如果这个对象中包含有对其他对象的引用,这些其他对象有可能是可变的或者可修改的;总之,只有直接引用的对象是不可以修改的。)
936 | * `+=`操作符会对左侧操作对象本身进行修改。但是元组是不允许修改的,所以会报错,但是实际上修改的是一个可变的列表(list),元组里保存的只是这个列表的地址,所以最终还是会修改成功。
937 |
938 | ---
939 |
940 | ### ▶ 消失的变量e
941 |
942 | ```py
943 | e = 7
944 | try:
945 | raise Exception()
946 | except Exception as e:
947 | pass
948 | ```
949 |
950 | **Output (Python 2.x):**
951 | ```py
952 | >>> print(e)
953 | # 什么都没有打印
954 | ```
955 |
956 | **Output (Python 3.x):**
957 | ```py
958 | >>> print(e)
959 | NameError: name 'e' is not defined
960 | ```
961 |
962 | #### :bulb: 解释:
963 |
964 | * 参考自: https://docs.python.org/3/reference/compound_stmts.html#except
965 |
966 | 当一个异常(exception)被用`as`赋值给一个变量后,这个变量将会在`except`语句块结束后清除。看下面这段
967 |
968 | ```py
969 | except E as N:
970 | foo
971 | ```
972 |
973 | 相当于这样
974 |
975 | ```py
976 | except E as N:
977 | try:
978 | foo
979 | finally:
980 | del N
981 | ```
982 |
983 | 这意味着你最好不要在外面定义一个和"N"重名的变量。这些被赋值的异常变量被清除是因为,Python会将这些变量加入一个异常回溯栈中(traceback,仔细观察每次程序出错控制台输出的错误信息就是从tranceback这个堆栈里面以此取出来的),记录这些异常的具体位置信息并且一直保持这个堆栈的信息直到下一次垃圾回收开始。
984 |
985 | * 这些Python的子句并没有独立的作用域,上面所有的例子都在一个作用域里,所以当变量`e`在`except`子句里面被移除后,就相当于在整个作用域都消失了。但是在函数中情况就和上面不一样了,因为Python函数有自己的“内部”作用域。下面这个例子将说明这种情况:
986 |
987 | ```py
988 | def f(x):
989 | del(x)
990 | print(x)
991 |
992 | x = 5
993 | y = [5, 4, 3]
994 | ```
995 |
996 | **Output:**
997 | ```py
998 | >>>f(x)
999 | UnboundLocalError: local variable 'x' referenced before assignment
1000 | >>>f(y)
1001 | UnboundLocalError: local variable 'x' referenced before assignment
1002 | >>> x
1003 | 5
1004 | >>> y
1005 | [5, 4, 3]
1006 | ```
1007 |
1008 | * 在 Python 2.x 版本里面, 变量`e`会被赋值`Exception()`实例,所以当打印的时候什么都不会打印出来。
1009 |
1010 | **Output (Python 2.x):**
1011 | ```py
1012 | >>> e
1013 | Exception()
1014 | >>> print e
1015 | # 什么都没有打印出来
1016 | ```
1017 |
1018 | ---
1019 |
1020 | ### ▶ 亦真还假
1021 |
1022 | ```py
1023 | True = False
1024 | if True == False:
1025 | print("It's true of false?")
1026 | ```
1027 |
1028 | **Output:**
1029 | ```
1030 | It's true of false?
1031 | ```
1032 |
1033 | #### :bulb: 解释:
1034 |
1035 | - 一开始, Python是没有`bool`布尔类型的(人们用0代表假用非0代表真)。后来增加了`True`,`False`还有`bool`类型,但是为了向前兼容,所以并没有`True`和`False`的关键字常量,只不过作为一个内部变量出现。
1036 | - Python 3是不向前兼容的,所以终于修复了这个问题,同时也要注意上面的例子在Python 3.X版本下是运行不了的!
1037 |
1038 | ---
1039 |
1040 | ### ▶ 转瞬即空
1041 |
1042 | ```py
1043 | some_list = [1, 2, 3]
1044 | some_dict = {
1045 | "key_1": 1,
1046 | "key_2": 2,
1047 | "key_3": 3
1048 | }
1049 |
1050 | some_list = some_list.append(4)
1051 | some_dict = some_dict.update({"key_4": 4})
1052 | ```
1053 |
1054 | **Output:**
1055 | ```py
1056 | >>> print(some_list)
1057 | None
1058 | >>> print(some_dict)
1059 | None
1060 | ```
1061 |
1062 | #### :bulb: 解释
1063 |
1064 | 大部分修改序列结构对象的方法,比如`list.append`,`dict.update`,`list.sort`等等。都会直接对序列对象本身进行操作,也就是所谓的“就地操作”(in-plcae)并且会返回`None`。这么做是为了提升程序的性能,避免还需要把原有对象拷贝一次。(参考[这里](http://docs.python.org/2/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list))。
1065 |
1066 | ```py
1067 | some_list = [1, 2, 3]
1068 | ```
1069 |
1070 | **Output:**
1071 | ```py
1072 | >>>some_list2 = some_list.append(4)
1073 | >>>some_list2
1074 | None
1075 | >>>some_list
1076 | [1, 2, 3, 4]
1077 | ```
1078 |
1079 | ---
1080 |
1081 | ### ▶ 子类的关系 *
1082 |
1083 | **Output:**
1084 | ```py
1085 | >>> from collections import Hashable
1086 | >>> issubclass(list, object)
1087 | True
1088 | >>> issubclass(object, Hashable)
1089 | True
1090 | >>> issubclass(list, Hashable)
1091 | False
1092 | ```
1093 |
1094 | 是不是觉得类的继承关系应该是可传递的(transitive)?(比如,`A`是`B`的一个子类,`B`又是`C`的一个子类,那么`A`也"应该"是`C`的子类)
1095 |
1096 | #### :bulb: 解释:
1097 |
1098 | * 在Python中子类的继承关系不一定是传递的,任何人都可以自定义元类(metaclass)中的`__subclasscheck__`函数(`_subclasscheck__(subclass)`检查subclass是不是调用类的子类)。
1099 | * 当调用`issubclass(cls, Hashable)`的时候,函数只是简单的检查一下`cls`和它继承的类中有没有"`__hash__`"这个方法。
1100 | * 因为`object`是可以被哈希的(也就是说`object`有`__hash__`这个函数),但是`list`是不能被哈希的,所以他们之间打破了传导关系。
1101 | * 如果想看更详尽的解释,[这里](https://www.naftaliharris.com/blog/python-subclass-intransitivity/)有关于Python子类关系传导的详细解释。
1102 |
1103 | ---
1104 |
1105 | ### ▶ 神秘的键值转换 *
1106 |
1107 | ```py
1108 | class SomeClass(str):
1109 | pass
1110 |
1111 | some_dict = {'s':42}
1112 | ```
1113 |
1114 | **Output:**
1115 | ```py
1116 | >>> type(list(some_dict.keys())[0])
1117 | str
1118 | >>> s = SomeClass('s')
1119 | >>> some_dict[s] = 40
1120 | >>> some_dict # expected: Two different keys-value pairs
1121 | {'s': 40}
1122 | >>> type(list(some_dict.keys())[0])
1123 | str
1124 | ```
1125 |
1126 | #### :bulb: 解释:
1127 |
1128 | * 类`s`和字符串`s`的哈希值是一样的,因为`SomeClass`继承了`str`类的`__hash__`方法。
1129 | * `SomeClass("s") == "s"` 等于`True`,因为`SomeClass`同样继承了`str`类的`__eq__`方法。
1130 | * 因为两个对象的哈希值(hash)和值(value)全都相等,所以他们在字典的关键字(key)里是被认为相同的。
1131 | * 如果想要打到预期的效果,我们可以重新定义`SomeClass`的`__eq__`方法
1132 | ```py
1133 | class SomeClass(str):
1134 | def __eq__(self, other):
1135 | return (
1136 | type(self) is SomeClass
1137 | and type(other) is SomeClass
1138 | and super().__eq__(other)
1139 | )
1140 |
1141 | # 当我们自定义 __eq__ 方法后, Python会停止
1142 | # 自动继承 __hash__ 方法, 所以我们同样也需要定义它
1143 | __hash__ = str.__hash__
1144 |
1145 | some_dict = {'s':42}
1146 | ```
1147 |
1148 | **Output:**
1149 | ```py
1150 | >>> s = SomeClass('s')
1151 | >>> some_dict[s] = 40
1152 | >>> some_dict
1153 | {'s': 40, 's': 42}
1154 | >>> keys = list(some_dict.keys())
1155 | >>> type(keys[0]), type(keys[1])
1156 | (__main__.SomeClass, str)
1157 | ```
1158 |
1159 | ---
1160 |
1161 | ### ▶ 看你能不能猜到这个的结果?
1162 |
1163 | ```py
1164 | a, b = a[b] = {}, 5
1165 | ```
1166 |
1167 | **Output:**
1168 | ```py
1169 | >>> a
1170 | {5: ({...}, 5)}
1171 | ```
1172 |
1173 | #### :bulb: 解释:
1174 |
1175 | * 根据Python的赋值语句的[文档](https://docs.python.org/3.7/reference/simple_stmts.html#assignment-statements),赋值语句有如下的格式
1176 | ```
1177 | (target_list "=")+ (expression_list | yield_expression)
1178 | ```
1179 | 并且
1180 | > 一个赋值语句会对表达式列表(expression_list)进行求值(这个可以只有一个表达式,也可以多个表达式用逗号分开组成表达式列表,后者最终会表现为元组的形式)并且会将单次的求值结果对象依次从左至右赋值给目标列表(target_list)。
1181 |
1182 | * 在`(target_list "=")+`这个表达式中`+`意味着 **一个或者多个** 目标列表。这上面这个例子中,目标列表是`a,b`和`a[b]`(注意表达式列表永远只有一个,在我们这个例子里是`{}, 5`)。
1183 |
1184 | * 当表达式计算出来后,它的值会被 **从左至右** 依次赋值给目标列表。所以,在上面的例子里,第一次赋值会把`{}, 5`这个元组赋值给`a, b`这个目标列表,这个时候`a = {}` 并且 `b = 5`。
1185 |
1186 | * 现在`a`被赋值成了`{}`,一个可变对象(mutable object)。
1187 |
1188 | * 第二个目标列表是`a[b]`(你可能会认为这种写法会报错,因为我们没有之前并没有定义`a`和`b`,但是没关系,我们刚刚已经给`a`和`b`分别赋值了`{}`和`5`)。
1189 |
1190 | * 现在我们会把元组`({}, 5)`赋值给字典关键为`5`的字典对象(也就是`a[5]`),同时也因此创建了一个循环引用(circular reference)(`{...}`在输出中代表同一个对象`a`已经被引用了)。下面是一个关于引用循环简单点的例子
1191 |
1192 | ```py
1193 | >>> some_list = some_list[0] = [0]
1194 | >>> some_list
1195 | [[...]]
1196 | >>> some_list[0]
1197 | [[...]]
1198 | >>> some_list is some_list[0]
1199 | True
1200 | >>> some_list[0][0][0][0][0][0] == some_list
1201 | True
1202 | ```
1203 | 在我们的例子用也是同样的情况(`a[b][0]`和`a`是指向同一个对象)
1204 |
1205 | * 所以总结一下,我们可以把上面的列子像如下这样拆分
1206 | ```py
1207 | a, b = {}, 5
1208 | a[b] = a, b
1209 | ```
1210 | 并且通过`a[b][0]`和`a`是同一个对象来证明确实存在引用循环
1211 | ```py
1212 | >>> a[b][0] is a
1213 | True
1214 | ```
1215 |
1216 | ---
1217 |
1218 | ---
1219 |
1220 | ## 第二章: 瞒天过海
1221 |
1222 | ### ▶ 无效的一行?
1223 |
1224 | **Output:**
1225 | ```py
1226 | >>> value = 11
1227 | >>> valuе = 32
1228 | >>> value
1229 | 11
1230 | ```
1231 |
1232 | 哈?一脸懵逼是不是(囧)?
1233 |
1234 | **提示:** 最简单的复现上面结果的办法是把上面的赋值语句原封不动的复制粘贴到你的文件或者命令行中执行。
1235 |
1236 | #### :bulb: 解释
1237 |
1238 | 一些非西方字符集看起来和我们平时用的英文字母的外观是一模一样的,但是在解释器看来就不一样了。
1239 |
1240 | ```py
1241 | >>> ord('е') # 俄语的字母 'e' (Ye)
1242 | 1077
1243 | >>> ord('e') # 拉丁文的字母'e', 也就是我们平时在英语和用标准键盘打出来的字母
1244 | 101
1245 | >>> 'е' == 'e'
1246 | False
1247 |
1248 | >>> value = 42 # 拉丁的 e
1249 | >>> valuе = 23 # 俄语里的 'e', 这里如果是用的Python 2.x,解释器会产生一个`SyntaxError`的异常
1250 | >>> value
1251 | 42
1252 | ```
1253 |
1254 | 内置函数`ord()`可以返回对应字符在Unicode编码中的位置([code point](https://en.wikipedia.org/wiki/Code_point)), 因为俄语的字符'e'和拉丁语的字符'e'的位置是不一样的,所以在上面的例子里,解释器会认为两个`value`变量是不同的。
1255 |
1256 | ---
1257 |
1258 | ### ▶ 移形换位 *
1259 |
1260 | ```py
1261 | import numpy as np
1262 |
1263 | def energy_send(x):
1264 | # 初始化一个numpy数组
1265 | np.array([float(x)])
1266 |
1267 | def energy_receive():
1268 | # 返回一个空numpy数组
1269 | return np.empty((), dtype=np.float).tolist()
1270 | ```
1271 |
1272 | **Output:**
1273 | ```py
1274 | >>> energy_send(123.456)
1275 | >>> energy_receive()
1276 | 123.456
1277 | ```
1278 |
1279 | 是不是很神奇?
1280 |
1281 | #### :bulb: 解释:
1282 |
1283 | * 因为在`energy_send`函数中创建的numpy数组并没有通过`return`返回赋给任何一个变量,所以带边这个数组的那一块内存空间是可以自由分配的。
1284 | * `numpy.empty()` 会返回最近的一块还没有被使用的内存块,并且不会对这个内存块进行初始化。这块内存块恰恰就是上一个函数刚刚释放的那块内存(大部分时间是这样的,但并不是绝对的)。
1285 |
1286 | ---
1287 |
1288 | ### ▶ 到底哪里出错了呢?
1289 |
1290 | ```py
1291 | def square(x):
1292 | """
1293 | 通过加法运算计算平方的函数
1294 | """
1295 | sum_so_far = 0
1296 | for counter in range(x):
1297 | sum_so_far = sum_so_far + x
1298 | return sum_so_far
1299 | ```
1300 |
1301 | **Output (Python 2.x):**
1302 |
1303 | ```py
1304 | >>> square(10)
1305 | 10
1306 | ```
1307 |
1308 | 结果不应该是100吗?
1309 |
1310 | **注意:** 如果你不能复现上面例子的结果, 可以试试直接运行[mixed_tabs_and_spaces.py](/mixed_tabs_and_spaces.py) 这个文件。
1311 |
1312 | #### :bulb: 解释
1313 |
1314 | * **不要把空格和制表符混在一起使用!** 例子中`return`语句前面的是一个用制表符"tab"来缩进的,代码的其他地方都是使用“四个空格”作为缩进的。
1315 | * Python是这样处理制表符的:
1316 | > 首先,制表符(tabs)会被依次替换成(从左至右)1到8个不等的空格直到替换后的空格数满足8的倍数......[出自Python文档](https://docs.python.org/2.0/ref/indentation.html)
1317 | * 所以在`square`这个函数最后这个制表符"tab"就被替换成了8个空格,八个空格就是两个缩进,所以就等于把`return`语句缩进到了`for`循环内。
1318 | * 这种情况在Python 3中就会直接报错。
1319 |
1320 | **Output (Python 3.x):**
1321 | ```py
1322 | TabError: inconsistent use of tabs and spaces in indentation
1323 | ```
1324 |
1325 | ---
1326 |
1327 | ---
1328 |
1329 |
1330 | ## 第三章: 隐藏的陷阱
1331 |
1332 |
1333 | ### ▶ 不要在迭代一个字典的时候修改这个字典!
1334 |
1335 | ```py
1336 | x = {0: None}
1337 |
1338 | for i in x:
1339 | del x[i]
1340 | x[i+1] = None
1341 | print(i)
1342 | ```
1343 |
1344 | **Output (Python 2.7- Python 3.5):**
1345 |
1346 | ```
1347 | 0
1348 | 1
1349 | 2
1350 | 3
1351 | 4
1352 | 5
1353 | 6
1354 | 7
1355 | ```
1356 |
1357 | 这个迭代会准确运行**八**次停止。
1358 |
1359 | #### :bulb: 解释:
1360 |
1361 | * 首先我是不赞成在迭代一个字典的时候对这个字典做修改的。
1362 | * 这个迭代正好运行了八次是因为正好到了一个点,到了这个点后,这个字典对象需要重新调整大小用来容纳足够的关键字(keys)(我们在循环中已经连续做了八次删除了,当然需要重新调整字典的大小了)。实际上这个点在哪取决于Python的具体实现细节。
1363 | * 对删除键值的处理和什么时候触发调整字典大小会根据不同Python的版本而有所不同。
1364 |
1365 | ---
1366 |
1367 | ### ▶ 删不掉的对象 *
1368 |
1369 | ```py
1370 | class SomeClass:
1371 | def __del__(self):
1372 | print("Deleted!")
1373 | ```
1374 |
1375 | **Output:**
1376 | 1\.
1377 | ```py
1378 | >>> x = SomeClass()
1379 | >>> y = x
1380 | >>> del x # 这里应该输出 "Deleted!"
1381 | >>> del y
1382 | Deleted!
1383 | ```
1384 |
1385 | 啧.....最后还是输出了的。你可能已经猜到了,我们第一次尝试删除`x`的时候,`__del__`方法并没有被调用。现在我们在上面例子的基础上再加几句代码。
1386 |
1387 | 2\.
1388 | ```py
1389 | >>> x = SomeClass()
1390 | >>> y = x
1391 | >>> del x
1392 | >>> y # 检查y是否存在
1393 | <__main__.SomeClass instance at 0x7f98a1a67fc8>
1394 | >>> del y # 就像上一个例子一样,这里应该输出"Deleted!"
1395 | >>> globals() # 嗯......好像并没有输出。让我们检查一下全局变量看看。
1396 | Deleted!
1397 | {'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None}
1398 | ```
1399 |
1400 | 好吧,最后还是输出了(好奇怪的说):confused:
1401 |
1402 | #### :bulb: 解释:
1403 | + `del x`并不会直接调用`x.__del__()`。
1404 | + 当调用`del x`的时候,Python会把所有x指向的对象的引用数减一(在这里就是前面实例化的对象`SomeClass()`),只有当这个对象的引用数为零的时候,才会调用`__del__()`。
1405 | + 在第二个例子中,`y.__del__()`没有被调用时因为前面的语句(`>>> y`)引用了同一个对象(SomeClass()),增加了这个对象的引用数,所以在`del y`以后这个对象的引用数并没有变成零。
1406 | + 调用`globals`会检查并销毁没用的引用,这时候针对上面对象(`SomeClass()`)的引用数就变成零了,"Deleted!"就被打印出来了。
1407 |
1408 | ---
1409 |
1410 | ### ▶ 删除正在迭代中的列表项
1411 |
1412 | ```py
1413 | list_1 = [1, 2, 3, 4]
1414 | list_2 = [1, 2, 3, 4]
1415 | list_3 = [1, 2, 3, 4]
1416 | list_4 = [1, 2, 3, 4]
1417 |
1418 | for idx, item in enumerate(list_1):
1419 | del item
1420 |
1421 | for idx, item in enumerate(list_2):
1422 | list_2.remove(item)
1423 |
1424 | for idx, item in enumerate(list_3[:]):
1425 | list_3.remove(item)
1426 |
1427 | for idx, item in enumerate(list_4):
1428 | list_4.pop(idx)
1429 | ```
1430 |
1431 | **Output:**
1432 | ```py
1433 | >>> list_1
1434 | [1, 2, 3, 4]
1435 | >>> list_2
1436 | [2, 4]
1437 | >>> list_3
1438 | []
1439 | >>> list_4
1440 | [2, 4]
1441 | ```
1442 |
1443 | 知道为什么输出是`[2, 4]`吗?
1444 |
1445 | #### :bulb: 解释:
1446 |
1447 | * 首先,我是不赞成在迭代的过程中修改迭代参数的(就像上面例子里那样)。正确的方法应该是先把迭代的列表拷贝一份,拿拷贝后的列表做迭代,就像上面第三个迭代做的那样(`list_3[:]`)。
1448 |
1449 | ```py
1450 | >>> some_list = [1, 2, 3, 4]
1451 | >>> id(some_list)
1452 | 139798789457608
1453 | >>> id(some_list[:]) # 注意如果用切片法的话,Python会新建一个list对象。
1454 | 139798779601192
1455 | ```
1456 |
1457 |
1458 | **`del`,`remove`和`pop`的区别:**
1459 | * `del var_name` 只会移除本地或者全局命名空间中对`var_name`这个变量的绑定(这就是为什么`list_1`迭代后没有任何改变,因为`list_1`这个变量并没有收到影响)。
1460 | * `remove` 会根据值(value)进行匹配并移除第一个匹配上的元素,若没有匹配的元素则抛出`ValueError`异常。
1461 | * `pop` 会移除指定索引(index)的元素并且返回这个元素,如果指定的元素不存在则抛出`IndexError`异常。
1462 |
1463 | **所以为什么会输出`[2, 4]`呢?**
1464 | - 首先列表迭代是根据索引值一个一个迭代的,所以当我们把`1`这个元素从`list_2`或者`list_4`中移除了以后,这两个列表就变成了`[2, 3, 4]`。剩下的这几个元素会依次向左移填补刚刚删除了的元素的位置,也就是说,`2`这个元素现在的索引值(index)变成了0,`3`的索引值变成了1。接着在下次迭代的时候,`for`循环访问的索引值已经变成了1(也就是列表里`3`这个元素),所以`2`这个元素就被跳过去(没有删除)。同样的情况每迭代一次都会发生一次。
1465 |
1466 | ---
1467 |
1468 | ### ▶ 被泄露出去的循环变量!
1469 |
1470 | 1\.
1471 | ```py
1472 | for x in range(7):
1473 | if x == 6:
1474 | print(x, ': for x inside loop')
1475 | print(x, ': x in global')
1476 | ```
1477 |
1478 | **Output:**
1479 | ```py
1480 | 6 : for x inside loop
1481 | 6 : x in global
1482 | ```
1483 |
1484 | 但是我从来没有在循环外定义一个变量`x`呀!
1485 |
1486 | 2\.
1487 | ```py
1488 | # 这回我先初始化一个x变量
1489 | x = -1
1490 | for x in range(7):
1491 | if x == 6:
1492 | print(x, ': for x inside loop')
1493 | print(x, ': x in global')
1494 | ```
1495 |
1496 | **Output:**
1497 | ```py
1498 | 6 : for x inside loop
1499 | 6 : x in global
1500 | ```
1501 |
1502 | 3\.
1503 | ```
1504 | x = 1
1505 | print([x for x in range(5)])
1506 | print(x, ': x in global')
1507 | ```
1508 |
1509 | **Output (Python 2.x):**
1510 | ```
1511 | [0, 1, 2, 3, 4]
1512 | (4, ': x in global')
1513 | ```
1514 |
1515 | **Output (Python 3.x):**
1516 | ```
1517 | [0, 1, 2, 3, 4]
1518 | 1 : x in global
1519 | ```
1520 |
1521 | #### :bulb: 解释:
1522 |
1523 | - 在Python里, for循环有它自己的作用域,但是在循环结束后,不会销毁那些循环变量。就算我们提前在全局作用域里定义了变量(就像上面那种情况),也会被循环变量覆盖。
1524 |
1525 | - 关于为什么在Python2和Python3中有不同的输出,可以在Python3的文档([What's New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html))中找到答案,下面会贴出原文:
1526 |
1527 | >"List comprehensions no longer support the syntactic form `[... for var in item1, item2, ...]`. Use `[... for var in (item1, item2, ...)]` instead. Also, note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a `list()` constructor, and in particular the loop control variables are no longer leaked into the surrounding scope."
1528 |
1529 | - 只用看最后一句话就够了**in particular the loop control variables are no longer leaked into the surrounding scope**,翻译过来是**特别是循环控制变量不会再泄露到包围着它的作用域以外了**,还有这个特性是针对构造列表(List comprehensions)说的,不包括正常的for循环。
1530 |
1531 | ---
1532 |
1533 | ### ▶ 小心那些有默认值的可变参数!
1534 |
1535 | ```py
1536 | def some_func(default_arg=[]):
1537 | default_arg.append("some_string")
1538 | return default_arg
1539 | ```
1540 |
1541 | **Output:**
1542 | ```py
1543 | >>> some_func()
1544 | ['some_string']
1545 | >>> some_func()
1546 | ['some_string', 'some_string']
1547 | >>> some_func([])
1548 | ['some_string']
1549 | >>> some_func()
1550 | ['some_string', 'some_string', 'some_string']
1551 | ```
1552 |
1553 | #### :bulb: 解释:
1554 |
1555 | - 在Python中,如果参数有默认值并且是一个可变对象,那么这个默认的对象会一直存在,并不会随着每次创建实例而重新生成一个新的对象。当我们显式的把`[]`对象传递给`some_func`函数的时候,`default_arg`这个参数的默认值就不会被使用而被新对象替代了,所以这个函数会按照我们预期的那样返回值。
1556 |
1557 | ```py
1558 | def some_func(default_arg=[]):
1559 | default_arg.append("some_string")
1560 | return default_arg
1561 | ```
1562 |
1563 | **Output:**
1564 | ```py
1565 | >>> some_func.__defaults__ #__defaults__会把这个函数的默认参数都打印出来
1566 | ([],)
1567 | >>> some_func()
1568 | >>> some_func.__defaults__
1569 | (['some_string'],)
1570 | >>> some_func()
1571 | >>> some_func.__defaults__
1572 | (['some_string', 'some_string'],)
1573 | >>> some_func([])
1574 | >>> some_func.__defaults__
1575 | (['some_string', 'some_string'],)
1576 | ```
1577 |
1578 | - 一个常用的用来避免上述bug的做法是这样的,把默认可变参数赋值成`None`,然后在函数体中检查这个参数是否被赋值,如果没有就赋一个默认值:
1579 |
1580 | ```py
1581 | def some_func(default_arg=None):
1582 | if not default_arg:
1583 | default_arg = []
1584 | default_arg.append("some_string")
1585 | return default_arg
1586 | ```
1587 |
1588 | ---
1589 |
1590 | ### ▶ 抓住这些异常!
1591 |
1592 | ```py
1593 | some_list = [1, 2, 3]
1594 | try:
1595 | # 这里应该会抛出一个 ``IndexError`` 异常
1596 | print(some_list[4])
1597 | except IndexError, ValueError:
1598 | print("Caught!")
1599 |
1600 | try:
1601 | # 这里会抛出一个 ``ValueError`` 异常
1602 | some_list.remove(4)
1603 | except IndexError, ValueError:
1604 | print("Caught again!")
1605 | ```
1606 |
1607 | **Output (Python 2.x):**
1608 | ```py
1609 | Caught!
1610 |
1611 | ValueError: list.remove(x): x not in list
1612 | ```
1613 |
1614 | **Output (Python 3.x):**
1615 | ```py
1616 | File "", line 3
1617 | except IndexError, ValueError:
1618 | ^
1619 | SyntaxError: invalid syntax
1620 | ```
1621 |
1622 | #### :bulb: 解释
1623 |
1624 | * 如果想在`except`语句中同时捕获多个异常类型,需要把这些类型写成元组(tuple)的形式放在第一个参数的位置。第二个参数是一个可选参数,用来绑定具体捕获异常后的对象。比如,
1625 | ```py
1626 | some_list = [1, 2, 3]
1627 | try:
1628 | # 这里会产生一个 ``ValueError`` 异常
1629 | some_list.remove(4)
1630 | except (IndexError, ValueError), e:
1631 | print("Caught again!")
1632 | print(e)
1633 | ```
1634 | **Output (Python 2.x):**
1635 | ```
1636 | Caught again!
1637 | list.remove(x): x not in list
1638 | ```
1639 | **Output (Python 3.x):**
1640 | ```py
1641 | File "", line 4
1642 | except (IndexError, ValueError), e:
1643 | ^
1644 | IndentationError: unindent does not match any outer indentation level
1645 | ```
1646 |
1647 | * 在Python 3中已经弃用了使用逗号分隔异常类型和异常变量了;而是要使用`as`语法了。比如,
1648 | ```py
1649 | some_list = [1, 2, 3]
1650 | try:
1651 | some_list.remove(4)
1652 |
1653 | except (IndexError, ValueError) as e:
1654 | print("Caught again!")
1655 | print(e)
1656 | ```
1657 | **Output:**
1658 | ```
1659 | Caught again!
1660 | list.remove(x): x not in list
1661 | ```
1662 |
1663 | ---
1664 |
1665 | ### ▶ 相同的操作,不同的结果
1666 |
1667 | 1\.
1668 | ```py
1669 | a = [1, 2, 3, 4]
1670 | b = a
1671 | a = a + [5, 6, 7, 8]
1672 | ```
1673 |
1674 | **Output:**
1675 | ```py
1676 | >>> a
1677 | [1, 2, 3, 4, 5, 6, 7, 8]
1678 | >>> b
1679 | [1, 2, 3, 4]
1680 | ```
1681 |
1682 | 2\.
1683 | ```py
1684 | a = [1, 2, 3, 4]
1685 | b = a
1686 | a += [5, 6, 7, 8]
1687 | ```
1688 |
1689 | **Output:**
1690 | ```py
1691 | >>> a
1692 | [1, 2, 3, 4, 5, 6, 7, 8]
1693 | >>> b
1694 | [1, 2, 3, 4, 5, 6, 7, 8]
1695 | ```
1696 |
1697 | #### :bulb: 解释:
1698 |
1699 | * `a += b` 和 `a = a + b`的内部处理流程有时候会根据操作数的不同有所不同。比如*`op=`*这种操作符(比如`+=`,`-=`,`*=`这样),针对列表(list)对象这两个表达式就会有不一样的内在表现。
1700 |
1701 | * 表达式`a = a + [5,6,7,8]`会生成一个新的对象,并且`a`会引用这个新生成的对象,`b`变量不会变(继续引用之前的列表对象`[1,2,3,4]`)。
1702 |
1703 | * 表达式`a += [5,6,7,8]`就不会生成一个新对象,它实际上是在原有对象的基础上又“扩展了”几个元素,也就是所谓的就地改变(in-place)。所以这个时候`a`,`b`两个变量还是同时引用的一个对象。
1704 |
1705 | ---
1706 |
1707 | ### ▶ 作用域之外的变量
1708 |
1709 | ```py
1710 | a = 1
1711 | def some_func():
1712 | return a
1713 |
1714 | def another_func():
1715 | a += 1
1716 | return a
1717 | ```
1718 |
1719 | **Output:**
1720 | ```py
1721 | >>> some_func()
1722 | 1
1723 | >>> another_func()
1724 | UnboundLocalError: local variable 'a' referenced before assignment
1725 | ```
1726 |
1727 | #### :bulb: 解释:
1728 | * 当你在某个作用域给一个变量赋值的时候,这个变量就会自动变成这个作用域的变量。所以`a`会变成`another_func`这个函数的局部变量,但是又没有在这个函数作用域内正确的初始化`a`变量,结果就发生了异常。
1729 | * 这篇[文章](http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html)很好的解释了命名空间和作用域是如何在Python中起作用的(是英文版,等有时间我会翻译一下,如果有热心的同学帮忙就更好啦!)。
1730 | * 如果想在局部作用域内使用全局作用域的变量`a`,需要使用`global`关键字。
1731 | ```py
1732 | def another_func()
1733 | global a
1734 | a += 1
1735 | return a
1736 | ```
1737 |
1738 | **Output:**
1739 | ```py
1740 | >>> another_func()
1741 | 2
1742 | ```
1743 |
1744 | ---
1745 |
1746 | ### ▶ 小心那些链式操作符
1747 |
1748 | ```py
1749 | >>> (False == False) in [False] # 没问题,OK?
1750 | False
1751 | >>> False == (False in [False]) # 这个也OK对吧?
1752 | False
1753 | >>> False == False in [False] # 这个呢?是不是开始不对了?
1754 | True
1755 |
1756 | >>> True is False == False
1757 | False
1758 | >>> False is False is False
1759 | True
1760 |
1761 | >>> 1 > 0 < 1
1762 | True
1763 | >>> (1 > 0) < 1
1764 | False
1765 | >>> 1 > (0 < 1)
1766 | False
1767 | ```
1768 |
1769 | #### :bulb: 解释:
1770 |
1771 | 按照 https://docs.python.org/2/reference/expressions.html#not-in 里面说的:
1772 |
1773 | > 如果 a, b, c, ..., y, z 这些是表达式,op1, op2, ..., opN 这些是比较符,那么 a op1 b op2 c ... y opN z 就等于 a op1 b and b op2 c and ... y opN z, 除非其中有些表达式最多只能计算一次。
1774 |
1775 | 这种模式运行在像 `a == b == c` 和 `0 <=x <= 100` 这种语句里就会产生很有意思的结果了,就像上边那些例子一样。
1776 |
1777 | * `False is False is False` 等于 `(False is False) and (False is False)`。
1778 | * `True is False == False` 等于 `True is False and False == False` 而且第一部分 (`True is False`) 等于 `False`, 所以整个表达式结果就为 `False`。
1779 | * `1 > 0 < 1` 等于 `1 > 0 and 0 < 1` 最后结果等于 `True`。
1780 | * 表达式 `(1 > 0) < 1` 等于 `True < 1` 而且
1781 | ```py
1782 | >>> int(True)
1783 | 1
1784 | >>> True + 1 #这个跟例子没啥关系,就是想告诉你们布尔值也可以这样玩^_^
1785 | 2
1786 | ```
1787 | 所以, `1 < 1`也等于`False`
1788 |
1789 | ---
1790 |
1791 | ### ▶ 被忽略的类变量
1792 |
1793 | 1\.
1794 | ```py
1795 | x = 5
1796 | class SomeClass:
1797 | x = 17
1798 | y = (x for i in range(10))
1799 | ```
1800 |
1801 | **Output:**
1802 | ```py
1803 | >>> list(SomeClass.y)[0]
1804 | 5
1805 | ```
1806 |
1807 | 2\.
1808 | ```py
1809 | x = 5
1810 | class SomeClass:
1811 | x = 17
1812 | y = [x for i in range(10)]
1813 | ```
1814 |
1815 | **Output (Python 2.x):**
1816 | ```py
1817 | >>> SomeClass.y[0]
1818 | 17
1819 | ```
1820 |
1821 | **Output (Python 3.x):**
1822 | ```py
1823 | >>> SomeClass.y[0]
1824 | 5
1825 | ```
1826 |
1827 | #### :bulb: 解释
1828 | - 如果一个类定义内嵌套了一个子作用域,那么在这个作用域内部会忽略所有这个类级别的变量绑定。
1829 | - 一个生成器(generator)表达式有自己的作用域
1830 | - 从Python 3.X开始,列表生成式也有自己的作用域了。
1831 |
1832 | 
1833 |
1834 | ---
1835 | ### ▶ 小心处理元组
1836 |
1837 | 1\.
1838 | ```py
1839 | x, y = (0, 1) if True else None, None
1840 | ```
1841 |
1842 | **Output:**
1843 | ```
1844 | >>> x, y # 期待的结果是 (0, 1)
1845 | ((0, 1), None)
1846 | ```
1847 |
1848 | 基本每一个Python程序员都会遇到过下面这种情况。
1849 |
1850 | 2\.
1851 | ```py
1852 | t = ('one', 'two')
1853 | for i in t:
1854 | print(i)
1855 |
1856 | t = ('one')
1857 | for i in t:
1858 | print(i)
1859 |
1860 | t = ()
1861 | print(t)
1862 | ```
1863 |
1864 | **Output:**
1865 | ```py
1866 | one
1867 | two
1868 | o
1869 | n
1870 | e
1871 | tuple()
1872 | ```
1873 |
1874 | #### :bulb: 解释:
1875 | * 第一个例子里,想要得到期待的结果的写法应该是`x, y = (0, 1) if True else (None, None)`,要显式声明元组。
1876 | * 第二个例子里,想要得到期待的结果的写法应该是 `t = ('one',)` 或者 `t = 'one',`(原来的少了逗号)否则解释器会把变量`t`当做一个`str`字符串处理挨个迭代里面的每一个字符。
1877 | * `()`是一个特殊的标识符专门用来表示空元组(`tuple`)。
1878 |
1879 | ---
1880 |
1881 | ---
1882 |
1883 |
1884 | ## 第四章: 一起来找些有趣的东西!
1885 |
1886 | 这一章包含了一些有趣的但是鲜为人知的彩蛋,大部分的Python新手都很少有人知道,就比如我(当然,现在我知道了)。
1887 |
1888 | ### ▶ 人生苦短,我用Python *
1889 |
1890 | 首先,我先引用下面这个模块,然后......
1891 |
1892 | ```py
1893 | import antigravity
1894 | ```
1895 |
1896 | **Output:**
1897 | 嘘....这个秘密可没几个人知道!
1898 |
1899 | #### :bulb: 解释:
1900 | + `antigravity` 模块是针对Python开发者的一个小彩蛋。
1901 | + `import antigravity` 会打开一个指向 [classic XKCD comic](http://xkcd.com/353/)的网站,一个关于Python的漫画网站。
1902 | + 而且,这还是一个俄罗斯套娃彩蛋,也就是说**这个彩蛋中还包含着另一个彩蛋**。如果你打开这个模块的[代码](https://github.com/python/cpython/blob/master/Lib/antigravity.py#L7-L17),你会发现代码中还定义了一个实现[geohashing算法](https://xkcd.com/426/)的函数(这个算法是一个利用股票市场数据随机生成经度纬度坐标的算法)。
1903 |
1904 | ---
1905 |
1906 | ### ▶ 为什么要用`goto`? *
1907 |
1908 | ```py
1909 | from goto import goto, label
1910 | for i in range(9):
1911 | for j in range(9):
1912 | for k in range(9):
1913 | print("I'm trapped, please rescue!")
1914 | if k == 2:
1915 | goto .breakout #从一个深层循环内跳出
1916 | label .breakout
1917 | print("Freedom!")
1918 | ```
1919 |
1920 | **Output (Python 2.3):**
1921 | ```py
1922 | I'm trapped, please rescue!
1923 | I'm trapped, please rescue!
1924 | Freedom!
1925 | ```
1926 |
1927 | #### :bulb: 解释:
1928 | - `goto`模块是作为2004年的愚人节(2004.4.1)玩笑[发布](https://mail.python.org/pipermail/python-announce-list/2004-April/002982.html)的,只有Python 2.3版本有`goto`模块。
1929 | - 当前版本的Python是没有这个模块的(如果你用的是conda甚至不能下载到2.3版本的Python)。
1930 | - 虽然上面版本可以使用`goto`,但是也请不要使用它。[这里](https://docs.python.org/3/faq/design.html#why-is-there-no-goto)是为什么当前版本没有`goto`模块的原因。
1931 |
1932 | ---
1933 |
1934 | ### ▶ 试试用大括号? *
1935 |
1936 | 如果你不太习惯Python中用空格缩进来表示语句段(scopes)的方法,可以通过引用`__future__`模块来使用C语言大括号({})形式的表示方法来表示语句段,
1937 |
1938 | ```py
1939 | from __future__ import braces
1940 | ```
1941 |
1942 | **Output:**
1943 | ```py
1944 | File "some_file.py", line 1
1945 | from __future__ import braces
1946 | SyntaxError: not a chance
1947 | ```
1948 |
1949 | 想用大括号?不可能的,如果你不服气,用Java去。
1950 |
1951 | #### :bulb: 解释:
1952 | + `__future__`模块一般用来提供未来Python版本才拥有的一些特性。当然,在这个例子里,"future"这个词本身也算是对大括号的嘲讽。
1953 | + 这是一个Python社区针对大括号这个问题设计出来的一个彩蛋。
1954 |
1955 | ---
1956 |
1957 | ### ▶ 不等号的争议 *
1958 |
1959 | **Output (Python 3.x)**
1960 | ```py
1961 | >>> from __future__ import barry_as_FLUFL
1962 | >>> "Ruby" != "Python" # 这句话没有任何争议
1963 | File "some_file.py", line 1
1964 | "Ruby" != "Python"
1965 | ^
1966 | SyntaxError: invalid syntax
1967 |
1968 | >>> "Ruby" <> "Python"
1969 | True
1970 | ```
1971 |
1972 | 直接看解释吧。
1973 |
1974 | #### :bulb: 解释:
1975 | - 上面的问题起源自一个已经淘汰的Python修正包[PEP-401](https://www.python.org/dev/peps/pep-0401/),发布于2009年的4月1日(明白为什么它会被淘汰了吧)。
1976 | - 下面引用 PEP-401 的内容
1977 | > 为了让你们认识到在Python3.0中 != 这种逻辑不等式是多么的不好用,FLUFL引用会使解释器禁止这种语法只允许 <> 这种便利的逻辑不等式。
1978 |
1979 | ---
1980 |
1981 | ### ▶ 就算Python也知道爱是个复杂的东西 *
1982 |
1983 | ```py
1984 | import this
1985 | ```
1986 |
1987 | Wait, what's **this**? `this` is love :heart:
1988 | (译注:我认为这句话原汁原味的看才能体会其中的乐趣,就不翻译了😊)
1989 |
1990 | **Output:**
1991 | ```
1992 | The Zen of Python, by Tim Peters
1993 |
1994 | Beautiful is better than ugly.
1995 | Explicit is better than implicit.
1996 | Simple is better than complex.
1997 | Complex is better than complicated.
1998 | Flat is better than nested.
1999 | Sparse is better than dense.
2000 | Readability counts.
2001 | Special cases aren't special enough to break the rules.
2002 | Although practicality beats purity.
2003 | Errors should never pass silently.
2004 | Unless explicitly silenced.
2005 | In the face of ambiguity, refuse the temptation to guess.
2006 | There should be one-- and preferably only one --obvious way to do it.
2007 | Although that way may not be obvious at first unless you're Dutch.
2008 | Now is better than never.
2009 | Although never is often better than *right* now.
2010 | If the implementation is hard to explain, it's a bad idea.
2011 | If the implementation is easy to explain, it may be a good idea.
2012 | Namespaces are one honking great idea -- let's do more of those!
2013 | ```
2014 |
2015 | 这些(指上面输出的内容)就是Python之道!
2016 |
2017 | ```py
2018 | >>> love = this
2019 | >>> this is love
2020 | True
2021 | >>> love is True
2022 | False
2023 | >>> love is False
2024 | False
2025 | >>> love is not True or False
2026 | True
2027 | >>> love is not True or False; love is love # 爱(love)是个复杂的东西,不是吗?
2028 | True
2029 | ```
2030 |
2031 | #### :bulb: 解释:
2032 |
2033 | * `this`模块是一个针对Python之道做阐述的彩蛋([PEP 20](https://www.python.org/dev/peps/pep-0020))。
2034 | * 但是这并没有完,如果你看看这个模块的实现代码[this.py](https://hg.python.org/cpython/file/c3896275c0f6/Lib/this.py)。你会发现一个更加有趣的事情,那就是这个代码本身就违反了上面输出的Python之道(当然,我觉得这也是唯一一处违反的地方)。
2035 | * 关于`love is not True or False; love is love`这句话,它解释的很对,不是吗?
2036 |
2037 | ---
2038 |
2039 | ### ▶ 这些语句是存在的!
2040 |
2041 | **循环语句对应的`else`语句.** 下面是一个标准的例子:
2042 |
2043 | ```py
2044 | def does_exists_num(l, to_find):
2045 | for num in l:
2046 | if num == to_find:
2047 | print("Exists!")
2048 | break
2049 | else:
2050 | print("Does not exist")
2051 | ```
2052 |
2053 | **Output:**
2054 | ```py
2055 | >>> some_list = [1, 2, 3, 4, 5]
2056 | >>> does_exists_num(some_list, 4)
2057 | Exists!
2058 | >>> does_exists_num(some_list, -1)
2059 | Does not exist
2060 | ```
2061 |
2062 | **异常处理对应的`else`语句** 看例子,
2063 |
2064 | ```py
2065 | try:
2066 | pass
2067 | except:
2068 | print("Exception occurred!!!")
2069 | else:
2070 | print("Try block executed successfully...")
2071 | ```
2072 |
2073 | **Output:**
2074 | ```py
2075 | Try block executed successfully...
2076 | ```
2077 |
2078 | #### :bulb: 解释:
2079 | - 当一个`else`语句紧跟在一个循环语句后面的时候,只有当循环语句块内有明确使用`break`退出,否则所有循环结束就会执行`else`语句块。
2080 | - 跟在`try`语句块后面的`else`语句,又叫做“完成语句(completion clause)”。意味着如果`try`语句块的内容顺利运行完毕,那么就会进入到`else`语句块。
2081 |
2082 | ---
2083 |
2084 | ### ▶ 无限(Inpinity) *
2085 |
2086 | **Output (Python 3.x):**
2087 | ```py
2088 | >>> infinity = float('infinity')
2089 | >>> hash(infinity)
2090 | 314159
2091 | >>> hash(float('-inf'))
2092 | -314159
2093 | ```
2094 |
2095 | #### :bulb: 解释:
2096 | - 无限(infinity)的哈希值是 10⁵ x π.
2097 | - 有趣的是, 在Python3中,`float('-inf')`的哈希值是"-10⁵ x π", 但是在Python2中是"-10⁵ x e"(正无穷还是一样的哈希值)。
2098 |
2099 | ---
2100 |
2101 | ### ▶ 被修改的类成员 *
2102 |
2103 | ```py
2104 | class Yo(object):
2105 | def __init__(self):
2106 | self.__honey = True
2107 | self.bitch = True
2108 | ```
2109 |
2110 | **Output:**
2111 | ```py
2112 | >>> Yo().bitch
2113 | True
2114 | >>> Yo().__honey
2115 | AttributeError: 'Yo' object has no attribute '__honey'
2116 | >>> Yo()._Yo__honey
2117 | True
2118 | ```
2119 |
2120 | 为什么非要`Yo()._Yo__honey`才对? 如果有印度朋友他一定会懂这个梗的(好吧,我猜不会有印度人看一个中文的文章,Yo Yo Honey Singh是一个印度著名rapper,这就是梗,也是为什么这个类要起名Yo,哈哈).
2121 |
2122 | #### :bulb: 解释:
2123 |
2124 | * [名称混淆](https://en.wikipedia.org/wiki/Name_mangling)是一种用来避免命名空间下命名冲突的技术。
2125 | * 在Python里,如果某个类成员的名称以`__`(两个下划线)开头,并且不以两个以上的下划线结尾(这就意味着你想定义一个私有变量,但是Python是没有私有变量的),那么编辑器就会自动在这个成员名称之前加上`_NameOfTheClass`(在这里就是`_Yo`),来防止子类不小心访问修改到这个成员属性。
2126 | * 所以,当要访问`__honey`这个成员属性的时候,我们需要在前面加上`_Yo`来进行访问。
2127 |
2128 | ---
2129 |
2130 | ---
2131 |
2132 | ## 第五章: 杂项
2133 |
2134 |
2135 | ### ▶ `+=`更快
2136 |
2137 | ```py
2138 | # 使用"+"连接三个字符串:
2139 | >>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
2140 | 0.25748300552368164
2141 | # 使用"+="连接三个字符串:
2142 | >>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
2143 | 0.012188911437988281
2144 | ```
2145 |
2146 | #### :bulb: 解释:
2147 | + 当需要连接的字符串超过两个的时候,使用`+=`会比使用`+`更快。(比如上面的例子`s1 += s2 + s3`中的`s1`),因为第一个字符串不会在做链接的时候销毁重建(因为`+=`操作符是就地操作的,上面多次已经提到过这个符号特性)。
2148 |
2149 | ---
2150 |
2151 | ### ▶ 超长字符串!
2152 |
2153 | ```py
2154 | #使用加号连接字符串
2155 | def add_string_with_plus(iters):
2156 | s = ""
2157 | for i in range(iters):
2158 | s += "xyz"
2159 | assert len(s) == 3*iters
2160 |
2161 | #使用比特流连接字符串
2162 | def add_bytes_with_plus(iters):
2163 | s = b""
2164 | for i in range(iters):
2165 | s += b"xyz"
2166 | assert len(s) == 3*iters
2167 |
2168 | #使用format函数连接字符串
2169 | def add_string_with_format(iters):
2170 | fs = "{}"*iters
2171 | s = fs.format(*(["xyz"]*iters))
2172 | assert len(s) == 3*iters
2173 |
2174 | #使用数组连接字符串
2175 | def add_string_with_join(iters):
2176 | l = []
2177 | for i in range(iters):
2178 | l.append("xyz")
2179 | s = "".join(l)
2180 | assert len(s) == 3*iters
2181 |
2182 | #将列表转换成字符串
2183 | def convert_list_to_string(l, iters):
2184 | s = "".join(l)
2185 | assert len(s) == 3*iters
2186 | ```
2187 |
2188 | **Output:**
2189 | ```py
2190 | >>> timeit(add_string_with_plus(10000))
2191 | 1000 loops, best of 3: 972 µs per loop
2192 | >>> timeit(add_bytes_with_plus(10000))
2193 | 1000 loops, best of 3: 815 µs per loop
2194 | >>> timeit(add_string_with_format(10000))
2195 | 1000 loops, best of 3: 508 µs per loop
2196 | >>> timeit(add_string_with_join(10000))
2197 | 1000 loops, best of 3: 878 µs per loop
2198 | >>> l = ["xyz"]*10000
2199 | >>> timeit(convert_list_to_string(l, 10000))
2200 | 10000 loops, best of 3: 80 µs per loop
2201 | ```
2202 |
2203 | 让我们把循环次数提升十倍。
2204 |
2205 | ```py
2206 | >>> timeit(add_string_with_plus(100000)) # 执行时间线性增加
2207 | 100 loops, best of 3: 9.75 ms per loop
2208 | >>> timeit(add_bytes_with_plus(100000)) # 平方式增加
2209 | 1000 loops, best of 3: 974 ms per loop
2210 | >>> timeit(add_string_with_format(100000)) # 线性增加
2211 | 100 loops, best of 3: 5.25 ms per loop
2212 | >>> timeit(add_string_with_join(100000)) # 线性增加
2213 | 100 loops, best of 3: 9.85 ms per loop
2214 | >>> l = ["xyz"]*100000
2215 | >>> timeit(convert_list_to_string(l, 100000)) # 线性增加
2216 | 1000 loops, best of 3: 723 µs per loop
2217 | ```
2218 |
2219 | #### :bulb: 解释
2220 | - 关于`timeit`模块的用法可以看[这里](https://docs.python.org/3/library/timeit.html)。这是一个用来测量程序块执行时间的模块。
2221 | - 在Python中,不要试图用`+`来连接一个长字符串,因为`str`这个类型在Python中是一个不可变类型,这就意味着每次两个字符串对象连接程序都需要从左到右把两个字符串的每一个字符都复制一份。比如你现在要连接四个长度为10的字符串,那么程序需要拷贝 (10+10) + ((10+10)+10) + (((10+10)+10)+10) = 90 个字符,而不是40个。而且程序运行时间会随着你需要连接的字符串数量和字符数量呈平方形式的增长。(请参考`add_bytes_with_plus`函数的执行时间对比)。
2222 | - 所以,建议大家使用`.format`或者`%`这种语法(但是,它们在连接短字符串的时候会比`+`号慢一点)。
2223 | - 或者,如果已经提前获得了一个可以迭代的对象,则可以使用`''.join(iterable_object)`这种语法把它连接成字符串,这样的速度会比前几个都快。
2224 | - 至于为什么`add_string_with_plus`不会像`add_bytes_with_plus`着这样成平方形式的提升执行时间,是因为前面例子里已经解释过的`+=`操作符的优化效果。如果把`s += "xyz"`这句话替换成了`s = s + "x" + "y" + "z"`,那么执行时间还是会呈平方形式的增加。
2225 | ```py
2226 | def add_string_with_plus(iters):
2227 | s = ""
2228 | for i in range(iters):
2229 | s = s + "x" + "y" + "z"
2230 | assert len(s) == 3*iters
2231 |
2232 | >>> timeit(add_string_with_plus(10000))
2233 | 100 loops, best of 3: 9.87 ms per loop
2234 | >>> timeit(add_string_with_plus(100000)) # 平方式增加
2235 | 1 loops, best of 3: 1.09 s per loop
2236 | ```
2237 |
2238 | ---
2239 |
2240 | ### ▶ 字符串到浮点数的转换
2241 |
2242 | ```py
2243 | a = float('inf')
2244 | b = float('nan')
2245 | c = float('-iNf') #这些字符串是大小写不敏感的
2246 | d = float('nan')
2247 | ```
2248 |
2249 | **Output:**
2250 | ```py
2251 | >>> a
2252 | inf
2253 | >>> b
2254 | nan
2255 | >>> c
2256 | -inf
2257 | >>> float('some_other_string')
2258 | ValueError: could not convert string to float: some_other_string
2259 | >>> a == -c #inf==inf
2260 | True
2261 | >>> None == None # None==None
2262 | True
2263 | >>> b == d #但是 nan!=nan 为真
2264 | False
2265 | >>> 50/a
2266 | 0.0
2267 | >>> a/a
2268 | nan
2269 | >>> 23 + b
2270 | nan
2271 | ```
2272 |
2273 | #### :bulb: 解释:
2274 |
2275 | `'inf'`和`'nan'`是一种特殊的字符串(大小写不敏感),它们两个都可以强制转换成`float`浮点类型,并且分别代表了数学意义上的"无限"(infinity)和"非数字"(not a number)。
2276 |
2277 | ---
2278 |
2279 | ### ▶ 最后一些小特点集合
2280 |
2281 | * `join()`是一个字符串的函数而不是一个列表附属函数(第一次用的人可能会觉得有点别扭)
2282 |
2283 | **:bulb: 解释:**
2284 | 如果`join()`作为一个字符串的方法,那么它就可以操作所有的可迭代类型(list, tuple, iterators)。但是如果它作为了一个列表(list)的方法,那它同样的也需要为另外那些可迭代类型构造专属的函数内容。
2285 |
2286 | * 下面这些句子看着奇怪,但是它们确实都是符合语法规则的
2287 | + `[] = ()` 在语法上是正确的 (把一个元组赋值给一个列表)
2288 | + `'a'[0][0][0][0][0]` 同样也是语法正确的,因为在Python中字符串是一个序列(可迭代的对象就可以使用索引值进行访问里面的元素)。
2289 | + `3 --0-- 5 == 8`和`--5 == 5`也都是语法正确的,并且最后结果都为`True`。
2290 |
2291 | * 给定一个数字类型变量`a`,`++a`和`--a`在Python中都是合法的,但是它们表达的意思和在C语言(或者C++,Java)中是完全不一样的。
2292 | ```py
2293 | >>> a = 5
2294 | >>> a
2295 | 5
2296 | >>> ++a
2297 | 5
2298 | >>> --a
2299 | 5
2300 | ```
2301 |
2302 | **:bulb: 解释:**
2303 | + 在Python语法中,是没有`++`这个操作符的。这个仅仅代表两个单独的`+`操作符而已。
2304 | + `++a`在Python中会被解析成`+(+a)`,也就等于`a`。同样的,`--a`也是这种情况。
2305 | + 这篇StackOverflow的[文章](https://stackoverflow.com/questions/3654830/why-are-there-no-and-operators-in-python)讨论了Python中没有自增自减符号背后的逻辑关系。
2306 |
2307 | * 多个Python线程并不会并发执行你的*Python代码*(你没看错,不会!)。也许你想同时开很多个线程然后并发执行你写的Python代码,但是,由于Python中存在一个叫[GIL](https://wiki.python.org/moin/GlobalInterpreterLock)(Global Interpreter Lock)的东西,所以最终你的线程还是会一个接着一个执行。Python的线程执行IO阻塞的任务比较拿手,但是如果想在CPU阻塞的任务中获得真正的并发能力,你需要了解一下Python的[multiprocessing](https://docs.python.org/2/library/multiprocessing.html)模块。
2308 |
2309 | * 列表切片的时候如果索引值超出界限并不会报错
2310 | ```py
2311 | >>> some_list = [1, 2, 3, 4, 5]
2312 | >>> some_list[111:]
2313 | []
2314 | ```
2315 |
2316 | * 在Python3中,`int('١٢٣٤٥٦٧٨٩')`会返回`123456789`。在Python中,十进制字符不仅包含十进制的数字,还包含了大部分可以表示为十进制数字形式的字符,比如,U+0660, ARABIC_INDIC DIGIT ZERO,一种印度阿拉伯语里代表数字0的字符。不过还没有中文的"一,二,三..."或者"壹,贰,叁..."这种,有人说是因为中文在unicode中的编码是不连续的,不过无论如何,现在确实还没有。这里有一篇[小故事](http://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/)是关于这个特性的,英语版,有兴趣的同学可以看看。
2317 |
2318 | * `'abc'.count('') == 4`。 下面模拟实现了一个相似的`count()`方法,
2319 | ```py
2320 | def count(s, sub):
2321 | result = 0
2322 | for i in range(len(s) + 1 - len(sub)):
2323 | result += (s[i:i + len(sub)] == sub)
2324 | return result
2325 | ```
2326 | 产生这个结果的原因是子串为空时会匹配上原始字符串长度为0时的字符(总之,看代码你会懂的)。
2327 |
2328 | ---
2329 |
2330 |
2331 | # 贡献
2332 |
2333 | 欢迎任何补丁和修正!详细信息请看 [CONTRIBUTING.md](./CONTRIBUTING.md)
2334 |
2335 | 如果想要参与讨论, 可以选择创建一个新的[issue](https://github.com/true1023/Crazy-Python/issues) 或者加入我创建的gitter[房间](https://gitter.im/Crazy-Python/Lobby)
2336 |
2337 | # 感谢
2338 |
2339 | 这个仓库翻译自 [What the fu*k Python](https://github.com/satwikkansal/wtfpython),迄今为止没谁需要感谢,感谢下原作者[Satwik Kansal](https://github.com/satwikkansal)吧。
2340 |
2341 |
2342 |
2343 | # :mortar_board: 版权声明
2344 |
2345 | [](http://www.wtfpl.net/)
2346 |
2347 | :copyright: [True1023](https://github.com/true1023/)
2348 |
2349 |
2350 |
2351 | ## 帮助
2352 |
2353 | 如果你有任何好的想法和意见,请务必要分享给我。
2354 |
2355 | ## 关于pdf出版的问题
2356 |
2357 | 我会在收集并修正一些bug后生成pdf版本的Crazy Python,想要的小伙伴可以[发信](mailto:shi12li12@gmail.com)给我,或者关注公众号**编程行动派**,我也会第一时间在公众号里通知大家。
2358 |
2359 | 
--------------------------------------------------------------------------------