Interesting in python(3)
think twice before coding
再怎么小心也不为过
小心使用return
@python 3.6.7
def func():
if 1 == 2:
return True
return_result = func()
print(return_result) # None
if return_result is not False:
print(True) # True
上面代码很好理解,1不等于2于是返回空即为None
。python任何函数都有返回值,没有在语句分支中定义或者本来就不打算return
,就返回None
。这很大程度上增强了语言的灵活性并且减少了代码量,但在注重规范表达时应该要在明确返回False
时使用return,毕竟None is not False
。
@python 3.6.7
def func():
try:
return 'hello first'
finally:
return 'hello finally'
print(func()) # hello finally
try...finally
语句中执行return,break,continue后,都会执行finally语句,而函数的返回值是由最后执行的return决定的,所以最后return的是hello finally
。
认真对待嵌套
@python 3.6.7
func_list = []
normal_list = []
for i in range(11):
def func():
return i
func_list.append(func) # 存储函数,未调用函数
normal_list.append(func()) # 存储函数返回值,调用函数
func_list_result = [func() for func in func_list] # 此时再调用
print(normal_list)
print(func_list_result)
# result
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
当在循环内部定义一个函数时,如果函数使用了循环变量,比如上面中的i
,那么所有的函数都会使用最后分配给变量的值即上面中的10
来进行计算。func_list
就是这样,而normal_list
中的函数在循环中正常执行返回变量i
的值。
那么如何推迟函数执行而达到和normal_list
一样的效果呢?
@python 3.6.7
func_list = []
for i in range(11):
def func(x=i):
return x
func_list.append(func) # 存储函数,未调用函数
func_list_result = [func() for func in func_list] # 此时再调用
print(func_list_result)
# result
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
将循环变量作为参数传给函数就行了,x
也可以换成i
,命名无所谓,但是为了表意清晰还是重命名比较好。此时函数会在内部重新定义一个局部变量,每循环一次都会存储循环变量i
。
boolean and int
@python 3.6.7
some_list = [1, 1.0, 0, True, False, [], "string"]
for item in some_list:
if isinstance(item, int):
print(item)
# result
# 1
# 0
# True
# False
从上面我们可以看出,其实boolean
是int
类型的子类。因此True
的整数值是1
,而False
的整数值是0
。
@python 3.6.7
>>> True == 1 == 1.0
True
>>> False == 0 == 0.0
True
>>> # but
...
>>> True is 1
False
>>> True = False # SyntaxError: can't assign to keyword
所以当我们做出下面的神奇操作时,请小心:
@python 3.6.7
test_dict = {}stack
test_dict[True] = 'java'
test_dict[1] = 'js'
test_dict[1.0] = 'python'
print(test_dict[True])
for key, value in test_dict.items():
print(key, value)
# result
# python
# True python
这是因为python字典以键来存储值,给相同的键赋值会覆盖之前键的值;而键的区分是通过计算hash
值。
@python 3.6.7
print(hash(True), hash(1), hash(1.0))
# result
# 1 1 1
类的属性
@python 3.6.7
class Father:
first_list = [0]
second_list = [0]
def __init__(self, x):
self.first_list = self.first_list + [x]
self.second_list += [x]
sub_one = Father(200)
print(sub_one.first_list)
print(sub_one.second_list)
# result
# [0, 200]
# [0, 200]
sub_two = Father(300)
print(sub_two.first_list)
print(sub_two.second_list)
# result
# [0, 300]
# [0, 200, 300]
之所以会出现上面的原因是因为python类变量和实例变量是通过字典来处理的。如果在当前类的字典中找不到就去它的父类去找,而+=
运算符会原地修改可变对象,不会重新创建新的对象,因此(类的创建省略,和上面一样):
@python 3.6.7
print(sub_one.first_list is sub_two.first_list) # False
print(sub_one.second_list is sub_two.second_list) # True
# and you will see
print(sub_one.second_list) # [0, 200, 300]
print(Father.second_list) # [0, 200, 300]
对yield不要太随心所欲
@python 3.6.7
iterable = ['a', 'b']
def do_something(no_use):
return 'do_something'
iter1 = [(yield x) for x in iterable]
print(list(iter1))
# ['a', 'b']
iter2 = ((yield x) for x in iterable)
print(list(iter2))
# ['a', None, 'b', None]
iter3 = (do_something((yield x)) for x in iterable)
print(list(iter3))
# ['a', 'do_something', 'b', 'do_something']
iter4 = [do_something((yield x)) for x in iterable]
print(list(iter4))
# ['a', 'b']
this is a bug!
对此stackoverflow中给出了完善的答复,值得一提的是这个bug在python 3.8
中已经修复,直接报语法错误_。但对于我们来说或许真正应该学会的是如何正确的理解和使用快捷舒适的python。下面是对yield
的官方引用:
The yield expression is only used when defining a generator function and thus can only be used in the body of a function definition.
可变和不可变
@python 3.6.7
>>> test_tuple = ([1, 2], [2, 3])
>>> test_tuple[1].append(1000)
>>> test_tuple
([1, 2], [2, 3, 1000])
>>> test_tuple[1] += [2000]
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> test_tuple # go on to check value
([1, 2], [2, 3, 1000, 2000])
不可变序列,不可变序列的对象一旦创建就不能再改变. (如果对象包含对其他对象的引用,则这些其他对象可能是可变的并且可能会被修改; 但是,由不可变对象直接引用的对象集合不能更改.)
首先来我们重温一下+=
运算符,对于可变对象例如:list
会直接在原对象上进行修改;而对于不可变对象例如:tuple
则a += b
等价于a = a + b
,会产生新的对象然后赋值给原来的变量引用即a
。
所以+=
操作符会在原地修改列表,但元素赋值操作=
对于元组并不工作,因此会抛出异常,但此时元素的值已经改变了。因此我们可以这样认为,实际上+=
是两个操作:
@python 3.6.7
test_tuple[1] += [2000]
# means
test_tuple[1].extend([2000]) # it works, change list
test_tuple[1] = test_tuple[1] # error
一些有趣的赋值
@python 3.6.7
>>> a, b = a[b] = {}, 2
>>> a
{2: ({...}, 2)}
>>> b
2
上面赋值语句我们可以这样来看,在右边最后一个等号前面都称为目标列表,即目标列表是a, b
和a[b]
;而表达式只能有一个即{}, 2
。表达式计算结束后,会将其值从左到右分配给目标列表。
因此a, b
会先分别赋值为{}
和2
;接着再将{}, 2
赋值给a[b]
即a[2]
。那么现在就是将字典中键为2
的值设置为元组{}, 2
,此时会造成循环引用,因为{}
和之前的a
都引用了相同的对象,所以结果是{2: ({...}, 2)}
。(输出中的{...}
指与a
引用了相同的对象)
@python 3.6.7
# same as this
>>> a, b = {}, 2
>>> a[b] = a, b
>>> a
{2: ({...}, 2)}
>>> a[b][0] is a # same object
True
网友评论