BeginMan blog

wtfpython 第二弹

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

相关文章:

1.字典键的隐式转换

some_dict = {}
some_dict[5.5] = "Ruby"
some_dict[5.0] = "JavaScript"
some_dict[5] = "Python"

输出:

>>> some_dict[5.5]
"Ruby"
>>> some_dict[5.0]
"Python"
>>> some_dict[5]
"Python"

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

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

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

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.迭代修改字典

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 时直接删除字典元素是不允许的,如下:

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.迭代删除列表元素

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)

输出如下:

>>> 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]呢?看下边演示:

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的魔法

>>> 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中:

>>> 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, 为什么在一行定义的时候它们指向同一内存地址呢??

>>> 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

>>> a = 10000;b=10000;c=10000;d=10000
>>> a is b is c is d
True

6.闭包的影响

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:

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

或:

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

详解见:Python闭包

7.列表重复N次

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

Output:

>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]

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