In [1]:
def deco(func):
def wrapped():
print('Before func')
func()
print('After func')
return wrapped
@deco
def f1():
print('This is f1 function')
f1()
In [2]:
def deco(func):
def wrapped():
print('Before func')
func()
print('After func')
return wrapped
@deco
def f1():
print('This is f1 function')
print(f1.__name__)
In [3]:
from functools import wraps
def deco(func):
@wraps(func)
def wrapped():
print('Before func')
func()
print('After func')
return wrapped
@deco
def f1():
print('This is f1 function')
print(f1.__name__)
In [4]:
import time
from functools import wraps
def deco(func):
@wraps(func)
def wrapped(*args, **kwargs):
print('Before func')
print('---')
print('{} argument:\nargs = {}\nkwargs = {}\n'.format(
func.__name__, args, kwargs
))
func(*args, **kwargs)
print('---')
print('After func')
return wrapped
@deco
def f1(*args, **kwargs):
print('This is f1 function')
f1(1, '2', [3], {'4': 4}, time=time.ctime())
In [5]:
from functools import wraps
def tag(name):
def deco(func):
@wraps(func)
def wrapped(*args, **kwargs):
print('<{}>'.format(name))
func(*args, **kwargs)
print('</{}>'.format(name))
return wrapped
return deco
@tag('p')
def content(*args, **kwargs):
for i in args:
print(i)
content('Hello World.', 'This is second argument.')
In [6]:
from functools import wraps
def tag(name):
def deco(func):
@wraps(func)
def wrapped(*args, **kwargs):
print('<{}>'.format(name))
func(*args, **kwargs)
print('</{}>'.format(name))
return wrapped
return deco
tag_p = tag('p')
@tag_p
def content(*args, **kwargs):
for i in args:
print(i)
content('Hello World.', 'This is second argument.')
In [7]:
from functools import wraps
def tag(name):
def deco(func):
@wraps(func)
def wrapped(*args, **kwargs):
print('<{}>'.format(name))
func(*args, **kwargs)
print('</{}>'.format(name))
return wrapped
return deco
tag_div = tag('div')
tag_p = tag('p')
@tag_div
@tag_p
def content(*args, **kwargs):
for i in args:
print(i)
content('Hello World.', 'This is second argument.')
可以透過 decorator 寫出漂亮的 Pythonic code (e.g. link)
Task: 重複操作「紀錄當下時間為起始時間」->「執行 function」->「當下時間減掉起始時間為 function 耗時時間」
搭配 logging 還可以設定 log level 為 DEBUG, 這樣可以非常輕鬆的 enable/ disable 這些訊息
In [8]:
import time
import logging
from functools import wraps
LOGGER = logging.getLogger(__name__)
def func_profiling(func):
@wraps(func)
def wrapped(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
time_spent = time.time() - start_time
fullname = '{}.{}'.format(func.__module__, func.__name__)
LOGGER.debug('{}[args={}, kwargs={}] completed in {}'.format(
fullname, args, kwargs, time_spent
))
return result
return wrapped
@func_profiling
def test_func_profiling(msg=None):
import random
sleep_sec = random.randrange(1,3)
LOGGER.debug('random sleep in {} sec'.format(sleep_sec))
time.sleep(sleep_sec)
LOGGER.info(msg)
LOGGER.debug('Wake up')
if __name__ == '__main__':
"""testing"""
import sys
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s %(filename)12s:L%(lineno)3s [%(levelname)8s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
stream=sys.stdout
)
test_func_profiling('Hello World')
In [9]:
from functools import wraps
def author(email):
def sub_command(func):
@wraps(func)
def wrapped(*args, **kwargs):
try:
func(*args, **kwargs)
except ValueError:
'''you can use your own defined exception'''
print('some useful message to debug')
except Exception:
print('Unexpected exception, please contant author: {}'.format(email))
return wrapped
return sub_command
@author('afun@example.com')
def divide(a, b):
print('{}/{} = {}'.format(a, b, a/b))
return a/b
divide(6, 2)
divide(6, 0)
當我們的 function output 是可預期的 (同樣的 input 會有同樣的 output), 可以透過 lru_cache
把結果儲存起來, 下一次遇到同樣的 input 時就可以直接從 cache 取出結果而不用重新計算一次
詳細細節可以參考文件
In [10]:
from functools import lru_cache
@lru_cache()
def heavy_jobs(x):
print('do some heavy jobs with input {}'.format(x))
return x+1000
print(heavy_jobs(1))
print(heavy_jobs(1))
print(heavy_jobs(2))
print(heavy_jobs(1))
print(heavy_jobs(2))
In [11]:
import json
from functools import wraps
def cache_json(filename):
def deco(func):
@wraps(func)
def wrapped(*args, **kwargs):
try:
print('Before try wrapped')
return json.load(open(filename))
except FileNotFoundError:
print('Before except wrapped')
data = func(*args, **kwargs)
json.dump(data, open(filename, 'w'))
return data
return wrapped
return deco
@cache_json('heavy.json')
def heavy_jobs(*args, **kwargs):
print('do heavy jobs')
if 'count' in kwargs:
return kwargs['count']
return
print(heavy_jobs(user='afun', count=5))
In [12]:
from functools import wraps
function_map = {}
def deco(func):
global function_map
function_map[func.__name__] = func
@wraps(func)
def wrapped(*args, **kwargs):
func(*args, **kwargs)
return wrapped
@deco
def f1():
print('This is f1')
@deco
def f2():
print('This is f2')
print(function_map)
In [ ]:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Index page'
@app.route('/hello')
def hello():
return 'Hello, world!'
@app.route('/post/<int:post_id>')
def show_post(post_id):
return 'Post {}'.format(post_id)
In [13]:
class my_context(object):
def __enter__(self):
print('in enter')
return 'enter'
def __exit__(self, *excinfo):
print('in exit')
return 'exit'
with my_context() as f:
print('Hello')
print('World')
上面 with
的使用方式同等於下面列的程式碼
In [14]:
context = my_context()
obj = context.__enter__()
try:
print('Hello')
print('World')
except Exception as e:
if context.__exit__(sys.exc_info()):
raise e
else:
context.__exit__()
In [15]:
from contextlib import contextmanager
@contextmanager
def my_context():
print('do things in enter')
yield 'It is a feature, not a bug!!!'
print('do things in exit')
with my_context() as obj:
print('Hello')
print('World')
到這邊你可能會疑惑這兩邊哪裡一樣?然後 yield 的東西呢?
其實你可以把 contextlib.contextmanager
視為一個轉換工具
In [16]:
@contextmanager
def my_context():
print('do things in enter')
yield 'It is a feature, not a bug!!!'
print('do things in exit')
# 透過 contextmanager 轉換, 與下面這段程式碼等價
class my_context(object):
def __enter__(self):
print('do things in enter')
return 'It is a feature, not a bug!!!'
def __exit__(self, *excinfo):
print('do things in exit')
return 'exit'
In [17]:
from contextlib import contextmanager
@contextmanager
def my_context():
yield 'Hello'
yield 'World'
with my_context():
print('line 1')
print('line 2')
In [18]:
from contextlib import contextmanager
@contextmanager
def context_loop():
for i in range(100):
yield i
with context_loop():
print('line 1')
print('line 2')
In [19]:
from contextlib import contextmanager
@contextmanager
def context_condition(cond):
if cond:
yield 'in condition'
else:
yield 'else case'
with context_condition(True) as f1:
print(f1)
print('line 1')
print('line 2')
with context_condition(False) as f2:
print(f2)
print('line 3')
print('line 4')
In [20]:
from contextlib import contextmanager
@contextmanager
def context_try(cond):
try:
yield 'normal'
except Exception:
print('exception')
finally:
print('finally')
with context_try(True):
print('line 1')
print('line 2')
with
statement所以說到底 with
是甚麼? 根據 PEP 343 的說法, with 基本上就是 try/finally 的實現
Abstract
This PEP adds a new statement "with" to the Python language to make
it possible to factor out standard uses of try/finally statements.
In this PEP, context managers provide __enter__() and __exit__()
methods that are invoked on entry to and exit from the body of the
with statement.
In [21]:
fp = open('test.txt', 'w+')
try:
fp.write('Hello world')
finally:
fp.close()
# 上面這種寫法透過 with 實現的話會變成下面這種程式碼
with open('test.txt', 'w+') as fp:
fp.write('Hello world')
In [22]:
from contextlib import contextmanager
@contextmanager
def tag(name):
print('<{}>'.format(name))
yield
print('</{}>'.format(name))
with tag('div'):
print('content 1')
In [23]:
from contextlib import contextmanager
@contextmanager
def tag(name):
print('<{}>'.format(name))
yield
print('</{}>'.format(name))
# nested
with tag('div'):
with tag('p'):
print('content 1')
print()
# multiple
with tag('div'), tag('h1'):
print('content 2')
In [24]:
from contextlib import redirect_stdout
with open('test_redirect.txt', 'w+') as f:
with redirect_stdout(f):
help(redirect_stdout)
print('afun defined message')
In [25]:
from contextlib import contextmanager
import time
@contextmanager
def timeit(name=''):
start_time = time.time()
yield
elapse_time = time.time() - start_time
print('{} - completed in {:.6f}'.format(name, elapse_time))
# note: comprehension speed seens slower when range(10)
test = [i for i in range(100000)]
with timeit('afun 1'):
a = [i for i in test if i % 2 == 0]
with timeit('afun 2'):
b = []
for i in test:
if i % 2 == 0:
b.append(i)
@timeit('afun 3')
def func(test_list):
result = []
for i in test_list:
if i % 2 == 0:
result.append(i)
return result
c = func(test)
In [26]:
from functools import wraps
# contextmanager 原始碼
# 這邊就是我們上面提過的 decorator 寫法
# 雖然他是回傳 _GeneratorContextManager 這個 helper class, 但其實這邊主要是透過 ContextDecorator 實作
def contextmanager(func):
@wraps(func)
def helper(*args, **kwds):
return _GeneratorContextManager(func, args, kwds)
return helper
# ContextDecorator 原始碼
# _recreate_cm 會回傳自己的 instance
class ContextDecorator(object):
def _recreate_cm(self):
return self
def __call__(self, func):
@wraps(func)
def inner(*args, **kwds):
with self._recreate_cm():
return func(*args, **kwds)
return inner
透過上面的原始碼你可以發現 contextmanager
目前有兩點限制