python:裝飾器與偏函數

裝飾器

對於某個函數或類,需要增添大量類似的需求。對於類來說,可以用繼承解決,但是繼承會增加大量的靜態屬性,子類會變得越來越臃腫;對於函數來說,每個需求增加一個函數封裝過於繁瑣。
裝飾器的出現解決了這一問題。裝飾器本身是一個python函數,它可以讓其它函數不發生變動的情況下增加額外的功能,裝飾器的返回值是函數對象。
直觀來說,裝飾器就是爲已有代碼增添新的功能。
原代碼:

def foo():
    print('i am foo')

此時,希望增加打印日誌功能
使用函數封裝:

def use_logging(func):
	logging.warn("%s is running" % func.__name__)
    func()
    
use_logging(foo)

使用裝飾器:

def use_logging(func):
	def warpper(*args,**kwargs):
		loggin.warn("%s is running"% func.__name__)
		return func(*args,**kwargs)
	return warpper

foo = use_logging(foo)
foo()

語法糖@可以幫助我們減少一次函數定義:

def use_logging(func):
	def warpper(*args,**kwargs):
		loggin.warn("%s is running"% func.__name__)
		return func(*args,**kwargs)
	return warpper

@use_logging   # 對要擴展功能的函數使用語法糖@
def foo():
    print('i am foo')

foo()

語法糖@幫我們省去了foo = use_logging(foo)這一句。

帶參裝飾器
使用裝飾器時,可以對裝飾器傳遞參數,如@use_logging(level)

def use_logging(level):
	def decorator(func):
		def wrapper(*args, **kwargs):
			if level == "warn":
				logging.warn("%s is running" % func.__name__)
			return func(*args)
		return wrapper
	return decorator

@use_logging(level="warn")
def foo(name='foo'):
	print("i am %s" % name)

foo()

上面的use_logging是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,並返回一個裝飾器。我們可以將它理解爲一個含有參數的閉包。當我 們使用@use_logging(level=“warn”)調用的時候,Python能夠發現這一層的封裝,並把參數傳遞到裝飾器的環境中。

類裝飾器
相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器還可以依靠類內部的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。

class Foo(object):
	def __init__(self, func):
	self._func = func
	
	def __call__(self):
		print ('class decorator runing')
		self._func()
		print ('class decorator ending')

@Foo
def bar():
	print ('bar')

bar()

functools.wraps
使用裝飾器極大地複用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表:

def logged(func):
	def with_logging(*args, **kwargs):
		print func.__name__ + " was called"
		return func(*args, **kwargs)
	return with_logging

@logged
def f(x):
	"""does some math"""
	return x + x * x

該函數完成等價於:

def f(x):
	"""does some math"""
	return x + x * x

f = logged(f)

不難發現,函數f被with_logging取代了,當然它的docstring,__name__就是變成了with_logging函數的信息了。

functools.wraps能解決這一問題。wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。

from functools import wraps

def logged(func):
	@wraps(func)  # add
	def with_logging(*args, **kwargs):
		print func.__name__ + " was called"
		return func(*args, **kwargs)
	return with_logging

@logged
def f(x):
	"""does some math"""
	return x + x * x
	print f.__name__ # prints 'f'print f.__doc__ # prints 'does some math'

內置裝飾器
@staticmathod、@classmethod、@property

裝飾器的順序:

@a
@b
@c
def f ():

等效於f = a(b(c(f)))

偏函數

偏函數可以設定函數的默認行爲.
例子:通過設定參數來讓int()函數默認轉換成八進制數字

# 首先導入 functools 模塊
import functools

# 定義一個默認轉換爲二進制的int函數
int2 = functools.partial(int, base=2)
# 調用 
int2('10110101')
181

functools.partial的作用就是將固定一個函數的默認行爲,從而簡化之後的使用
這個方法可以接收函數、*args、**kwargs這些對象

當函數的參數個數太多,需要簡化時,使用functools.partial可以創建一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單。

參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章