1. 程式人生 > 其它 >cook book:7:函式+8:類與物件

cook book:7:函式+8:類與物件

1:可接受任意數量引數的函式  *args

# 為了能讓一個函式接受任意數量的位置引數,可以使用一個*引數
def avg(first, *args):
    return (first + sum(args)) / (1 + len(args))


print(avg(1, 2))        # 1.5
print(avg(1, 2, 3, 4))  # 2.5

# *args接收後面的所有的位置引數封裝成一個元組到args這個變數裡
# args是由所有其他位置引數組成的元組。然後我們在程式碼中把它當成了一個序列來進行後續的計算
# 接受任意數量的關鍵字引數的函式,**kwargs
import html def make_element(name, value, **attrs): keyvals = [' %s="%s"' % item for item in attrs.items()]  # [' size="large"', ' quantity="6"'] attr_str = ''.join(keyvals)                # size="large" quantity="6" str型別 element = '<{name}{attrs}>{value}</{name}>'.format( name
=name, attrs=attr_str, value=html.escape(value)) return element # 建立 '<item size="large" quantity="6">Albatross</item>' print(make_element('item', 'Albatross', size='large', quantity=6)) # 建立 '<p>&lt;spam&gt;</p>' print(make_element('p', '<spam>
')) # **attrs接收所有的關鍵字引數封裝成一個字典到attrs裡面 # attrs是一個包含所有被傳入進來的關鍵字引數的字典
# 同時接受任意數量的位置引數和關鍵字引數,同時使用*和**
def anyargs(*args, **kwargs):
    print(args)         # args是一個元組
    print(kwargs)     # kwargs是一個字典
 
使用這個函式時,所有位置引數會被放到args元組中,所有關鍵字引數會被放到字典kwargs中。
# 一個*引數只能出現在函式定義中最後一個位置引數後面,而 **引數只能出現在最後一個引數。在*引數後面仍然可以定義其他引數
def a(x, *args, y):
    pass
print(a(1, 2, 3, 4, 5, y=10))  # 只能這麼呼叫
def b(x, *args, y, **kwargs): pass print(a(1, 2, 3, 4, 5, y=10, c=20, b=30))  # 只能這麼呼叫 強制關鍵字引數

2:只接受關鍵字引數的函式  希望函式的某些引數強制使用關鍵字引數傳遞

# 將強制關鍵字引數放到某個*引數或者單個*後面就能達到這種效果
def recv(maxsize, *, block):
    pass

# recv(1024) # TypeError  還需要傳一個引數
# recv(1024, 2048, block=True)  # TypeError  報錯,*不是*args,*不接受引數只是強制後面得引數和關鍵字引數
recv(1024, block=True)  # Ok

# 在接受任意多個位置引數的函式中指定關鍵字引數
def minimum(*values, clip=None):
    m = min(values)
    if clip is not None:
        m = clip if clip > m else m
    return m

print(minimum(1, 5, 2, -5, 10))  # -5
print(minimum(1, 5, 2, -5, 10, clip=0))  # 0

# *vaues接收多個位置引數,clip=None接收一個關鍵字引數
# 使用強制關鍵字引數會比使用位置引數表意更加清晰,程式也更加具有可讀性
msg = recv(1024, False)

# 如果呼叫者對recv函式並不是很熟悉,那他肯定不明白那個False引數到底來幹嘛用的。 但是,如果程式碼變成下面這樣子的話就清楚多了
msg = recv(1024, block=False)
# 使用強制關鍵字引數也會比使用**kwargs引數更好,因為在使用函式help的時候輸出也會更容易理解
help(recv)

3:給函式引數增加元資訊  寫好了一個函式,然後想為這個函式的引數增加一些額外的資訊,讓其他使用者就能清楚的知道這個函式應該怎麼使用

# 函式引數註解提示程式設計師應該怎樣正確使用這個函式
def add(x: int, y: int) -> int:
    return x + y
x是int,y是int ->返回是int
# python直譯器不會對這些註解新增任何的語義。它們不會被型別檢查,執行時跟沒有加註解之前的效果也沒有任何差距。
然而,對於那些閱讀原始碼的人來講就很有幫助。第三方工具和框架可能會對這些註解新增語義。同時它們也會出現在文件中
輸出: Help on function add in module __main__: add(x: int, y: int) -> int None
可以使用任意型別的物件給函式添加註解(例如數字,字串,物件例項等等),不過通常來講使用類或者字串會比較好點
# 函式註解只儲存在函式的 __annotations__ 屬性中
def add(x: int, y: int) -> int:
    return x + y

print(add.__annotations__)
輸出:
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

# 註解的使用方法可能有很多種,但是它們的主要用途還是文件。 因為python並沒有型別宣告,
通常來講僅僅通過閱讀原始碼很難知道應該傳遞什麼樣的引數給這個函式。
這時候使用註解就能給程式設計師更多的提示,讓他們可以正確的使用函式

4:返回多個值的函式  構造一個可以返回多個值的函式  return一個元組就行了

# return一個元組就行了
def func():
    return 1, 2, 3

a = func()
print(a)  # (1, 2, 3)

a, b, c = func()
print(a, b, c)  # 1 2 3

# 儘管myfun()看上去返回了多個值,實際上是先建立了一個元組然後返回的。 
# 這個語法看上去比較奇怪,實際上我們使用的是逗號來生成一個元組,而不是用括號
a = (1, 2)
b = 1, 2
print(a == b, b)  # True (1, 2)

# 當我們呼叫返回一個元組的函式的時候 ,通常我們會將結果賦值給多個變數, 
其實這就是元組解包。返回結果也可以賦值給單個變數, 這時候這個變數值就是函式返回的那個元組本身了
def myfun(): return 1, 2, 3 x = myfun() print(x) # (1, 2, 3) a, b, c = x print(a, b, c) # 1 2 3

5:定義有預設引數的函式  定義一個函式或者方法,它的一個或多個引數是可選的並且有一個預設值

# 定義一個有可選引數的函式是非常簡單的,直接在函式定義中給引數指定一個預設值,並放到引數列表最後就行了
def spam(a, b=42):
    print(a, b)
spam(1)    # 1, 42
spam(1, 2)    # 1, 2

# 如果預設引數是一個可修改的容器比如一個列表、集合或者字典,可以使用None作為預設值
# 使用列表作為預設值
def spam(a, b=None):
    if b is None:
        b = []

# 不想提供一個預設值,而是想僅僅測試下某個預設引數是不是有傳遞進來,
_no_value = object()

def spam(a, b=_no_value):
    if b is _no_value:
        print('No b value supplied')
spam(1)
輸出:No b value supplied
spam(1, 2) # b = 2
spam(1, None) # b = None
# 傳遞一個None值和不傳值兩種情況是有差別的
# 預設引數的值僅僅在函式定義的時候賦值一次後面不會複製了
x = 42

def spam(a, b=x):
    print(a, b)

spam(1)  # 1 42

x = 23
spam(1)  # 1 42
#  改變x的值的時候對預設引數值並沒有影響,這是因為在函式定義的時候就已經確定了它的預設值了
# 預設引數的值應該是不可變的物件,比如None、True、False、數字或字串,不要寫個列表[]
def spam(a, b=[]):
    pass

# 預設值b=[]這樣寫了,當預設值在其他地方被修改後你將會遇到各種麻煩。這些修改會影響到下次呼叫這個函式時的預設值
def spam(a, b=[]):
    print(b)
    return b


x = spam(1)
print(x)        # 輸出:[]現在函式返回的是[]
x.append(99)
x.append('hello')
print(x)        # 輸出:[99, 'hello']
# 為了避免這種情況的發生,最好是將預設值設為None,
# 測試None值時使用 is 操作符是很重要的
def spam(a, b=None):
    if not b:  # 不改用“b為無”
        b = []
        print(b)

# 儘管None值確實是被當成False, 但是還有其他的物件(比如長度為0的字串、列表、元組、字典等)都會被當做False。
# 因此,上面的程式碼會誤將一些其他輸入也當成是沒有輸入。
spam(1)
x = []
spam(1, x)  # 無聲錯誤。預設情況下會覆蓋x值
spam(1, 0)  # 無聲錯誤。0被忽略
spam(1, '')  # 無聲的錯誤,忽略
# 一個函式需要測試某個可選引數是否被使用者傳遞進來。 這時候需要小心的是你不能用某個預設值比如None、 
0或者False值來測試使用者提供的值(因為這些值都是合法的值,是可能被使用者傳遞進來的)。 因此,你需要其他的解決方案了 # 建立一個獨一無二的私有物件例項,就像上面的_no_value變數那樣。
在函式裡面,你可以通過檢查被傳遞引數值跟這個例項是否一樣來判斷。
這裡的思路是使用者不可能去傳遞這個_no_value例項作為輸入。 因此,這裡通過檢查這個值就能確定某個引數是否被傳遞進來了 # object 是python中所有類的基類。 你可以建立 object 類的例項,
但是這些例項沒什麼實際用處,因為它並沒有任何有用的方法, 也沒有任何例項資料(因為它沒有任何的例項字典,你甚至都不能設定任何屬性值)。
你唯一能做的就是測試同一性。這個剛好符合要求,因為我在函式中就只是需要一個同一性的測試而已

6:定義匿名或行內函數  lambda表示式定義簡單函式

add = lambda x, y: x + y
add(2, 3)    # 5
add('hello', 'world')    # 'helloworld'

# 這個lambda函式類似
def add(x, y):
    return x + y
add(2, 3)    # 5
# lambda表示式典型的使用場景是排序或資料reduce
names = ['David Beazley', 'Brian Jones',
         'Raymond Hettinger', 'Ned Batchelder']
print(sorted(names))  # ['Brian Jones', 'David Beazley', 'Ned Batchelder', 'Raymond Hettinger']
print(sorted(names, key=lambda name: name.split()[-1].lower()))
# ['Ned Batchelder', 'David Beazley', 'Raymond Hettinger', 'Brian Jones']

a = [5, 4, 2, 3, 1]
a.sort()
print(a)    # [1, 2, 3, 4, 5]
# sorted(xxx)不會改變原資料,會返回一個排序後的資料物件
# xxx.sort()會改變原始資料,

7:匿名函式捕獲變數值  lambda定義了一個匿名函式,並想在定義時捕獲到某些變數的值

x = 10
a = lambda y: x + y
x = 20
b = lambda y: x + y
print(a(10))    # 30
print(b(10))    # 30

# 都是返回30,
# lambda表示式中的x是一個自由變數, 在執行時繫結值,而不是定義時就繫結,這跟函式的預設值引數定義是不同的。
因此,在呼叫這個lambda表示式的時候,x的值是執行時的值,執行兩個lambda匿名函式的資料x=20
x = 10 a = lambda y: x + y x = 20 b = lambda y: x + y print(a(10)) # 30 print(b(10)) # 30 x = 15 print(a(10)) # 25 x = 3 print(a(10)) # 3 # 匿名函式在定義時就捕獲到值,將那個引數值定義成預設引數即可 x = 10 a = lambda y, x=x: x + y x = 20 b = lambda y, x=x: x + y print(a(10)) # 20 print(b(10)) #
30

類似函式定義下面情況
x = 10
def func1(y):
print(x+y)
x = 20

def func2(y):
print(x+y)
func1(10) # 30
func2(10) # 30
# x只有再呼叫的時候才會賦值,而不會在定義的時候賦值,所有是30,30

x = 10
def func1(y, x=x):
print(x+y)
x = 20
def func2(y, x=x):
print(x+y)
func1(10) # 20
func2(10) # 30
# x在定義的時候就被賦值了,而不是等到func1()被呼叫的時候賦值,所有書20,30
# 通過在一個迴圈或列表推導中建立一個lambda表示式列表,並期望函式能在定義時就記住每次的迭代值
funcs = [lambda x: x+n for n in range(5)]
for f in funcs:
    print(f(0))
輸出:
4
4
4
4
4
# 實際效果是執行是n的值為迭代的最後一個值
列表生成式只是定義了5個lambda函式,n的值沒有賦值到x+n的n裡面去
定義五個lambda函式函式後現在n=4了,這個列表裡面儲存了5個lambda函式,for迴圈遍歷取出每個lambda傳遞引數0,所以結果是0+4=4

# 如果想結果改變可以寫個n的關鍵字引數
funcs = [lambda x, n=n: x+n for n in range(5)]
for f in funcs:
    print(f(0))
輸出:
0
1
2
3
4
# 這樣寫每個lambda函式就會先把每個n的值定義好了,每個lambda儲存的n的值都不同

8:減少可呼叫物件的引數個數