def add_end(L=[]):
L.append("end")
return L
正常傳參數時結果正常。
例1
if __name__ == "__main__":
print(add_end())
print(add_end([1]))
返回結果是:
['end']
[1, 'end']
例2
if __name__ == "__main__":
print(add_end())
print(add_end([1]))
返回結果是:
['end']
['end']
但是如果:
if __name__ == "__main__":
print(add_end())
print(add_end())
則返回結果是:
['end']
['end', 'end']
因為Python函數在定義的時候,默認參數L
的值就被計算出來了,即[]
,因為默認參數L
也是一個變量,它指向對象[]
,每次調用該函數,如果改變了L
的內容,則下次調用時,默認參數的內容就變了,不再是函數定義時的[]
了。
因此,默認參數需要指向不變對象。
假如我們的函數需要傳入多個(但是個數不定的)參數,可以使用可變參數*args
。例如:
sum(1,2,3)
sum(1,2,3,4,5,6)
sum()
對應的函數定義為:
def sum(*args):
s = 0
for i in args:
s += i
return s
但是,假如說我們已經有一個列表或者tuple,要傳進去的話,可以采用下列形式:
l = [1,2,3,4,5,6]
sum(*l)
傳入不受限制的關鍵字參數
def enroll(name, age, **kwargs):
for key, value in kwargs.items():
print(key, value)
if __name__ == "__main__":
enroll("Pgl", 23, nation="CN", grade=1)
傳入受限制的關鍵字參數
def enroll(name, age, *, nation, grade):
''' 這意味著我們只能接受關鍵字nation和grade '''
pass
對於任意函數,都可以通過類似func(*args, **kw)
的形式調用它,無論它的參數是如何定義的。
python內生的、簡單卻強大的創建list的生成式。
幾個例子:
# 生成[1*1, 2*2, ..,100*100]:
[x*x for x in range(1, 101)]
# for循環後面還可以加上if判斷,這樣我們就可以篩選出僅偶數的平方:
[x*x for x in range(1, 101) if x%2 == 0]
# 可以使用兩層循環,生成全排列:
[m+n for m in "python" for n in "PYTHON"]
# if...else 的用法:
[x if x % 2 == 0 else -x for x in range(0, 100)]
如果列表元素很多,比如一個列表占了500M,那麼完整地生成這個列表之後再操作肯定是不劃算的。
此時,列表元素可以按照算法推算出來,不必創建完整的list,節省了大量的空間。generator保存的就是這個算法。
創建generator的方法:
# 列表生成式的 [] 改成 ()
L = (x*x for x in range(10))
next(L)
next(L)
next(L)
next(L)
在計算到最後一個元素,沒有更多的元素時,拋出StopIteration
的錯誤。
generator是可迭代對象,因此可以使用for
循環:
g = (x*x for x in range(10))
for n in g:
print(n)
某種程度上說,生成器表達的就是序列的推算規則。
比如,我們寫一個Fibonacci數列的推算:
# 如果一個函數包含yield關鍵字,那麼這個函數就是一個生成器。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n += 1
return
生成器運行時在碰到yield之後,會將yield的值返回掉,但是下一次又會從yield處繼續執行。直到沒有多余的yield之後,會拋出StopIteration
異常。
可以被用在for循環裡的對象:
集合數據類型:list, tuple, dict, set, 字符串等。
生成器類型:生成器和帶yield的函數。
都被稱為可迭代對象Iterable。
可以使用isinstance()
判斷一個對象是否是Iterable對象:
from collections.abc import Iterable
isinstance([1,2,3], Iterable)
可以被next()
函數調用並且不斷返回下一個值的對象稱為迭代器:Iterator
。
ls = [1, 2, 3]
print(isinstance(ls, Iterator))
# False
next(ls)
#Traceback (most recent call last):
# File "d:\vscode-workspace\hlwd.py", line 5, in <module>
# next(ls)
#TypeError: 'list' object is not an iterator
生成器都是 Iterator 對象,但是list, dict, str 是 Iterable ,而不是Iterator。
原因:Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。
因此,我們可以把這個數據流看做是一個有序序列,卻不能提前知道序列的長度,只能不斷通過next()
函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。
Iterator甚至可以存儲一個無限大的數據流,比如 N \mathbb{N} N。但是list不行。
總結:
Iterator惰性調用next方法(順序生成一些東西)。
Iterable可以變成Iterator。
泛函編程的一個特點就是,允許把函數本身作為參數傳入另一個函數,還允許返回一個函數!
map/reduce/filter函數
map(func, Iterable)
:把規則作用到 Iterable 上,把結果作為Iterator返回。
返回的結果是個惰性序列,需要轉換成list之類的東西。
所以,map()
作為高階函數,事實上它把運算規則抽象了。
reduce(func, Iterable)
:把一個函數作用在一個序列上,reduce把結果和序列的下一個元素做累計計算。
例如,把整數字符按順序寫成數:
from functools import reduce
def a(x, y):
return x + y
print(reduce( a, ['1', '3', '5', '7', '9'] ))
>>> 13579
filter(func, list)
func
參數確定判斷准則,只把list裡滿足條件的東西拿出來。返回的是一個惰性的Iterator
。
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 結果: [1, 5, 9, 15]
一個更經典的示例:生成素數
def _odd_iter():
n = 1
while True:
n = n + 2
yield n
def _not_divisible(n):
return lambda x: x % n > 0
def primes():
yield 2
it = _odd_iter() # 初始序列
while True:
n = next(it) # 返回序列的第一個數
yield n
it = filter(_not_divisible(n), it) # 構造新序列
# 打印1000以內的素數:
for n in primes():
if n < 1000:
print(n)
else:
break
排序函數
sorted(list, key=func)
根據key的返回值來對元素進行排序。
sorted([36, 5, -12, 9, -21], key=abs)
高階函數的抽象能力是非常強大的,而且,核心代碼可以保持得非常簡潔。
閉包——返回一個函數
在一個函數裡再定義函數。返回的是函數中定義的函數。
def f(a):
def g():
for i in range(a):
print(i)
return g
這裡需要注意:返回閉包時返回函數不要引用任何循環變量,或者後續會發生變化的變量。
匿名函數lambda
略
裝飾器
函數是對象,可以被賦值給變量。因此,通過變量也可以調動函數。
但是函數有__name__
屬性,可以拿到函數的名字,而變量只是一個標簽或者說是對對象的引用。
比如,變量f
指向print函數,print.__name__
是print,那麼f.__name__
仍然是print。
我們希望在一個函數執行前和執行後能夠再做一些事情,比如執行前輸出點東西,那可以用到decorator。
import time
def tictoc(f):
def wrapper(*args, **kwargs):
t1 = time.time()
r = f(*args, **kwargs)
t2 = time.time()
print(t2 - t1)
return r
return wrapper
@tictoc
print("hello world!")
相當於執行了tictoc(print("hello world!"))
如果decorator裡也需要加參數,比如根據不同的函數打印不同的結果,那麼可以再套一層函數:
def log(text):
def tictoc(f):
def wrapper(*args, **kwargs):
t1 = time.time()
r = f(*args, **kwargs)
t2 = time.time()
print(f"running {
f.__name__} with {
text}")
print(t2 - t1)
return r
return wrapper
return tictoc
@log("logging")
print("hello world")
我們還需要更改原始參數的__name__
等屬性,否則依賴於__name__
的代碼執行會出錯。
可以用functools.wraps
更改一手。
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
偏函數
int('12345', base=2)
我們可以定義一個int2
函數,把base固定,從而簡化調用。
def int2(x, base=2):
return int(x, base)
這種固定一個函數的某些參數使得函數的調用變得更簡單的函數稱為偏函數。
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
創建偏函數時,實際上可以接收函數對象、*args
和**kw
這3個參數。
例如:
import functools
int2 = functools.partial(int, base=2)
k = {
'base': 10}
int2('2134643', **k)
例如:
max2 = functools.partial(max, 10)
# 實際上會把10作為*args的一部分自動加到左邊
訪問限制
屬性的名稱前添加兩個下劃線__
。比如__name
,可以通過._ClassName__name
來訪問。
但是以__
開頭和結尾的變量/方法,是可以訪問的特殊變量。
一個下劃線開頭的實例變量名,比如_name
,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。
繼承和多態
繼承:子類的同名方法會覆蓋父類的同名方法。
多態:不同的類實現的同名方法都可以調用,不要求嚴格的繼承體系。
__slots__
在動態語言python中,可以給class綁定任何的屬性和方法——動態語言的靈活性。
class Student():
pass
# 綁定屬性
s = Student()
s.name = "Michael"
print(s.name)
def set_age(self, age):
self.age = age
# 給一個實例綁定方法,對另一個實例無效
from types import MethodType
s.set_age = MethodType(set_age, s)
# 給class綁定方法,允許我們在程序運行的過程中動態給class加上功能
def set_score(self, score):
self.score = score
Student.set_score = set_score
# slots允許我們指定對class實例添加的屬性:
class UGStudents(Student):
__slots__ = ("name", "age")
s = UGStudents()
s.score = 99 # 報錯
# 類中定義的__slots__對當前類起作用,對子類不起作用。
# 子類中起東西
@property
需求:檢查參數,並且用類似於屬性的簡單形式訪問變量。
使用@property裝飾器。對於類的方法,裝飾器一樣可用。
class Student():
''' 僅僅定義屬性的話就是只讀的變量 '''
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError("value should be int")
self._score = value
注意:屬性的方法名不能和實例變量重名。會爆棧。
class Student(object):
# 方法名稱和實例變量均為birth:
@property
def birth(self):
return self.birth
調用birth屬性時,會轉成方法,返回時視為調用屬性,再轉成方法…於是棧爆了。
多重繼承
略
定制類
__str__
打印得好看一些罷了
__repr__
返回程序開發者看到的字符串,為調試服務。
__iter__
返回一個迭代對象(用於for循環,接著調用__next__
方法惰性計算下一個值)
__getitem__
, __setitem__
, __delitem__
__getitem__
支持索引。__setitem__
也支持索引。設置對應索引的值。
但是復雜的切片功能需要另外設計。
__getattr__
在沒有找到屬性的情況下,才調用__getattr__
,已有的屬性。
任意調用如s.abc
都會返回None
,這是因為我們定義的__getattr__
默認返回就是None