WTF Python:开启懵逼模式第二弹
wtf-10 循环内的函数
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]
print(results)
print(funcs_results)
[0, 1, 2, 3, 4, 5, 6]
[6, 6, 6, 6, 6, 6, 6]
按照我们的猜想,funcs_results结果应该是[0, 1, 2, 3, 4, 5, 6],不应该全是6。
分析:
在循环体中函数使用的是循环体中的变量,对于每一次循环而言,函数绑定的是变量而不是变量的值。所以在程序运行时,所有的函数都使用的变量最新赋值。
怎么实现我们的想法,让每次循环发生变化?
funcs = []for x in range(7):
#函数有参数
def some_func(x=x):
return x
funcs.append(some_func)
funcs_results = [func() for func in funcs]
print(funcs_results)
[0, 1, 2, 3, 4, 5, 6]
wtf-11 循环变量超出作用域
Loop variables leaking out of local scope!翻译不知道对不对
for x in range(7):
if x == 6:
print(x, ': for x inside loop')
#但是x没有在循环体外定义,怎么x变成全局变量了?
print(x, ': x in global')
6 : for x inside loop
6 : x in global
# 定义一个全局变量x
x = -1
for x in range(7):
if x == 6:
print(x, ': for x inside loop')
#还是不懂为啥出现这情况
print(x, ': x in global')
6 : for x inside loop
6 : x in global
在Python中,for循环使用它们存在的范围,并在结束循环时留下循环变量。如果我们在全局命名空间中前显式地定义for循环变量,这些循环变量就会得到调用。在这种情况下,它会重新绑定现有的变。
有点类似wtf10
x = 1
print([x for x in range(5)])
print(x, ': x in global')
[0, 1, 2, 3, 4]
1 : x in global
wtf-12 小心默认的可变对象
def some_func(default_arg=[]):
default_arg.append("some_string")
return default_arg
print(some_func())
print(some_func())
print(some_func())
['some_string']
['some_string', 'some_string']
['some_string', 'some_string', 'some_string']
分析:
Python中函数的默认可变参数在每次调用该函数时都不会被初始化。 而是使用最近给它们分配的值作为默认值。
当我们明确地将[]传递给somefunc作为参数时,defaultarg变量的默认值不被使用,因此该函数没有按预期方式返回。
#改成这样就可以如您所愿
def some_func(default_arg):
default_arg.append("some_string")
return default_arg
print(some_func([]))
print(some_func([]))
print(some_func([]))
['some_string']
['some_string']
['some_string']
#也可以在函数内加判断,初始化
def some_func(default_arg=None):
if not default_arg:
default_arg = []
default_arg.append("some_string")
return default_arg
wtf-13 近似的形式却截然不同
a = [1, 2, 3, 4]
b = a
a = a + [5, 6, 7, 8]
print(a)
print(b)
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4]
分析:
a = [1,2,3,4],a拥有一块内存地址。
b = a,使得b也绑定跟a一样的内存地址,也就是此时a与b是内存地址与值都相同。
a = a + [5,6,7,8]。此时相当于新建了一个变量a,解除了原来a的内存地址。此时a与b不相同了
a = [1, 2, 3, 4]
b = a
a += [5, 6, 7, 8]
print(a)
print(b)
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
分析
a = [1,2,3,4],a拥有一块内存地址。
b = a,使得b跟着a,拥有一样的内存地址,也就是此时a与b是内存地址与值都相同。
a += [5,6,7,8]。此时原地对内存进行修改,内存地址没变,数据发生变化。b也跟着a一起变
wtf-14 修改不可变型对象
#按照我们的理解,元组一旦创建就不可修改。
some_tuple = ("A", "tuple", "with", "values")
another_tuple = ([1, 2], [3, 4], [5, 6])
some_tuple[2] = "change this"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-57-a46a5b5ae67c> in <module>()
----> 1 some_tuple[2] = "change this"
TypeError: 'tuple' object does not support item assignment
#对不可对象内的可变对象的引用,此时修改不会报错
another_tuple[2].append(1000)
([1, 2], [3, 4], [5, 6, 1000])
#不可变对象another_tuple引用,会报错 ,但修改已发生
another_tuple[2] += [99, 999]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-59-d07c65f24a63> in <module>()
----> 1 another_tuple[2] += [99, 999]
TypeError: 'tuple' object does not support item assignment
#虽然上一行报错,但实际上发生变化了another_tuple
([1, 2], [3, 4], [5, 6, 1000, 99, 999])
总结:
不可变序列类型的对象一旦创建就不能改变。 (如果对象包含对其他对象的引用,则这些其他对象可能是可变,此时不可变序列可能被修改;但是,由不可变对象直接引用的对象的集合不能更改。)
wtf-15 作用域未定义变量
a = 1
def some_func():
return a
def another_func():
a += 1
return a
print(some_func())
1
print(another_func())
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-64-3b85aab5eec8> in <module>()
----> 1 print(another_func())
<ipython-input-63-c6c17295dd3e> in another_func()
4
5 def another_func():
----> 6 a += 1
7 return a
8
UnboundLocalError: local variable 'a' referenced before assignment
分析:
当对作用域中的变量进行赋值操作时,该变量变为局部变量。所以a变成了anotherfunc的局部变量,anotherfunc函数没有给局域变量a初始化,所以调用函数时会报错。
#修正方法,作用域内初始化,声明a为全局变量a
def another_func():
global a
a += 1
return a
print(another_func())
2
wtf-16 到处返回return
def some_func():
try:
return 'from_try'
finally:
return 'from_finally'
print(some_func())
from_finally
分析:
当break、return、continue在try...finally..对语句中的try语句中执行时,finally语句中的也会被执行的。
函数的返回值由执行的最后一个返回语句确定。 由于finally子句总是执行,所以在finally子句中执行的return语句将永远是执行的最后一个。
wtf-17 Python中逻辑运算律
print('True is False == False:',True is False == False)
print('False is False is False:',False is False is False)
print('1 > 0 < 1',1 > 0 < 1)
print('(1 > 0) < 1',(1 > 0) < 1)
print('1 > (0 < 1)',1 > (0 < 1))
True is False == False: False
False is False is False: True
1 > 0 < 1 True
(1 > 0) < 1 False
1 > (0 < 1) False
分析:
False is False is False 相当于 (False is False) and (False is False)
True is False == False 相当于 True is False and False == False ,因为第一部分 (True is False) 相当于False, 第二部分(False == False)相当于True,总体结合起来(False and True)是False.
1 > 0 < 1 相当于 1 > 0 and 0 < 1 ,结果True.
其他的类似,都是很像数学里的逻辑运算律。
x = 5
class SomeClass:
x = 17
y = (x for i in range(10))
print(list(SomeClass.y)[0])
5
x = 5
class SomeClass:
x = 17
y = [x for i in range(10)]
print(SomeClass.y[0])
5
分析:
1、列表和生成器都有自己的作用域
2、嵌套在类内的列表或生成器中的变量忽略类内绑定的变量。如在本例子中的 推导式中的x忽略类内绑定的x。
wtf-18 奇葩的原地修改对象
some_list = [1, 2, 3]
some_dict = {
"key_1": 1,
"key_2": 2,
"key_3": 3}
some_list = some_list.append(4)
some_dict = some_dict.update({"key_4": 4})
print(some_list)
print(some_dict)
None
None
分析:
大多数修改对象(如list.append,dict.update,list.sort等)的方法会在原位修改对象并返回None。 其背后的理由是如果可以在原地进行操作,则避免制作对象的副本(新建内存空间)来提高性能
wtf-19 类属性与实例属性
class A:
x = 1
class B(A):
pass
class C(A):
pass
print(A.x, B.x, C.x)
1 1 1
#相当于B新建了xB.x = 2
print(A.x, B.x, C.x)
1 2 1
#A新建了x,因为C继承A。所以A、C一样。#但因为B自己独立新建了,已经摆脱了A
A.x = 3
print(A.x, B.x, C.x)
3 2 3
#实例化A,返回的x一样的
a = A()
print(a.x, A.x)
3 3
#+=操作,相当于a原地修改实例a。
a.x += 1
print(a.x, A.x)
4 3
class SomeClass:
some_var = 15
some_list = [5]
another_list = [5]
def __init__(self, x):
self.some_var = x + 1
self.some_list = self.some_list + [x]
self.another_list += [x]
some_obj = SomeClass(420)
some_obj.some_list
[5, 420]
some_obj.another_list
[5, 420]
another_obj = SomeClass(111)
another_obj.some_list
[5, 111]
another_obj.another_list
[5, 420, 111]
print(id(some_obj),id(another_obj))
print(id(some_obj.some_list),id(another_obj.some_list))
#but这里却一样
print(id(some_obj.another_list),id(another_obj.another_list))
4371905224 4371906512
4371728648 4371728520
4371874696 4371874696
分析:
类的属性和实例的属性是储存在类字典中,当当前类字典中没有这个属性,那么就要找该类的父类。
+=相当于原地更改变量,并没有新建对象。所以改变一个实例的属性时,会影响到另一个实例的属性;
历史文章:
数据采集
【视频】有了selenium,小白也可以自豪的说:“去TMD的抓包、cookie”
【视频】快来get新技能--抓包+cookie,爬微博不再是梦