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任何函數(shù)都有返回值,沒有在語句分支中定義或者本來就不打算return,就返回None。這很大程度上增強了語言的靈活性并且減少了代碼量,但在注重規(guī)范表達時應該要在明確返回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語句中執(zhí)行return,break,continue后,都會執(zhí)行finally語句,而函數(shù)的返回值是由最后執(zhí)行的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) # 存儲函數(shù),未調(diào)用函數(shù)
normal_list.append(func()) # 存儲函數(shù)返回值,調(diào)用函數(shù)
func_list_result = [func() for func in func_list] # 此時再調(diào)用
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]
??當在循環(huán)內(nèi)部定義一個函數(shù)時,如果函數(shù)使用了循環(huán)變量,比如上面中的i,那么所有的函數(shù)都會使用最后分配給變量的值即上面中的10來進行計算。func_list就是這樣,而normal_list中的函數(shù)在循環(huán)中正常執(zhí)行返回變量i的值。
??那么如何推遲函數(shù)執(zhí)行而達到和normal_list一樣的效果呢?
@python 3.6.7
func_list = []
for i in range(11):
def func(x=i):
return x
func_list.append(func) # 存儲函數(shù),未調(diào)用函數(shù)
func_list_result = [func() for func in func_list] # 此時再調(diào)用
print(func_list_result)
# result
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
??將循環(huán)變量作為參數(shù)傳給函數(shù)就行了,x也可以換成i,命名無所謂,但是為了表意清晰還是重命名比較好。此時函數(shù)會在內(nèi)部重新定義一個局部變量,每循環(huán)一次都會存儲循環(huán)變量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的整數(shù)值是1,而False的整數(shù)值是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字典以鍵來存儲值,給相同的鍵賦值會覆蓋之前鍵的值;而鍵的區(qū)分是通過計算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]
??之所以會出現(xiàn)上面的原因是因為python類變量和實例變量是通過字典來處理的。如果在當前類的字典中找不到就去它的父類去找,而+=運算符會原地修改可變對象,不會重新創(chuàng)建新的對象,因此(類的創(chuàng)建省略,和上面一樣):
@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中已經(jīng)修復,直接報語法錯誤_。但對于我們來說或許真正應該學會的是如何正確的理解和使用快捷舒適的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])
??不可變序列,不可變序列的對象一旦創(chuàng)建就不能再改變. (如果對象包含對其他對象的引用,則這些其他對象可能是可變的并且可能會被修改; 但是,由不可變對象直接引用的對象集合不能更改.)
??首先來我們重溫一下+=運算符,對于可變對象例如:list會直接在原對象上進行修改;而對于不可變對象例如:tuple則a += b等價于a = a + b,會產(chǎn)生新的對象然后賦值給原來的變量引用即a。
??所以+=操作符會在原地修改列表,但元素賦值操作=對于元組并不工作,因此會拋出異常,但此時元素的值已經(jīng)改變了。因此我們可以這樣認為,實際上+=是兩個操作:
@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。表達式計算結(jié)束后,會將其值從左到右分配給目標列表。
??因此a, b會先分別賦值為{}和2;接著再將{}, 2賦值給a[b]即a[2]。那么現(xiàn)在就是將字典中鍵為2的值設置為元組{}, 2,此時會造成循環(huán)引用,因為{}和之前的a都引用了相同的對象,所以結(jié)果是{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