PythonDecorator

PythonDecorator

2023年6月29日发(作者:)

PythonDecorator由于没时间编写,就把⼏张写的不错的⽂章摘录整合到⼀起。原⽂地址: 

  Python的修饰器的英⽂名叫Decorator,当你看到这个英⽂名的时候,你可能会把其跟Design Pattern⾥的Decorator搞混了,其实这是完全不同的两个东西。在认识装饰器之前,我们先来点感性认识,看⼀个Python修饰器的Hello World的代码。下⾯是代码:⽂件名: hello(fn): def wrapper(): print "hello, %s" % fn.__name__ fn() print "goodby, %s" % fn.__name__ return wrapper

@hellodef foo(): print "i am foo"

foo() 当你运⾏代码,你会看到如下输出:[chenaho@chenhao-air]$ python o, fooi am foogoodby, foo 你可以看到如下的东西:  1)函数foo前⾯有个@hello的“注解”,hello就是我们前⾯定义的函数hello;  2)在hello函数中,其需要⼀个fn的参数(这就⽤来做回调的函数);  3)hello函数中返回了⼀个inner函数wrapper,这个wrapper函数回调了传进来的fn,并在回调前后加了两条语句。Decorator 的本质  对于Python的这个@注解语法糖- Syntactic Sugar 来说,当你在⽤某个@decorator来修饰某个函数func时,如下所⽰:@decoratordef func(): pass其解释器会解释成下⾯这样的语句:func = decorator(func)   了然,这不就是把⼀个函数当参数传到另⼀个函数中,然后再回调吗?是的,但是,我们需要注意,那⾥还有⼀个赋值语句,把decorator这个函数的返回值赋值回了原来的func。 根据《函数式编程》中的first class functions中的定义的,你可以把函数当成变量来使⽤,所以,decorator必需得返回了⼀个函数出来给func,这就是所谓的higher order function ⾼阶函数,不然,后⾯当func()调⽤的时候就会出错。 就我们上⾯那个⾥的例⼦来说,@hellodef foo(): print "i am foo" 被解释成了:foo = hello(foo) 是的,这是⼀条语句,⽽且还被执⾏了。你如果不信的话,你可以写这样的程序来试试看:def fuck(fn): print "fuck %s!" % fn.__name__[::-1].upper()

@fuckdef wfg(): pass 没了,就上⾯这段代码,没有调⽤wfg()的语句,你会发现, fuck函数被调⽤了,⽽且还很NB地输出了我们每个⼈的⼼声!  再回到我们的那个例⼦,我们可以看到,hello(foo)返回了wrapper()函数,所以,foo其实变成了wrapper的⼀个变量,⽽后⾯的foo()执⾏其实变成了wrapper()。知道这点本质,当你看到有多个decorator或是带参数的decorator,你也就不会害怕了。⽐如:多个decorator:@decorator_one@decorator_twodef func(): pass 相当于:func = decorator_one(decorator_two(func)) ⽐如:带参数的decorator:@decorator(arg1, arg2)def func(): pass 相当于:func = decorator(arg1,arg2)(func) 这意味着decorator(arg1, arg2)这个函数需要返回⼀个“真正的decorator”。带参数及多个Decrorator我们来看⼀个有点意义的例⼦:def makeHtmlTag(tag, *args, **kwds): def real_decorator(fn): css_class = " class='{0}'".format(kwds["css_class"]) if "css_class" in kwds else "" def wrapped(*args, **kwds): return "<"+tag+css_class+">" + fn(*args, **kwds) + "" return wrapped return real_decorator

@makeHtmlTag(tag="b", css_class="bold_css")@makeHtmlTag(tag="i", css_class="italic_css")def hello(): return "hello world"

print hello()

# 输出:# hello world 在上⾯这个例⼦中,我们可以看到:makeHtmlTag有两个参数。所以,为了让 hello = makeHtmlTag(arg1, arg2)(hello) 成功,makeHtmlTag 必需返回⼀个decorator(这就是为什么我们在makeHtmlTag中加⼊了real_decorator()的原因),这样⼀来,我们就可以进⼊到 decorator 的逻辑中去了——decorator得返回⼀个wrapper,wrapper⾥回调hello。看似那个makeHtmlTag() 写得层层叠叠,但是,已经了解了本质的我们觉得写得很⾃然。初识Decorator  Decorator,修饰符,是在Python2.4中增加的功能,也是pythoner实现元编程的最新⽅式,同时它也是最简单的元编程⽅式。为什么是“最简单”呢?是的,其实在Decorator之前就已经有classmethod()和staticmethod()内置函数,但他们的缺陷是会导致函数名的重复使⽤(可以看看David Mertz的Charming Python: Decorators make magic easy ),以下是摘⾃他本⼈的原⽂:class C: def foo(cls, y): print "classmethod", cls, y foo = classmethod(foo)  是的,classmethod做的只是函数转换,但是它却让foo这个名字另外出现了2次。记得有⼀句话是:⼈类因懒惰⽽进步。Decorator的诞⽣,让foo少出现2次。class C: @classmethod def foo(cls, y): print "classmethod", cls, y  读者也许已经想到Decorator在python中是怎么处理的了(如果还没头绪的,强烈建议先去看看limodou写的Decorator学习笔记 )。下⾯我列出4种⽤法。单个 Decorator,不带参数  设想⼀个情景,你平时去买⾐服的时候,跟售货员是怎么对话的呢?售货员会先向你问好,然后你会试穿某件你喜爱的⾐服。def salesgirl(method): def serve(*args): print "Salesgirl:Hello, what do you want?", method.__name__ method(*args) return serve

@salesgirldef try_this_shirt(size): if size < 35: print "I: %d inches is to small to me" %(size) else: print "I:%d inches is just enough" %(size)try_this_shirt(38)

结果是:Salesgirl:Hello, what do you want? try_this_shirtI:38 inches is just enough  这只是⼀个太简单的例⼦,以⾄⼀些“细节”没有处理好,你试穿完了好⽍也告诉salesgirl到底要不要买啊。。。这样try_this_shirt⽅法需要改成带返回值

(假设是bool类型,True就是要买,False就是不想买),那么salesgirl中的serve也应该带返回值,并且返回值就是 method(*args)。修改后的salesgirl

def salesgirl(method): def serve(*args): print "Salesgirl:Hello, what do you want?", method.__name__ return method(*args) return serve

@salesgirldef try_this_shirt(size): if size < 35: print "I: %d inches is to small to me" %(size) return False else: print "I:%d inches is just enough" %(size) return Trueresult = try_this_shirt(38)print "Mum:do you want to buy this?", result结果是:Salesgirl:Hello, what do you want? try_this_shirtI:38 inches is just enoughMum:do you want to buy this? True 现在我们的salesgirl还不算合格,她只会给客⼈打招呼,但是客⼈要是买⾐服了,也不会给他报价;客⼈不买的话,也应该推荐其他款式!会报价的salesgirl:def salesgirl(method): def serve(*args): print "Salesgirl:Hello, what do you want?", method.__name__ result = method(*args) if result: print "Salesgirl: This shirt is 50$." else: print "Salesgirl: Well, how about trying another style?" return result return serve

@salesgirldef try_this_shirt(size): if size < 35: print "I: %d inches is to small to me" %(size) return False else: print "I:%d inches is just enough" %(size) return Trueresult = try_this_shirt(38)print "Mum:do you want to buy this?", result结果是:Salesgirl:Hello, what do you want? try_this_shirtI:38 inches is just enoughSalesgirl: This shirt is 50$.Mum:do you want to buy this? True这样的salesgirl总算是合格了,但离出⾊还很远,称职的salesgirl是应该对熟客让利,⽼⽤户总得有点好处吧?单个 Decorator,带参数  会报价并且带折扣的salesgirl:def salesgirl(discount): def expense(method): def serve(*args): print "Salesgirl:Hello, what do you want?", method.__name__ result = method(*args) if result: print "Salesgirl: This shirt is 50$.As an old user, we promised to discount at %d%%" %(discount) else: print "Salesgirl: Well, how about trying another style?" return result return serve return expense

@salesgirl(50)def try_this_shirt(size): if size < 35: print "I: %d inches is to small to me" %(size) return False else: print "I:%d inches is just enough" %(size) return Trueresult = try_this_shirt(38)print "Mum:do you want to buy this?", result

结果是:Salesgirl:Hello, what do you want? try_this_shirtI:38 inches is just enoughSalesgirl: This shirt is 50$.As an old user, we promised to discount at 50%Mum:do you want to buy this? True  这⾥定义的salesgirl是会给客户50%的折扣,因为salesgirl描述符是带参数,⽽参数就是折扣。如果你是第⼀次看到这个 salesgirl,会被她⾥⾯嵌套的2个⽅法⽽感到意外,没关系,当你习惯了Decorator之后,⼀切都变得很亲切啦  你看,Python的Decorator就是这么简单,没有什么复杂的东西,你也不需要了解过多的东西,使⽤起来就是那么⾃然、体贴,但是你觉得上⾯那个带参数的Decorator的函数嵌套太多了,你受不了。好吧,没事,我们看看下⾯的⽅法。class式的 Decorator(重点)1、⾸先,先得说⼀下,decorator的class⽅式,还是看个⽰例:class myDecorator(object):

def __init__(self, fn): print "inside myDecorator.__init__()" = fn

def __call__(self): () print "inside myDecorator.__call__()"

@myDecoratordef aFunction(): print "inside aFunction()"

print "Finished decorating aFunction()"

aFunction()

# 输出:# inside myDecorator.__init__()# Finished decorating aFunction()# inside aFunction()# inside myDecorator.__call__() 上⾯这个⽰例展⽰了,⽤类的⽅式声明⼀个decorator。我们可以看到这个类中有两个成员:  1)⼀个是__init__(),这个⽅法是在我们给某个函数decorator时被调⽤,所以,需要有⼀个fn的参数,也就是被decorator的函数。  2)⼀个是__call__(),这个⽅法是在我们调⽤被decorator函数时被调⽤的。上⾯输出可以看到整个程序的执⾏顺序。这看上去要⽐“函数式”的⽅式更易读⼀些。2、下⾯,我们来看看⽤类的⽅式来重写上⾯的的代码:s makeHtmlTagClass(object):

def __init__(self, tag, css_class=""): self._tag = tag self._css_class = " class='{0}'".format(css_class) if css_class !="" else ""

def __call__(self, fn): def wrapped(*args, **kwargs): return "<" + self._tag + self._css_class+">" + fn(*args, **kwargs) + "" return wrapped

@makeHtmlTagClass(tag="b", css_class="bold_css")@makeHtmlTagClass(tag="i", css_class="italic_css")def hello(name): return "Hello, {}".format(name)

print hello("Hao Chen") 上⾯这段代码中,我们需要注意这⼏点:  1)如果decorator有参数的话,__init__() 成员就不能传⼊fn了,⽽fn是在__call__的时候传⼊的。  2)这段代码还展⽰了 wrapped(*args, **kwargs) 这种⽅式来传递被decorator函数的参数。(其中:args是⼀个参数列表,kwargs是参数dict,具体的细节,请参考Python的⽂档或是StackOverflow的这个问题,这⾥就不展开了)3、⽤Decorator设置函数的调⽤参数,你有三种⽅法可以⼲这个事:第⼀种,通过 **kwargs,这种⽅法decorator会在kwargs中注⼊参数。def decorate_A(function): def wrap_function(*args, **kwargs): kwargs['str'] = 'Hello!' return function(*args, **kwargs) return wrap_function

@decorate_Adef print_message_A(*args, **kwargs): print(kwargs['str'])

print_message_A() 第⼆种,约定好参数,直接修改参数def decorate_B(function): def wrap_function(*args, **kwargs): str = 'Hello!' return function(str, *args, **kwargs) return wrap_function

@decorate_Bdef print_message_B(str, *args, **kwargs): print(str)

print_message_B() 第三种,通过 *args 注⼊def decorate_C(function): def wrap_function(*args, **kwargs): str = 'Hello!' #(1, str) args = args +(str,) return function(*args, **kwargs) return wrap_function

class Printer: @decorate_C def print_message(self, str, *args, **kwargs): print(str)

p = Printer()_message() Decorator的副作⽤  到这⾥,我相信你应该了解了整个Python的decorator的原理了。相信你也会发现,被decorator的函数其实已经是另外⼀个函数了,对于最前⾯那个的例⼦来说,如果你查询⼀下foo.__name__的话,你会发现其输出的是“wrapper”,⽽不是我们期望的“foo”,这会给我们的程序埋⼀些坑。所以,Python的functool包中提供了⼀个叫wrap的decorator来消除这样的副作⽤。下⾯是我们新版本的。⽂件名: functools import wrapsdef hello(fn): @wraps(fn) def wrapper(): print "hello, %s" % fn.__name__ fn() print "goodby, %s" % fn.__name__ return wrapper

@hellodef foo(): '''foo help doc''' print "i am foo" pass

foo()print foo.__name__ #输出 fooprint foo.__doc__ #输出 foo help doc 当然,即使是你⽤了functools的wraps,也不能完全消除这样的副作⽤。来看下⾯这个⽰例:from inspect import getmembers, getargspecfrom functools import wraps

def wraps_decorator(f): @wraps(f) def wraps_wrapper(*args, **kwargs): return f(*args, **kwargs) return wraps_wrapper

class SomeClass(object): @wraps_decorator def method(self, x, y): pass

obj = SomeClass()for name, func in getmembers(obj, predicate=od): print "Member Name: %s" % name print "Func Name: %s" % _name print "Args: %s" % getargspec(func)[0]

# 输出:# Member Name: method# Func Name: method# Args: [] 你会发现,即使是你你⽤了functools的wraps,你在⽤getargspec时,参数也不见了。要修正这⼀问,我们还得⽤Python的反射来解决,下⾯是相关的代码:def get_true_argspec(method): argspec = spec(method) args = argspec[0] if args and args[0] == 'self': return argspec if hasattr(method, '__func__'): method = method.__func__ if not hasattr(method, 'func_closure') or _closure is None: raise Exception("No closure for method.") method = _closure[0].cell_contents return get_true_argspec(method) 当然,我相信⼤多数⼈的程序都不会去getargspec。所以,⽤functools的wraps应该够⽤了。⼀些decorator的⽰例。好了,现在我们来看⼀下各种decorator的例⼦:给函数调⽤做缓存。这个例实在是太经典了,整个⽹上都⽤这个例⼦做decorator的经典范例,因为太经典了,所以,我这篇⽂章也不能免俗。from functools import wrapsdef memo(fn): cache = {} miss = object()

@wraps(fn) def wrapper(*args): result = (args, miss) if result is miss: result = fn(*args) cache[args] = result return result

return wrapper

@memodef fib(n): if n < 2: return n return fib(n - 1) + fib(n - 2)  上⾯这个例⼦中,是⼀个斐波拉契数例的递归算法。我们知道,这个递归是相当没有效率的,因为会重复调⽤。⽐如:我们要计算fib(5),于是其分解成fib(4) + fib(3),⽽fib(4)分解成fib(3)+fib(2),fib(3)⼜分解成fib(2)+fib(1)…… 你可看到,基本上来说,fib(3), fib(2), fib(1)在整个递归过程中被调⽤了两次。⽽我们⽤decorator,在调⽤函数前查询⼀下缓存,如果没有才调⽤了,有了就从缓存中返回值。⼀下⼦,这个递归从⼆叉树式的递归成了线性的递归。Profiler的例⼦  这个例⼦没什么⾼深的,就是实⽤⼀些。import cProfile, pstats, StringIO

def profiler(func): def wrapper(*args, **kwargs): datafn = func.__name__ + ".profile" # Name the data file prof = e() retval = l(func, *args, **kwargs) #_stats(datafn) s = IO() sortby = 'cumulative' ps = (prof, stream=s).sort_stats(sortby) _stats() print ue() return retval

return wrapper 注册回调函数下⾯这个⽰例展⽰了通过URL的路由来调⽤相关注册的函数⽰例:class MyApp(): def __init__(self): _map = {}

def register(self, name): def func_wrapper(func): _map[name] = func return func return func_wrapper

def call_method(self, name=None): func = _(name, None) if func is None: raise Exception("No function registered against - " + str(name)) return func()

app = MyApp()

@er('/')def main_page_func(): return "This is the main page."

@er('/next_page')def next_page_func(): return "This is the next page."

print _method('/')print _method('/next_page') 注意:  1)上⾯这个⽰例中,⽤类的实例来做decorator。  2)decorator类中没有__call__(),但是wrapper返回了原函数。所以,原函数没有发⽣任何变化。给函数打⽇志下⾯这个⽰例演⽰了⼀个logger的decorator,这个decorator输出了函数名,参数,返回值,和运⾏时间。from functools import wrapsdef logger(fn): @wraps(fn) def wrapper(*args, **kwargs): ts = () result = fn(*args, **kwargs) te = () print "function = {0}".format(fn.__name__) print " arguments = {0} {1}".format(args, kwargs) print " return = {0}".format(result) print " time = %.6f sec" % (te-ts) return result return wrapper

@loggerdef multipy(x, y): return x * y

@loggerdef sum_num(n): s = 0 for i in xrange(n+1): s += i return s

print multipy(2, 10)print sum_num(100)print sum_num(10000000) 上⾯那个打⽇志还是有点粗糙,让我们看⼀个更好⼀点的(带log level参数的):import inspectdef get_line_number(): return tframe().f_back.f_back.f_lineno

def logger(loglevel): def log_decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): ts = () result = fn(*args, **kwargs) te = () print "function = " + fn.__name__, print " arguments = {0} {1}".format(args, kwargs) print " return = {0}".format(result) print " time = %.6f sec" % (te-ts) if (loglevel == 'debug'): print " called_from_line : " + str(get_line_number()) return result return wrapper return log_decorator 但是,上⾯这个带log level参数的有两具不好的地⽅,  1) loglevel不是debug的时候,还是要计算函数调⽤的时间。  2) 不同level的要写在⼀起,不易读。我们再接着改进:mport inspect

def advance_logger(loglevel):

def get_line_number(): return tframe().f_back.f_back.f_lineno

def _basic_log(fn, result, *args, **kwargs): print "function = " + fn.__name__, print " arguments = {0} {1}".format(args, kwargs) print " return = {0}".format(result)

def info_log_decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): result = fn(*args, **kwargs) _basic_log(fn, result, args, kwargs) return wrapper

def debug_log_decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): ts = () result = fn(*args, **kwargs) te = () _basic_log(fn, result, args, kwargs) print " time = %.6f sec" % (te-ts) print " called_from_line : " + str(get_line_number()) return wrapper

if loglevel is "debug": return debug_log_decorator else: return info_log_decorator 你可以看到两点,  1)我们分了两个log level,⼀个是info的,⼀个是debug的,然后我们在外尾根据不同的参数返回不同的decorator。  2)我们把info和debug中的相同的代码抽到了⼀个叫_basic_log的函数⾥,DRY原则。⼀个MySQL的Decorator  下⾯这个decorator是我在⼯作中⽤到的代码,我简化了⼀下,把DB连接池的代码去掉了,这样能简单点,⽅便阅读。import umysqlfrom functools import wraps

class Configuraion: def __init__(self, env): if env == "Prod": = "" = 3306 = "coolshell" = "coolshell" = "fuckgfw" elif env == "Test": = 'localhost' = 3300 = 'coolshell' = 'coolshell' = 'fuckgfw'

def mysql(sql):

_conf = Configuraion(env="Prod")

def on_sql_error(err): print err (-1)

def handle_sql_result(rs): if > 0: fieldnames = [f[0] for f in ] return [dict(zip(fieldnames, r)) for r in ] else: return []

def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): mysqlconn = tion() eout(5) t(_, _, _, _, _, True, 'utf8') try: rs = (sql, {}) except as e: on_sql_error(e)

data = handle_sql_result(rs) kwargs["data"] = data result = fn(*args, **kwargs) () return result return wrapper

return decorator

@mysql(sql = "select * from coolshell" )def get_coolshell(data): ... ... ... .. 线程异步下⾯量个⾮常简单的异步执⾏的decorator,注意,异步处理并不简单,下⾯只是⼀个⽰例。from threading import Threadfrom functools import wraps

def async(func): @wraps(func) def async_func(*args, **kwargs): func_hl = Thread(target = func, args = args, kwargs = kwargs) func_() return func_hl return async_func

if __name__ == '__main__': from time import sleep

@async def print_somedata(): print 'starting print_somedata' sleep(2) print 'print_somedata: 2 sec passed' sleep(2) print 'print_somedata: 2 sec passed' sleep(2) print 'finished print_somedata'

def main(): print_somedata() print 'back in main' print_somedata() print 'back in main'

main()

发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1687977540a62878.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信