一、命名空間
1.1、全域命名空間
命名空間為 namespace 的直譯,決議如下:
num = 5
name = "xiaohao"
以上,我們簡單地定義了兩個變數,便等同于創建了兩個名字與物件的對應關系,這種建立名字與物件映射關系的行為便是命名
字典就是一個名字與值對應的典型例子,這使得 python 中的命名空間通常用字典實作,
print(globals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002F3C774FFD0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\86177\\Documents\\1.py', '__cached__': None, 'num': 5, 'name': 'xiaohao'}
呼叫globals方法便可回傳 全域命名空間 ,列印結果可以看到全域命名空間是一個字典物件,同時包含了我們剛剛定義的兩個變數,
包含 全域命名空間 在內有以下三種命名空間:
- 區域命名空間 Local namespace:函式中定義的命名,包括函式的引數,
- 全域命名空間 Global namespace:當前模塊中的命名,與其他模塊中 import 進來的命名,
- 內置命名空間 Built-in namespace:Python 語言內置的命名,比如函式名 print、type 等等關鍵字,
a = 1 # a 處在全域命名空間
def func():
b = 2 # b 處在區域命名空間
print(__name__) # __name__ 處在內建命名空間
1.2、內建命名空間
所謂的內建命名空間便是包括了 python 自帶的一些變數與函式,比如 dict,list,type,print 等等,這些都是由 python 默認從 builtins 模塊匯入,
我們可以配合 dir 函式列印一下 __builtins__ 模塊的內容,便可以看到內建命名空間中的內容,
print(dir(__builtins__))
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
1.3、區域命名空間
程式當前執行的代碼塊內的命名都屬于區域命名空間,
用 locals 函式可以看到當前區域命名空間中的內容,
a = 1
def func():
b = 2
print(locals())
func()
{'b': 2}
如果在最外層代碼中呼叫 locals ,此時它與 glocals 輸出的內容是等價的,因為當程式執行到最外層代碼時,區域命名空間等同于全域命名空間,
a = 1
def func():
b = 2
print(locals())
func()
print(locals())
print(globals())
{'b': 2}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002203218FFD0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\86177\\Documents\\1.py', '__cached__': None, 'a': 1, 'func': <function func at 0x0000022032625820>}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002203218FFD0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\86177\\Documents\\1.py', '__cached__': None, 'a': 1, 'func': <function func at 0x0000022032625820>}
1.4、命名空間的作用
命名空間包含了從名稱到物件的映射,大部分的命名空間都是通過 Python 字典來實作的,
命名空間可以將我們各部分的命名獨立,避免了它們之間的沖突,你在命名一個變數時不再需要翻遍整個專案來防止命名沖突導致影響其他部分的代碼,
a = 1
def func():
a = 2
print('區域命名空間的a:', a)
func()
print('全域命名空間的a:', a)
區域命名空間的a: 2
全域命名空間的a: 1
以上的兩個 a 處于不同的命名空間,它們之間互不影響,在區域命名空間的命名不會影響到全域命名空間的命名,
二、作用域
2.1、定義
我們了解到命名空間是可以獨立存在的,并且它們以一定的層次來構建,這使得我們能以一定范圍參考我們的變數,
限定變數名字的可用性的區域就是這個名字的作用域,并且作用域會直接決定我們參考一個變數時查找區域的順序,
a = 1 # 全域作用域
def func():
b = 2 # 閉合作用域
def func_2():
c = 3 # 區域作用域
print(__name__) # __name__ 處于內建作用域
在這段代碼中 a, b, c, __name__ 分別處于 全域作用域、閉合作用域、區域作用域、內建作用域,
一個函式內如果還定義有函式,則當前函式為閉合作用域,否則為區域作用域
作用域有四種:
- 區域作用域 Local:最內層的函式 (def 或 lambda),
- 閉合作用域 Enclosing:如果 A 函式內定義了一個 B 函式,那么相對 B 函式內的變數 A 函式內的變數就處于閉合作用域,
- 全域作用域 Global:最外部定義的變數
- 內建作用域 Built-in:內建模塊內定義的函式與關鍵字
python 尋找一個變數的順序是:LEGB,也就是 區域→閉合→全域→內建,

2.2、global 和 nonlocal關鍵字
如果我們想在函式中修改全域作用域中的值,就只會在區域作用域中生成一個新的命名,
這會就需要global 關鍵字,它使得我們可以修改全域作用域內的變數,
a = 1 # 全域作用域的 a
def func():
global a # 引入了全域作用域的 a 變數
print('修改前的 a:', a)
a = 2
print('修改后的 a:', a)
func()
print('最終的 a:', a)
修改前的 a: 1
修改后的 a: 2
最終的 a: 2
nonlocal 關鍵字同理,它允許我們修改閉合作用域內的變數,
a = 1 # 全域作用域的 a
def func():
a = 2 # 閉合作用域的 a 變數
def func_2():
nonlocal a # 引入了閉合作用域的 a 變數
print('修改前的 a:', a)
a = 3
func_2()
print('閉合作用域的 a:', a)
func()
print('全域作用域的 a:', a)
修改前的 a: 2
閉合作用域的 a: 3
全域作用域的 a: 1
注意:
# 全域作用域
a = 1
def func_1():
# 在區域作用域定義 a 變數
a = 2
def func_2():
# 引入全域作用域的 a 變數并重新賦值
global a
a = 2
def func_3():
# 嘗試對區域作用域的 a 變數進行自增操作
# 由于此時 a 變數未定義則報錯
a += 1
func_1()
func_2()
func_3()
Traceback (most recent call last):
File "C:\Users\86177\Documents\1.py", line 20, in <module>
func_3()
File "C:\Users\86177\Documents\1.py", line 16, in func_3
a += 1
UnboundLocalError: local variable 'a' referenced before assignment
python 會將在 func_3 的 a += 1 解釋為嘗試對區域作用域的 a 變數進行自增操作,由于此時 a 變數未定義會導致報錯,
2.3、命名空間與作用域的區別與聯系
- 命名空間是一種實際的代碼實作,而作用域是程式設計上的一種規定,
- 一個變數處在哪個命名空間則決定了它的作用域,而它的作用域則表示了它的作用范圍與被參考時所查找的順序優先級,
三、閉包
3.1、簡介
在 python 里萬物皆物件,函式也不例外,于是乎我們可以在一個函式中定義另一個函式作為回傳值,
def func(name):
def wrapper():
print(f'你好,{name}')
return wrapper
result = func('小明')
result()
你好小明
閉包: f 函式中定義了 g 函式, g 還參考了 f 定義的變數, 就是閉包,
由作用域的查找規則可知,在這段代碼中,func 函式內是區域作用域, wrapper 函式內是閉合作用域, wrapper 函式可以直接使用 func 函式內的變數,
在實作閉包時,回傳的函式會帶有 __closure __屬性,在__closure __里可以找到函式所需要的變數,
print(result.__closure__) # 列印一下 __closure__ 屬性
print(result.__closure__[0].cell_contents) # 通過 cell_contents 可以得到保存的變數
(<cell at 0x00000201DE3664F0: str object at 0x00000201DE397A50>,)
小明
3.2、閉包的應用場景
使用閉包主要有以下三個好處:
- 關聯資料與函式
- 保存臨時變數,減少全域變數
- 保存私有變數,防止變數被修改
3.2.1、關聯資料與函式
# 寫法 1:用類實作
class Timer:
def __init__(self, base):
self.base = base
def time(self, x):
return self.base * x
timer2 = Timer(2)
print('結果:', timer2.time(4))
# 寫法 2:用閉包實作
def timer(base):
def wrapper(x):
return x * base
return wrapper
timer2 = timer(2)
print('結果:', timer2(4))
結果: 8
結果: 8
3.2.2、保存臨時變數
# 寫法 1:將變數定義在全域作用域
count = 0
def func():
global count
count += 1
print(f'執行了{count}次')
func()
func()
# 寫法 2:將變數定義在閉包中的區域作用域
def counter():
count = 0
def wrapper():
nonlocal count
count += 1
print(f'執行了{count}次')
return wrapper
func = counter()
func()
func()
執行了1次
執行了2次
執行了1次
執行了2次
3.2.3、保存私有變數
def my_dict(**kwargs):
def wrapper():
# 這里是重新生成了一個字典物件,
# 因為如果直接回傳則屬于淺拷貝,
# 會導致外部仍能修改字典的值,
return {**kwargs}
return wrapper
xiaoming = my_dict(name='小明', age=18)
print('修改前:', xiaoming())
xiaoming()['name'] = '小紅'
print('修改后:', xiaoming())
修改前: {'name': '小明', 'age': 18}
修改后: {'name': '小明', 'age': 18}
四、裝飾器
4.1、函式裝飾器
在閉包中得知,函式是一個物件,
- 能在函式中定義一個函式
- 能作為引數傳遞
- 能作為回傳值
def decorator(func):
def wrapper(*args, **kwargs):
print('123')
return func(*args, **kwargs)
return wrapper
def say_hello():
print('同學你好')
say_hello_super = decorator(say_hello)
say_hello_super()
123
同學你好
在這段代碼中,我們將一個函式 say_hello 作為引數傳入函式 decorator,回傳一個wrapper 函式并賦值到 say_hello_super,此時執行 say_hello_super 相當于執行 wrapper 函式,當我們執行 wrapper 函式時會先列印123再執行先前傳入的 func 引數也就是 say_hello 函式,
裝飾器寫法:
def decorator(func):
def wrapper(*args, **kwargs):
print('123')
return func(*args, **kwargs)
return wrapper
@decorator
def say_hello():
print('同學你好')
say_hello()
這里在函式前加上 @decorator 相當于在定義函式后執行了一條陳述句, say_hello = decorator(say_hello) ,
4.2、帶引數的裝飾器
def info(value):
def decorator(func):
def wrapper(*args, **kwargs):
print(value)
return func(*args, **kwargs)
return wrapper
return decorator
@info('456')
def say_hello():
print('同學你好')
say_hello()
456
同學你好
我們可以在裝飾器外部再套上一層函式,用該函式的引數接收我們想要列印的資料,并將先前的 decorator 函式作為回傳值,這就是之前學到的閉包的一種功能,就是用閉包來生成一個命名空間,在命名空間中保存我們要列印的值 value,
4.3、wraps 裝飾器
一個函式不止有他的執行陳述句,還有著 __name__(函式名),__doc__(說明檔案)等屬性,我們之前的例子會導致這些屬性改變,
def decorator(func):
def wrapper(*args, **kwargs):
"""doc of wrapper"""
print('123')
return func(*args, **kwargs)
return wrapper
@decorator
def say_hello():
"""doc of say hello"""
print('同學你好')
print(say_hello.__name__)
print(say_hello.__doc__)
wrapper
doc of wrapper
由于裝飾器回傳了 wrapper 函式替換掉了之前的 say_hello 函式,導致函式名,幫助檔案變成了 wrapper 函式的了,
解決這一問題的辦法是通過 functools 模塊下的 wraps 裝飾器,
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""doc of wrapper"""
print('123')
return func(*args, **kwargs)
return wrapper
@decorator
def say_hello():
"""doc of say hello"""
print('同學你好')
print(say_hello.__name__)
print(say_hello.__doc__)
say_hello
doc of say hello
4.4、內置裝飾器
有三種我們經常會用到的裝飾器, property、 staticmethod、 classmethod,他們有個共同點,都是作用于類方法之上,
4.4.1、property 裝飾器
property 裝飾器用于類中的函式,使得我們可以像訪問屬性一樣來獲取一個函式的回傳值,
class XiaoMing:
first_name = '明'
last_name = '小'
@property
def full_name(self):
return self.last_name + self.first_name
xiaoming = XiaoMing()
print(xiaoming.full_name)
小明
我們像獲取屬性一樣獲取 full_name 方法的回傳值,這就是用property 裝飾器的意義,既能像屬性一樣獲取值,又可以在獲取值的時候做一些操作,
4.4.2、staticmethod 裝飾器
staticmethod 裝飾器同樣是用于類中的方法,這表示這個方法將會是一個靜態方法,意味著該方法可以直接被呼叫無需實體化,但同樣意味著它沒有 self引數,也無法訪問實體化后的物件,
class XiaoMing:
@staticmethod
def say_hello():
print('同學你好')
XiaoMing.say_hello()
# 實體化呼叫也是同樣的效果
# 有點多此一舉
xiaoming = XiaoMing()
xiaoming.say_hello()
同學你好
同學你好
4.4.3、classmethod 裝飾器
classmethod 依舊是用于類中的方法,這表示這個方法將會是一個類方法,意味著該方法可以直接被呼叫無需實體化,但同樣意味著它沒有 self 引數,也無法訪問實體化后的物件,相對于 staticmethod 的區別在于它會接收一個指向類本身的 cls 引數,
class XiaoMing:
name = '小明'
@classmethod
def say_hello(cls):
print('同學你好, 我是' + cls.name)
print(cls)
XiaoMing.say_hello()
同學你好, 我是小明
<class '__main__.XiaoMing'>
4.5、類裝飾器
類能實作裝飾器的功能, 是由于當我們呼叫一個物件時,實際上呼叫的是它的 __call__ 方法,
class Demo:
def __call__(self):
print('我是 Demo')
demo = Demo()
demo()
我是 Demo
通過這個特性,我們便可以用類的方式來完成裝飾器,功能與剛開始用函式實作的一致,
class Decorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('123')
return self.func(*args, **kwargs)
@Decorator
def say_hello():
print('同學你好')
say_hello()
123
同學你好
比如說我們有一些計算耗時很長的函式,并且每次計算的結果不變,那么我們就可以通過類定義一個快取裝飾器,來快取第一次執行的結果,
import time
class Cache:
__cache = {}
def __init__(self, func):
self.func = func
def __call__(self):
# 如果快取字典中有這個方法的執行結果
# 直接回傳快取的值
if self.func.__name__ in Cache.__cache:
return Cache.__cache[self.func.__name__]
# 計算方法的執行結果
value = self.func()
# 將其添加到快取
Cache.__cache[self.func.__name__] = value
# 回傳計算結果
return value
@Cache
def long_time_func():
time.sleep(5)
return '我是計算結果'
start = time.time()
print(long_time_func())
end = time.time()
print(f'計算耗時{end-start}秒')
start = time.time()
print(long_time_func())
end = time.time()
print(f'計算耗時{end-start}秒')
我是計算結果
計算耗時5.009094476699829秒
我是計算結果
計算耗時0.0秒
實作一個裝飾器,能夠計算函式的執行耗時,
import time
def calc(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}函式執行耗時{end-start}秒,')
return result
return wrapper
@calc
def count_2():
for i in range(0, 2):
print(f'第{i+1}次計數')
time.sleep(1) # 暫停一秒鐘
@calc
def count_5():
for i in range(0, 5):
print(f'第{i+1}次計數')
time.sleep(1) # 暫停一秒鐘
count_2()
count_5()
第1次計數
第2次計數
count_2函式執行耗時2.0178287029266357秒,
第1次計數
第2次計數
第3次計數
第4次計數
第5次計數
count_5函式執行耗時5.0245983600616455秒,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/289808.html
標籤:python
上一篇:【小白學Java】D27》》》程式的例外處理 try - catch & throw & throws& 自定義例外
