程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python高級編程筆記(不定期更新)

編輯:Python

1 默認參數的坑

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的內容,則下次調用時,默認參數的內容就變了,不再是函數定義時的[]了。

因此,默認參數需要指向不變對象。

2 可變參數

假如我們的函數需要傳入多個(但是個數不定的)參數,可以使用可變參數*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)

3 關鍵字參數

傳入不受限制的關鍵字參數

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)的形式調用它,無論它的參數是如何定義的。

4 列表生成式

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)]

5 生成器(generator)

如果列表元素很多,比如一個列表占了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異常。

6 迭代器

可以被用在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。

7 泛函編程

泛函編程的一個特點就是,允許把函數本身作為參數傳入另一個函數,還允許返回一個函數!

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的一部分自動加到左邊

8 面向對象

訪問限制

屬性的名稱前添加兩個下劃線__。比如__name,可以通過._ClassName__name來訪問。

但是以__開頭和結尾的變量/方法,是可以訪問的特殊變量。

一個下劃線開頭的實例變量名,比如_name,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。

繼承和多態

繼承:子類的同名方法會覆蓋父類的同名方法。

多態:不同的類實現的同名方法都可以調用,不要求嚴格的繼承體系。

9 面向對象高級編程

__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


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved