6.進階主題#
生成式(comprehension)#
建立list, dict, set時,有一個十分常用的技巧稱作生成式(comprehension)。 生成式的語法比起使用迴圈簡潔許多,執行速度也比使用迴圈快。
list comprehension#
例如想建立一個內含1~10數字元素的list,該怎麼做?
可能想到的做法是先建立一個空的list,然後利用range()
來append每個元素進去:
a_list = []
for i in range(1, 11):
a_list.append(i)
print(a_list)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
但一個符合python風格的寫法是透過生成式:
a_list = [i for i in range(1, 11)]
print(a_list)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
生成式有許多靈活的應用方法:
例如,可以對每個元素做加工:
a_list = [i**2 for i in range(1, 11)]
print(a_list)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
也可以對元素做篩選:
a_list = [i for i in range(1, 11) if i % 2 == 0]
print(a_list)
[2, 4, 6, 8, 10]
dictionary comprehension#
一個常用生成式來建立字典的時機是結合zip():
keys = ['a','b','c']
values = [1, 2, 3]
a_dict = {k: v for k, v in zip(keys, values)}
print(a_dict)
{'a': 1, 'b': 2, 'c': 3}
或是當你想要交換key跟value的對應關係時:
reverse_a_dict = {v: k for k, v in a_dict.items()}
print(reverse_a_dict)
{1: 'a', 2: 'b', 3: 'c'}
set comprehension#
set也有生成式,寫法如下:
a_set = {i // 4 for i in range(30)}
print(a_set)
{0, 1, 2, 3, 4, 5, 6, 7}
裝飾器(decorator)#
裝飾器的主要作用是為了在不更動function原始程式碼的情況下,添加或改變function的行為。
一個印出function執行時間的範例如下:
from datetime import datetime
def my_timer(func):
def wrapper(*args, **kwargs):
start = datetime.now()
print(f'{func.__name__} starts at {start}')
result = func(*args, **kwargs)
end = datetime.now()
print(f'{func.__name__} ends at {end}')
print(f'total execution time: {end - start}')
return result
return wrapper
當你要使用這個裝飾器時,只要在目標function的定義陳述式上面加上”@”以及裝飾器名稱即可:
import time
@my_timer
def lazy_square(number):
time.sleep(0.5)
return number**2
使用該function時就會被添加該function原本沒有的功能:
lazy_square(99)
lazy_square starts at 2024-03-28 07:30:24.868115
lazy_square ends at 2024-03-28 07:30:25.373174
total execution time: 0:00:00.505059
9801
裝飾器的使用時機在於當想一次對多個function添加一些行為時, 如果不使用裝飾器的話就必須一個一個function去修改程式碼。 除了很麻煩以外,也容易造成錯誤。
以上就是裝飾器的基本用法,但這樣的做法會有個小問題。
請看以下程式:
help(lazy_square)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
印出的結果是裝飾器中的wrapper()
的名稱。
要解決這個問題必須使用python標準函式庫中的一個套件functools
,
在內層的wrapper上面加上一個裝飾器@functools.wraps()
:
from datetime import datetime
import functools # 加這行
def my_timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = datetime.now()
print(f'{func.__name__} starts at {start}')
result = func(*args, **kwargs)
end = datetime.now()
print(f'{func.__name__} ends at {end}')
print(f'total execution time: {end - start}')
return result
return wrapper
重新定義一次function。
import time
@my_timer
def lazy_square(number):
time.sleep(0.5)
return number**2
檢查修改後的結果:
help(lazy_square)
Help on function lazy_square in module __main__:
lazy_square(number)
名稱空間#
我們知道變數名稱是一個標籤,貼在盒子(物件)上面, 當我們呼叫變數時,python會去取用盒子裡面的資料。
但是如果有多個一樣的變數名稱呢?到底要取用哪個物件就會造成混淆。
Python透過名稱空間(namespace)去界定變數名稱的搜尋範圍, 不同名稱空間有不同優先順序,在哪個空間先找到變數名稱,就去取用該變數名稱對應到的物件。
名稱空間依序如下:
local
enclosing
global
built-in
說明#
Build-in namespace
Build-in namespace在python啟動時就會建立,直到python編譯器終止為止。
以下是python內建的變數名稱:
dir(__builtins__)
['ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
'BaseExceptionGroup',
'BlockingIOError',
'BrokenPipeError',
'BufferError',
'BytesWarning',
'ChildProcessError',
'ConnectionAbortedError',
'ConnectionError',
'ConnectionRefusedError',
'ConnectionResetError',
'DeprecationWarning',
'EOFError',
'Ellipsis',
'EncodingWarning',
'EnvironmentError',
'Exception',
'ExceptionGroup',
'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',
'ZeroDivisionError',
'__IPYTHON__',
'__build_class__',
'__debug__',
'__doc__',
'__import__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'abs',
'aiter',
'all',
'anext',
'any',
'ascii',
'bin',
'bool',
'breakpoint',
'bytearray',
'bytes',
'callable',
'chr',
'classmethod',
'compile',
'complex',
'copyright',
'credits',
'delattr',
'dict',
'dir',
'display',
'divmod',
'enumerate',
'eval',
'exec',
'execfile',
'filter',
'float',
'format',
'frozenset',
'get_ipython',
'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',
'range',
'repr',
'reversed',
'round',
'runfile',
'set',
'setattr',
'slice',
'sorted',
'staticmethod',
'str',
'sum',
'super',
'tuple',
'type',
'vars',
'zip']
在定義變數時,要小心不要使用到這些變數名稱,否則會覆蓋掉。
global namespace
global namespace包含了在主程式中定義的變數名稱,所謂主程式可以先理解成就是正在使用中的jupyter notebook。
The Local and Enclosing Namespaces
至於local namespace就是function在執行時內部的變數名稱空間。
而enclosing namespace則是指當function是多層的時候, 例如雙層的function,外層function的namespace就是所謂的enclosing namespace。
請看下方釋例說明:
def outer():
print('start outer function')
namespace = 'outer'
def inner():
print('>> start inner function')
namespace = 'inner'
print(namespace)
print('>> end inner function')
inner()
print(namespace)
print('end outer function')
outer()
start outer function
>> start inner function
inner
>> end inner function
outer
end outer function
當我們呼叫outer()
時,python會為outer建立新的namespace。
當outer內部呼叫inner()
時,python也會為inner建立另一個獨立的namespace。
namespace彼此之間不會互相干擾,確保程式不會有意料之外的事情發生。
此時outer的namespace稱作enclosing namespace, 而inner的namespace則是local namespace。
範例#
以下範例靈感主要來自於Namespaces and Scope in Python – Real Python。
範例一
x
定義在f()
和g()
的外面,所以x定義在global namespace中。
x = 'global'
def f():
def g():
print(x)
g()
f()
global
範例二
x
定義了兩次,一個定義在global namespace中,另一個則是在f()
裡面,所以是enclosing namespace。
x = 'global'
def f():
x = 'enclosing'
def g():
print(x)
g()
f()
enclosing
範例三
x
定義了三次,一個在global namespace、一個在enclosing namespace,最後一個則是在g()
裡面,所以是local namespace。
x = 'global'
def f():
x = 'enclosing'
def g():
x = 'local'
print(x)
g()
f()
local
範例四
無法修改超出名稱空間範圍的變數。
例如我們定義了一個revise_x的function,裡面重新對x賦值, 但這邊的賦值行為只在local namespace中生效,並不會影響到global namespace。
x = 1
def revise_x():
x = 2
print(x)
revise_x()
print(x)
2
1
範例五
同樣地,在雙層的function中也是一樣的概念,裡面那層的賦值行為並不會影響到外面。
x = 1
def revise_x_outer():
x = 2
def revise_x_inner():
x = 3
print('local:', x)
revise_x_inner()
print('enclosing:', x)
revise_x_outer()
print('global:', x)
local: 3
enclosing: 2
global: 1
統整#
在取用變數時,python會從local → enclosing → global → build-in逐層搜尋變數名稱。
如果變數名稱都搜尋不到的話,就會丟出NameError
,說明變數並不存在。
在修改變數時,變數只在local namespace中作用,並不會影響到外面的namespace。
參考: