WTF Python: 开启你的懵逼模式
What the f*ck Python?
Python很流行很易学,但有时,Python代码片段的结果可能让小白们直接懵逼,现在大邓跟你一起开启懵逼模式
wtf-1 开胃菜hash
some_dict = {}
some_dict[5.5] = "Ruby"
some_dict[5.0] = "JavaScript"
some_dict[5] = "Python"
print(some_dict[5.5])
print(some_dict[5.0])
print(some_dict[5])
分析:
Ruby
Python
Python
Python字典检查相等,并比较key的hash值,以确定两个键是否相同。
同值不变的对象总是在Python中相同的hash值。
print(5==5.0)
print(hash(5)==hash(5.0))
True
True
wtf-2 生成器的if与in运行时间不同
array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]
print(list(g))
分析:
[8]
在Python的生成器语法中,in语句是在声明时就计算执行的,而if语句则是在运行时(调用g时)计算执行的。
也就是说,一开始我们告诉Python,当我们运行g时,g生成器in语句是在声明时确定有array有1、8、15,但是if语句是基于执行时查看发现array只有2、8、22,经过计算只有8出现1次。所以返回[8]。
tips:大家自己揣摩把,我解释的也不是很给力。
wtf-3 迭代时修改字典
x = {0: None}
for i in x:
del x[i]
x[i+1] = None
print(i)
0
1
2
3
4
分析:
只运行了5次,就停止了。
Python不支持迭代时对字典进行编辑(其实迭代时候也不能更改字典的容量)。
wtf-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)
print(list_1)
print(list_2)
print(list_3)
print(list_4)
分析:
[1, 2, 3, 4]
[2, 4]
[]
[2, 4]
在迭代ing时对自己进行修改是万万不可取的!!正确的做法是,使用备份数据迭代时对原数据进行修改。如list3,使用的是list3[:]。
some_list = [1, 2, 3, 4]
print(id(some_list))
print(id(some_list[:]))
4369310792
4369313544
del remove pop的区别
del varname仅仅删除局部或全局命名空间的varname所绑定的数据,这也是为什么list_1没有收到影响。
remove remove函数移除的是第一个匹配(match)的元素。
pop pop函数移除指定索引index值的元素,并且返回该元素。
为什么输出的是[2,4]?
第一次迭代 list2或者list4 为[1,2,3,4],index=0的元素为1。移除1
第二次迭代 list2或者list4 为[2,3,4],index=1的元素为3。移除3
第三次迭代 list2或者list4 为[3,4],没有index=2的元素,不再移除元素
所以,最后list2或者list4输出的是[2,4]
wtf-5 字符串后的反斜杠
print("\\ some string \\")
print(r"\ some string")
运行正常
\ some string \
\ some string
但这里,却运行出问题
print(r"\ some string \")
File "<ipython-input-27-605fb8dcbdb5>", line 1
print(r"\ some string \")
^
SyntaxError: EOL while scanning string literal
分析:
在raw字符串(以r为前缀)中,反斜杠没有特殊含义。
但是反斜杠不能再字符串末尾。
wtf-6 创建句型字符串
这部分不是python的奇葩问题,而是仅仅是为了学习运行效率。
import timeit
def add_string_with_plus(iters):
s = ""
for i in range(iters):
s += "xyz"
#当s长度等于3倍的iters时,断言。程序停止。
assert len(s) == 3*iters
def add_string_with_format(iters):
fs = "{}"*iters
s = fs.format(*(["xyz"]*iters))
assert len(s) == 3*iters
def add_string_with_join(iters):
l = []
for i in range(iters):
l.append("xyz")
s = "".join(l)
assert len(s) == 3*iters
%timeit add_string_with_plus(10000)
%timeit add_string_with_format(10000)
%timeit add_string_with_join(10000)
分析:
1000 loops, best of 3: 1.63 ms per loop
1000 loops, best of 3: 778 µs per loop
1000 loops, best of 3: 1.44 ms per loop
从上面的例子我们以后要注意,拼接大量字符串时,+ 是很低效率的。最高效的是format,其次是join。
wtf-7 +=与+效率PK
def addequal():
s1 = ''
for i in range(10000):
s1 += str(i) + str(i+1)
return s1
def addonly():
s1 = ''
for i in range(10000):
s1 = s1 + str(i) + str(i+1)
return s1
%timeit addequal()
%timeit addonly()
分析:
100 loops, best of 3: 8.48 ms per loop
10 loops, best of 3: 28.5 ms per loop
+= 比 + 快很多。+= 方法是在s1原地进行赋值覆盖操作,而+方法,相当于重新新建一个s1,并把值赋值给s1。所以+=更高效。
wtf-8 is 与 ==
a = 256
b = 256
print('256 id',id(256))
print('a id:',id(a))
print('b id:',id(b))
print('a is b:',a is b)
print('a == b:',a == b)
256 id 4297632096
a id: 4297632096
b id: 4297632096
a is b: True
a == b: True
再来看看257
c = 257
d = 257
print('257 id',id(257))
print('c id:',id(c))
print('d id:',id(d))
print('c is d:',c is d)
print('c == d:',c == d)
257 id 4370421936
c id: 4370422000
d id: 4370420880
c is d: False
c == d: True
分析:
is is判断的是左右两边变量指向的是不是同一个对象(我们这里可以用变量的id判断是不是同一个对象。)
== ==判断的是左右两边的变量值是否相等。
当我们一启动python时,[-5,256]范围内的数值会被立刻分配出去。在上面的代码我们可以看到256、a、b的id都是相同的。
wtf-9 is not ... 不是 is (not ...)
print('not none' is not None)
print('not none' is (not None))
True
False
分析:
如果is not两边指向的是同一个对象,则返回False,否则返回True