来源:wtfpython, 摘一些比较有代表性的问题。

相关文章:

1.字典键的隐式转换

1
2
3
4
some_dict = {}
some_dict[5.5] = "Ruby"
some_dict[5.0] = "JavaScript"
some_dict[5] = "Python"

输出:

1
2
3
4
5
6
>>> some_dict[5.5]
"Ruby"
>>> some_dict[5.0]
"Python"
>>> some_dict[5]
"Python"

解释:5和5.0 在hash过程中被隐式转换,视为同一个key, hash值一样。

1
2
3
4
>>> 5 == 5.0
True
>>> hash(5) == hash(5.0)
True

2.生成器执行时间差异性

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

输出下list(g) 会让你惊讶的。结果是8

对于生成器的猫腻很多,在上例中in子句在声明时进行计算,但是条件语句在运行时计算,所以在运行之前,array 被重新赋值为列表 [2, 8, 22],然后在条件表达式中分别计算 1,8,15的个数大于 0 的,显然只生成了 8。

3.迭代修改字典

1
2
3
4
5
6
x = {0: None}

for i in x:
del x[i]
x[i+1] = None
print(i)

在py3中运行4次,打印0,1,2,3,4;在py2中运行8次,打印0,1,2,3,4,5,6,7。在Modifying a dictionary while iterating over it. Bug in Python dict?这个问题中有详细的解释:迭代一个字典并修改它是不允许的,应为添加或删除时,迭代视图可能会引发RuntimeError或无法遍历所有条目。

TODO: 这个问题我也没有完全理解

如果我们在forloop 时直接删除字典元素是不允许的,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
In [49]: for i in x:
...: del x[i]
...:
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-49-a5c6e73be64f> in <module>()
----> 1 for i in x:
2 del x[i]
3

RuntimeError: dictionary changed size during iteration

# add
In [53]: x = {0: None}
...:

In [54]: for i in x:
...: x[i+1] = 1
...:
...:
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-54-8910bbfd056f> in <module>()
----> 1 for i in x:
2 x[i+1] = 1
3
4

RuntimeError: dictionary changed size during iteration

也就是说在迭代过程中是不能修改字典的长度的, 而上面的例子是先删掉后添加,那么这样就没有改变字典长度,但是运行了8次就停止了,也就说删掉一个后添加一个或 添加一个然后删掉一个 是可以的。

It runs eight times because that’s the point at which the dictionary resizes to hold more keys (we have eight deletion entries, so a resize is needed). This is actually an implementation detail.

4.迭代删除列表元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
list_1 = [1, 2, 3, 4]
list_2 = [1, 2, 3, 4]
list_3 = [1, 2, 3, 4]
list_4 = [1, 2, 3, 4]

for idx, item in enumerate(list_1):
del item

for idx, item in enumerate(list_2):
list_2.remove(item)

for idx, item in enumerate(list_3[:]):
list_3.remove(item)

for idx, item in enumerate(list_4):
list_4.pop(idx)

输出如下:

1
2
3
4
5
6
7
8
>>> list_1
[1, 2, 3, 4]
>>> list_2
[2, 4]
>>> list_3
[]
>>> list_4
[2, 4]

解释:

记住,绝不要改变正在迭代的对象。正确方式应该是迭代一个对象的副本,然后修改原对象,如上面的list_3[:], id(list_3) != id(list_3[:]), 它们不会是一个对象,这样就可以放心修改了。

del, remove, pop 的区别:

  • del var_name 仅仅是删除一个变量在local或global命名空间的绑定。所以list_1的操作就是迭代一个List元素赋值给变量item, 然后删除这个引用,但是对list内的元素没影响。
  • remove: 移除第一个匹配的元素
  • pop: 删除元素并返回

为什么会输出[2,4]呢?看下边演示:

1
2
3
4
5
6
7
8
9
10
11
In [80]: for idx, item in enumerate(list_2):
...: print(idx, item)
...: list_2.remove(item)
...:
...:
...:
0 1
1 3

In [81]: list_2
Out[81]: [2, 4]

5. is的魔法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

>>> a = 257; b = 257
>>> a is b
True

>>> a, b =257,257
>>> a is b
True

注意在ipython中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

# ipython中这一步不成立
>>> a = 257; b = 257
>>> a is b
False

>>> a, b =257,257
>>> a is b
True

When you start up python the numbers from -5 to 256 will be allocated. These numbers are used a lot, so it makes sense just to have them ready.

但是对于257已经超出了256, 为什么在一行定义的时候它们指向同一内存地址呢??

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a = 257
>>> b = 257
>>> id(a)
4355865968
>>> id(b)
4355865744
>>> a = 257; b = 257
>>> id(a)
4355864592
>>> id(b)
4355864592
>>> id(a) == id(b)
True

When a and b are set to 257 in the same line, the Python interpreter creates a new object, then references the second variable at the same time. If you do it on separate lines, it doesn’t “know” that there’s already 257 as an object.

还有这种操作….:-D

1
2
3
>>> a = 10000;b=10000;c=10000;d=10000
>>> a is b is c is d
True

6.闭包的影响

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

funcs_results = [func() for func in funcs]

Output:

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

或:

1
2
3
>>> powers_of_x = [lambda x: x**i for i in range(10)]
>>> [f(2) for f in powers_of_x]
[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

详解见:Python闭包

7.列表重复N次

1
2
3
4
# Let's initialize a row
row = [""]*3 #row i['', '', '']
# Let's make a board
board = [row]*3

Output:

1
2
3
4
5
6
7
8
9
>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]

可以看出,board的元素都引用了同一内存地址。board由多个row生成的,所以每个元素都是raw的引用,如下内存分配:

8.可变对象做默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
def some_func(default_arg=[]):
default_arg.append("some_string")
return default_arg

# output:
>>> some_func()
['some_string']
>>> some_func()
['some_string', 'some_string']
>>> some_func([])
['some_string']
>>> some_func()
['some_string', 'some_string', 'some_string']

这种情况在python中很典型,主要原因就在于def 语句会立马执行,所以函数的参数就立马初始化,然而每次函数调用时,默认的可变参数不会每次都被初始化。 来做个小实验

1
2
3
4
5
6
7
8
9
10
11
default_value = lambda :print('init')
In [127]: def foo(default_arg=default_value()):
...: pass
...:
init
# 这一步可以看到def语句被执行,同时默认参数会立马被初始化
# 调用该函数,发现没有任何输出
In [128]: foo()
# 如果传递一个参数,则作为新的参数会被初始化
In [130]: foo(default_value())
init

函数的__defaults__属性来显示函数的默认参数

1
2
3
4
5
6
7
8
9
10
11
>>> some_func.__defaults__ #This will show the default argument values for the function
([],)
>>> some_func()
>>> some_func.__defaults__
(['some_string'],)
>>> some_func()
>>> some_func.__defaults__
(['some_string', 'some_string'],)
>>> some_func([])
>>> some_func.__defaults__
(['some_string', 'some_string'],)