Python基礎與拾遺9:Python中的函式與作用域
- 函式的作用
- 函式的設計理念
- Python中的函式
- Python中的函式關鍵字
- Python中的多型
- Python中函式的引數傳遞
- Python中函式的引數匹配
- 引數匹配表
- 匹配引數順序
- 引數出現順序
- Python 3.0及之后版本中的Keyword-Only引數
- Python中函式的屬性與注解
- Python中的lambda運算式
- Python中的map,filter與reduce函式
- Python中的作用域
- 作用域規定
- global陳述句
- Python 3.0及之后版本中的nonlocal陳述句
- 寫在最后
與其他編程語言相同,在具有一定規模的模塊化的Python工程中, 函式是必不可少的組成部分,Python中的函式與作用域和其他常用編程語言亦有所不同,本篇博文總結Python中的函式與作用域相關知識,下面開始干貨,
函式的作用
- 最大化代碼重用和最小化代碼冗余,該點在所有編程語言中適用,函式的目的可以理解為集成一個功能,在整個工程中不同的程式部分中,可以直接呼叫相同的函式達到預期的目標,
- 流程的分解,在大型的工程或程式中,邏輯流程往往非常復雜,此時函式就起到了分解流程的作用,將整個邏輯流程中的不同功能部分拆解,封裝在函式中,可以使得工程更有條理,且邏輯清晰,
函式的設計理念
- 耦合性:對于輸入使用引數并且對于輸出使用return陳述句,函式是對部分功能進行封裝,函式外部的邏輯不應知曉函式內部的運行流程,只關心輸入與輸出,
- 耦合性:真正必要時才使用全域變數,全部變數較影響依賴關系,因此非必要不使用,
- 耦合性:非必要不改變可變型別的引數,對于引數中的可變型別,不建議在函式中修改,可以通過return回傳修改后的變數,
- 耦合性:避免直接改變在另一個模塊檔案中的變數,這樣會損壞模塊檔案間的耦合性,
- 聚合性:每一個函式都應該有一個單一的,統一的目標,每一個函式應該針對一個目標進行設計,
- 大小:每一個函式應該相對較小,對于冗長的函式,建議針對功能進行拆分,提升工程的模塊性,
Python中的函式
Python中的函式關鍵字
- def關鍵字表示可執行代碼,創建一個物件并將其賦值給某一變數名,def陳述句是實時執行的,因此可以出現在任何地方,甚至嵌套在其他陳述句中,創建函式時,def關鍵字會生成新的函式物件并將其賦值給這個函式名,函式名就成了這個函式的參考,
def <name>(arg0, arg1, ... argN):
# TODO
def add(a, b):
return a + b
def main():
sum = add(1, 2)
print(sum)
if __name__ == "__main__":
main()
# 輸出:
# 3
- lambda關鍵字創建物件但將其作為結果回傳,一般作用在def不能達到的地方,在本篇博文中包含lambda函式的詳細決議,請見后文,
f = [lambda x: x**2, lambda x: x**3]
def main():
a = f[0](2)
b = f[-1](2)
print(a)
print(b)
if __name__ == "__main__":
main()
# 輸出:
# 4
# 8
- return關鍵字將控制權回傳呼叫者,函式中沒有return也可以,會回傳一個None物件,自動忽略,
def add(a, b):
return a + b
def my_print(a):
print(a)
def main():
sum = add(1, 2)
pt = my_print(1)
print(sum)
print(pt)
if __name__ == "__main__":
main()
# 輸出:
# 1
# 3
# None
- yield關鍵字向呼叫者發回一個結果物件,但是記住離開的地方,yield關鍵字主要用在生成器中,通過yield陳述句來回傳值,并掛起他們的狀態以便稍后能恢復狀態,詳情可參見Python基礎與拾遺8:Python中的迭代器與決議,
def gen(N):
for i in range(N):
yield i ** 2
def main():
for i in gen(5):
print(i)
if __name__ == '__main__':
main()
'''
輸出:
0
1
4
9
16
'''
- global關鍵字宣告一個模塊級變數,為分配一個在整個模塊檔案中都可以用的變數名,可以用global進行變數宣告,在本篇博文中包含global關鍵字的詳細決議,請見后文,
var = 1
def f():
global var
var = 100
def main():
f()
print(var)
if __name__ == "__main__":
main()
# 輸出:
# 100
var = 1
def f():
# global var
var = 100
def main():
f()
print(var)
if __name__ == "__main__":
main()
# 輸出:
# 1
- Python 3.0及之后版本中的nonlocal關鍵字宣告一個嵌套函式作用域內的變數,而不是所有def之外的全域模塊作用域變數,在宣告nonlocal名稱時,這個變數必須已經存在于該嵌套函式的作用域中,在本篇博文中包含nonlocal關鍵字的詳細決議,請見后文,
def f():
var = 1
def g():
nonlocal var
print(var)
var += 1
return g
def main():
g = f()
g()
g()
g()
if __name__ == "__main__":
main()
# 輸出:
# 1
# 2
# 3
def f():
var = 1
def g():
# nonlocal var
print(var)
var += 1
return g
def main():
g = f()
g()
g()
g()
if __name__ == "__main__":
main()
# 輸出:
# UnboundLocalError: local variable 'var' referenced before assignment
- 函式通過賦值來傳遞,若傳遞的是可變物件,就可以在函式中改變此物件,但不建議這么做,注意Python語言與C語言的傳值呼叫,傳址呼叫機制的區別,
def f(a):
if isinstance(a, list):
for i in range(len(a)):
if isinstance(a[i], int) or isinstance(a[i], float):
a[i] += 1
def main():
a = [1, 2, 3]
f(a)
print(a)
if __name__ == "__main__":
main()
# 輸出:
# [2, 3, 4]
- 函式并不是宣告,也不需要宣告,
Python中的多型
- 在Python中,代碼不應該關心具體的資料型別,反之,操作的意義取決于被操作的物件型別,該特性稱為Python中的多型,
def times(x, y):
return x * y
def main():
a = times(2, 4)
b = times("hello ", 3)
print(a)
print(b)
if __name__ == "__main__":
main()
# 輸出:
# 8
# hello hello hello
Python中函式的引數傳遞
- 引數傳遞通過自動將物件賦值給本地變數名實作,所有引數實際通過指標進行傳遞,作為引數被傳遞的物件從來不用自動拷貝,
- 在函式內部的引數名的賦值不會影響呼叫者,函式定義中的引數名是一個新的、本地變數名,該點主要解釋了定義與呼叫時的引數之間沒有關聯,
- 改變函式的可變物件引數的值對呼叫有影響,重要的事情說三遍,這里是本篇博文的第三遍,事實上不推薦這樣做,建議通過return陳述句回傳修改的引數,
可以在函式內部直接改動形參的值,比如串列,字典等, - 不可變引數==通過“值”進行傳遞,比如字串,實數,可變物件通過“指標”==進行傳遞,比如串列,字典等,
def f(a, b):
a = 100
b[0] = "hello"
def main():
a = 1
b = [1, 2, 3]
f(a, b)
print(a)
print(b)
if __name__ == "__main__":
main()
# 輸出:
# 1
# ['hello', 2, 3]
Python中函式的引數匹配
引數匹配表
| 語法 | 使用時期 | 解釋 |
|---|---|---|
| func(value) | 函式呼叫 | 常規引數:通過關鍵字進行匹配 |
| funv(name=value) | 函式呼叫 | 關鍵字引數:通過變數名匹配 |
| func(*sequence) | 函式呼叫 | 以name傳遞所有物件,并作為獨立的基于位置的引數 |
| func(**dict) | 函式呼叫 | 以name成對地傳遞所有的關鍵字/值,并作為獨立的關鍵字引數 |
| def func(name) | 函式定義 | 常規引數:通過位置/變數名進行匹配 |
| def func(name=value) | 函式定義 | 默認引數值,如果沒有在呼叫中傳遞的話 |
| def func(*name) | 函式定義 | 匹配并收集(在元祖中)所有包含位置的引數 |
| def func(**name) | 函式定義 | 匹配并收集(在字典中)所有包含位置的引數 |
| def func(*args, name) | 函式定義 | 引數必須在呼叫中按照關鍵字傳遞 |
| def func(*, name=value) | 函式定義 | Python 3.0及之后版本的規則,同上 |
匹配引數順序
- 通過位置分配非關鍵字引數,
- 通過匹配變數名分配關鍵字引數,
- 其他的額外非關鍵字引數分配到==*name元組==中,
- 其他的額外關鍵字引數分配到==**name字典==中,
- 用默認值分配給在函式定義中未得到分配的引數,
引數出現順序
在函式定義中,引數的出現順序:
任何一般引數name -> 任何默認引數name=value -> *name(Python 3.0及之后版本可以用*) -> name/name=value(Python 3.0及之后版本中的key-only引數),-> **name,
在函式呼叫中,引數的出現順序:
任何位置引數value -> 任何關鍵字引數name=value和*sequence形式的組合 -> **dict引數,
- 通過位置匹配變數名,
def f(a, b, c):
print(a, b, c)
def main():
f(1, 2, 3)
# f(1) # TypeError: f() missing 2 required positional arguments: 'b' and 'c'
if __name__ == "__main__":
main()
# 輸出:
1, 2, 3
- 通過變數名匹配關鍵字引數,注意,非關鍵字引數必須在關鍵字引數前面,
def f(a, b=3, c=5):
print(a, b, c)
def main():
f(1, 2, 3)
f(10)
f(100, b=1)
f(1000, c=2)
f(10000, c=1, b=2)
f(c=1, a=100000, b=2)
f(c=1, b=2, 1000000) # SyntaxError: positional argument follows keyword argument
if __name__ == "__main__":
main()
'''
輸出:
1 2 3
10 3 5
100 1 5
1000 3 2
10000 2 1
100000 2 1
'''
- 默認引數在定義時必須在關鍵字引數之后,
def f(a, b=3, c=5):
print(a, b, c)
def main():
f(1)
if __name__ == "__main__":
main()
# 輸出:
# 1, 3, 5
def f(a=3, b, c=5):
print(a, b, c)
def main():
f(1)
if __name__ == "__main__":
main()
# 輸出:
# SyntaxError: non-default argument follows default argument
- 函式定義中*表示收集任意數目的不匹配位置引數,**表示收集任何數目的關鍵字引數,
def f(a, *pargs, **kargs):
print(a, pargs, kargs)
def main():
f(1, 2, 3, 4, x=5, y=6)
if __name__ == "__main__":
main()
# 輸出:
# 1 (2, 3, 4) {'x': 5, 'y': 6}
- 解包引數,在呼叫函式時,顯式地輸入*和**,*解包元組,**解包字典,
# 元組的解包
def f(*a): # 接收序列引數
print(a) # 直接列印序列引數
print(*a) # 解包元祖
def g(a, b, c, d):
print(a, b, c, d)
def main():
t = (1, 2, 3, 4)
f(*t)
g(*t)
if __name__ == "__main__":
main()
# 字典的解包
def f(**a): # 接收字典引數
print(a) # 直接列印字典引數
print(*a) # 注意,這里是得到字典鍵
# print(**a) # TypeError: 'a' is an invalid keyword argument for print()
def g(a, b, c):
print(a, b, c)
def main():
d = {'a': 1, 'b': 2, 'c': 3}
f(**d)
g(**d)
if __name__ == "__main__":
main()
Python 3.0及之后版本中的Keyword-Only引數
- 定義時,keyword-only引數必須撰寫在**args任意關鍵字形式之前,且在*args或者*之后,在呼叫時,keyword-only引數必須在**args引數之前或者包含在**args中,可以在*args之前或者之后,注意,呼叫時形式為鍵值對,
- 如果在函式定義中,keyword-only引數沒有指定默認值,在呼叫時必須傳入鍵值對,
- 注意,在函式定義與呼叫中,如果出現**arg形式,只能在最后,
def f(a, b=2, *c, d=4, **e):
print('a: ', a)
print('b: ', b)
print('c: ', c)
print('d: ', d)
print('e: ', e)
def main():
f(1, 10, 3, 4, 5, 6, 7)
print('------------------------------')
f(1, d=100, *(3, 4, 5, 6, 7))
print('------------------------------')
f(1, c=(3, 4, 5, 6, 7), d=1000)
print('------------------------------')
f(1, *(3, 4, 5, 6, 7), d=10000)
print('------------------------------')
f(1, 3, (4, 5, 6, 7), **dict(d=100000, e=8, f=9))
print('------------------------------')
f(1, 3, (4, 5, 6, 7), **dict(e=8, f=9))
print('------------------------------')
f(1, 3, (4, 5, 6, 7), e=8, f=9, d=4)
if __name__ == "__main__":
main()
'''輸出
a: 1
b: 10
c: (3, 4, 5, 6, 7)
d: 4
e: {}
------------------------------
a: 1
b: 3
c: (4, 5, 6, 7)
d: 100
e: {}
------------------------------
a: 1
b: 2
c: ()
d: 1000
e: {'c': (3, 4, 5, 6, 7)}
------------------------------
a: 1
b: 3
c: (4, 5, 6, 7)
d: 10000
e: {}
------------------------------
a: 1
b: 3
c: ((4, 5, 6, 7),)
d: 100000
e: {'e': 8, 'f': 9}
------------------------------
a: 1
b: 3
c: ((4, 5, 6, 7),)
d: 4
e: {'e': 8, 'f': 9}
------------------------------
a: 1
b: 3
c: ((4, 5, 6, 7),)
d: 4
e: {'e': 8, 'f': 9}
'''
Python中函式的屬性與注解
- 函式也是一個物件,自身全部都在記憶體塊中,
- 函式名可以直接進行賦值,也可以當做函式引數進行傳遞,當做回傳值回傳,
def add(*a):
return sum(*a)
def f(func, *a):
return func(a)
def main():
sum = f(add, 1, 2, 3, 4)
print(sum)
if __name__ == "__main__":
main()
# 輸出:
# 10
- 函式內省與屬性,內省工具允許用戶探索函式的實作細節,也可以給函式添加自定義屬性,通過dir查看,
def f(a, b):
return a + b
def main():
f.__handles__ = "F_Handle" # 增加自定義屬性
print(dir(f))
print(dir(f.__call__))
print(dir(f.__class__))
print(dir(f.__code__))
if __name__ == "__main__":
main()
'''
輸出:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__handles__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__objclass__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_posonlyargcount', 'co_stacksize', 'co_varnames', 'replace']
'''
- Python 3.0及之后版本中的函式注解,對于引數的注解,出現在緊隨引數名的冒號之后;對于回傳值的注解,撰寫于緊跟函式串列之后的一個->之后,注解不影響函式的默認值,注解只在def運算式中有效,在lambda運算式中無效,
def f(a: 'float', b: 'float') -> float:
return a + b
def main():
print(f.__annotations__)
if __name__ == "__main__":
main()
# 輸出:{'a': 'float', 'b': 'float', 'return': <class 'float'>}
Python中的lambda運算式
- lambda是一個運算式,不是一個陳述句,lambda主體是一個單個的運算式,不是一個代碼塊,允許在使用的代碼內嵌入一個函式的定義,注意,lambda運算式盡量簡短,復雜功能函式應該使用def,
def main():
f = lambda x, y: x + y
print(f(1, 2))
if __name__ == "__main__":
main()
# 輸出:3
- lambda可以嵌套,
def main():
f = (lambda x: (lambda y: x + y))
g = f(1)
print(g(2))
if __name__ == "__main__":
main()
# 輸出:3
Python中的map,filter與reduce函式
- map對一個序列物件中的每一個元素應用被傳入的函式,在Python 2.6及之后版本中回傳一個包含所有用函式呼叫結果的一個串列,而在Python 3.0及之后版本中回傳一個可迭代物件,可以使用list()將其轉換為串列,相比for回圈或串列決議,map有性能方面的優勢,執行更快,
def f(x):
return x ** 2
def main():
o = map(f, [1, 2, 3, 4])
print(o)
print(list(o))
if __name__ == "__main__":
main()
# 輸出:
# <map object at 0x000001383432D220>
# [1, 4, 9, 16]
- map與lambda可以協同使用,
def main():
l = list(map(lambda x: x ** 2, [1, 2, 3, 4]))
print(l)
if __name__ == "__main__":
main()
# 輸出:
# [1, 4, 9, 16]
- filter工具按照條件過濾,在Python 2.6及之后版本中回傳一個包含所有函式呼叫結果的一個串列,而在Python 3.0及之后版本中回傳一個可迭代物件,可以使用list()將其轉換為串列,
def main():
l = list(filter(lambda x: x > 0, range(-5, 5)))
print(l)
if __name__ == "__main__":
main()
# 輸出:
# [1, 2, 3, 4]
- reduce工具對每對元素都應用函式并運行到最后結果,在Python 2.6及之后版本中回傳一個包含所有函式呼叫結果的一個串列,而在Python 3.0及之后版本中回傳一個可迭代物件,可以使用list()將其轉換為串列,注意,在Python 3.0及之后版本中,reduce工具被包含在functools模塊中,要使用需先匯入模塊,對于filter和reduce也可參見Python基礎與拾遺8:Python中的迭代器與決議
from functools import reduce
def main():
sum = reduce((lambda x, y: x + y), [1, 2, 3, 4])
print(sum)
if __name__ == "__main__":
main()
Python中的作用域
作用域規定
- 內嵌的模塊是全域作用域,在每一個模塊檔案中直接定義的變數(在函式或者類外的),屬于全域作用域,可以在這個模塊中被任意使用,
x = [1, 2, 3, 4]
def f():
x.append(5)
def main():
f()
print(x)
if __name__ == "__main__":
main()
# 輸出:[1, 2, 3, 4, 5]
- 全域作用域的作用范圍僅限于單個檔案,一個檔案的頂層變數名僅對于這個檔案內部的代碼而言是全域的,
- 每次對函式的呼叫創建一個新的本地作用域,每個def陳述句或者lambda運算式都定義了一個新的本地作用域,
x = [1, 2, 3, 4]
def f():
x = 2
return x
def main():
x = f()
print(x)
if __name__ == "__main__":
main()
# 輸出:2
- 賦值的變數名,除非宣告為全域變數(global)或Python3.0 及之后版本的非本地變數(nonlocal),否則均為本地變數,
- 所有變數名都可以歸納為本地,全域或內置的,
變數名決議原則:L(本地作用域) -> E(上一層結構中def或lamda的本地作用域) -> G(全域作用域) -> B(內置作用域)
global陳述句
- 全域變數是位于模塊檔案內部頂層的變數名,
- 全域變如果在函式內部被賦值,必須經過宣告,
- 全域變數名在函式內部不經過宣告也可以被參考,
- global的賦值變數可以在賦值前直接不存在,會直接在模塊中創建該變數,
- global使得作用域查找從模塊的作用域開始,繼續查找至內置作用域,對全域變數的賦值總是在模塊的作用域中修改或創建變數,
x = 1
y = 2
def f():
global x, z
x = 2
z = 3
return x, y
def g():
global z
return z
def main():
x, y = f()
z = g()
print(x)
print(y)
print(z)
if __name__ == "__main__":
main()
'''
輸出:
2
2
3
'''
- 沒有在函式中賦值的變數會在整個模塊中查找,
Python 3.0及之后版本中的nonlocal陳述句
- nonlocal應用于一個嵌套函式作用域中的一個名稱,而不是所有def之外的全域模塊作用域,哪怕全域作用域中有這個名稱,
- nonlocal的名稱必須要在一個嵌套的def作用域中出現過,
- nonlocal限制作用域查找只是嵌套的def,作用域查找不會繼續到全域或內置作用域,下面的例子對比非本地變數與本地變數的區別,
def f(x):
a = x
def g():
nonlocal a
a += 1
return a
return g
def main():
a = f(1)()
print(a)
if __name__ == "__main__":
main()
# 輸出:2
def f(x):
a = x
def g():
a = 100
return a
return g
def main():
a = f(1)()
print(a)
if __name__ == "__main__":
main()
# 輸出:100
以上,歡迎各位讀者朋友提出意見或建議,
寫在最后
經過Python基礎與拾遺部分的9講,相信各位讀者朋友對于Python語言已經有了初步的體會,能夠進行初級的Python編程,這也是筆者對技術總結與復盤的程序,很高興與各位讀者朋友一起成長,享受進步的喜悅,
呈上Python基礎與拾遺前8講鏈接:
Python基礎與拾遺1:Python中的數字
Python基礎與拾遺2:Python中的字串與字串格式化
Python基礎與拾遺3:Python中的串列
Python基礎與拾遺4:Python中的字典
Python基礎與拾遺5:Python中的元組
Python基礎與拾遺6:Python中的檔案
Python基礎與拾遺7:Python中的資料型別總結
Python基礎與拾遺8:Python中的迭代器與決議
歡迎閱讀筆者后續博客,各位讀者朋友的支持與鼓勵是我最大的動力!
written by jiong
無人相
無我相
無眾生相
無壽者相
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/258730.html
標籤:python
