做网站毕业设计存在的问题,设计师网红cad图库,建设工程信息网官网入口,如何做网站开屏虽然人们能利用函数闭包(function clouser)写出简单的装饰器#xff0c;但其可用范围常受限制。多数实现装饰器的基本方式会破坏与内省(Introspection)的关联性。可大多数人会说#xff1a;who cares#xff01;但我仍坚持追求正确地写出漂亮代码。我爱内省(introspection)但其可用范围常受限制。多数实现装饰器的基本方式会破坏与内省(Introspection)的关联性。可大多数人会说who cares但我仍坚持追求正确地写出漂亮代码。我爱内省(introspection)讨厌猴子补丁(Monkey Patching)请记住以下两点要为被装饰器包裹的函数(wrapped function)保留内省功能。要理解清楚Python对象模型的执行方式如何工作。接下来我会通过14篇blog来向你解释你的典型Python装饰器及包裹的函数哪里有问题如何修复这些问题以下是第一篇内容我会从几个方面简单说明你的典型Python装饰器如何产生问题。Python 装饰器基础知识人皆所知Python装饰器语法如下function_wrapperdef function():pass符号为自Python2.4引入的装饰器的语法糖(syntactic sugar), 它等同以下写法def function():passfunction function_wrapper(function)此装饰器语法用于包裹定义或修改的函数装饰器与猴子补丁不同前者作用于定义时后者作用于运行时函数wrapper剖析以下用class来实现一个装饰器class function_wrapper(object):def __init__(self, wrapped):self.wrapped wrappeddef __call__(self, *args, **kwargs):return self.wrapped(*args, **kwargs)function_wrapperdef function():pass以上例子class实例初始化后会在其内部记录一个原函数(self.wrapped wrapped)在调用这个被class装饰器包裹起来的函数时实际上是通过调用class对象的__call()__方法来调用原函数。你可以通过装饰器在调用原函数之前或之后实现一些额外的功能。如需修改传递给原函数的输入参数或原函数返回的结果你只要在__call__()方法内进行修改。用class来实现装饰器或许不太流行(2014年)。普遍用函数闭包来实现装饰器。函数闭包实现方式为利用嵌套函数逐层返回传入的原函数(wrapped)。代码如下def function_wrapper(wrapped):def _wrapper(*args, **kwargs):return wrapped(*args, **kwargs)return _wrapperfunction_wrapperdef function():pass此例中无明显地给内嵌函数_wrapper传入原函数wrapped内嵌函数仍可通过外层函数function_wrapper的参数访问到原函数(闭包原理)与用class实现装饰器相比此做法方便多了。Introspecting a function函数内省我们期望函数可指定一些与描述自身相关的特性(properties)如__name__ 及 __doc__ 这样的属性。当我们把以函数闭包方式实现的装饰器应用到普通函数时函数的这些属性会发生意料之外的变化。这些属性细节为内嵌函数提供。def function_wrapper(wrapped):def _wrapper(*args, **kwargs):return wrapped(*args, **kwargs)return _wrapperfunction_wrapperdef function():pass print(function.__name__)_wrapper若以class方式实现的wrapper类实例通常不带有__name__属性以此方式去尝试访问原函数的name属性时会得到一个AttributeError异常class function_wrapper(object):def __init__(self, wrapped):self.wrapped wrappeddef __call__(self, *args, **kwargs):return self.wrapped(*args, **kwargs)function_wrapperdef function():pass print(function.__name__)Traceback (most recent call last):File , line 1, in AttributeError: function_wrapper object has no attribute __name__当以函数闭包方式实现装饰器時为保留原函数相关信息我们可以把原函数的相关属性Copy一份给内嵌函数。如下例可正确获得原函数的__name__及__doc__内容。def function_wrapper(wrapped):def _wrapper(*args, **kwargs):return wrapped(*args, **kwargs)_wrapper.__name__ wrapped.__name___wrapper.__doc__ wrapped.__doc__return _wrapperfunction_wrapperdef function():pass print(function.__name__)function这样Copy属性实在费力将来如有要追加的属性还得更新代码。例如我们想Copy__module__还有Python 3新增加的__qualname__及__annotations__属性。我们可以利用Python标准库提供的functools.wraps()装饰器来实现这些需求。import functoolsdef function_wrapper(wrapped):functools.wraps(wrapped)def _wrapper(*args, **kwargs):return wrapped(*args, **kwargs)return _wrapperfunction_wrapperdef function():pass print(function.__name__)function如以class方式实现装饰器则可用functools.update_wrapper()如下例所示import functoolsclass function_wrapper(object):def __init__(self, wrapped):self.wrapped wrappedfunctools.update_wrapper(self, wrapped)def __call__(self, *args, **kwargs):return self.wrapped(*args, **kwargs)虽然functools.wraps()能解决诸如访问原函数的__name__及__doc__的问题但实际上并没有完美解决函数内省接下来你会看到。当我们查询被装饰器包裹的原函数的参数定义时返回的结果却是wrapper的参数定义。以函数闭包实现的装饰器为例返回的为内嵌函数的参数定义。因此装饰器不具签名保护(not signature preserving)import inspectdef function_wrapper(wrapped):def _wrapper(*arg, **kwarg):return wrapped(*arg, **kwarg)return _wrapperfunction_wrapperdef function(arg1, arg2): pass print(inspect.signature(function))(*arg, **kwarg)以class实现的装饰器也是同样的结果。import inspectclass function_wrapper:def __init__(self, wrapped):self.wrapped wrappeddef __call__(self, *arg, **kwarg):return self.wrapped(*arg, **kwarg)function_wrapperdef function(arg1, arg2): pass print(inspect.signature(function))(*arg, **kwarg)另一个和内省相关的例子是当用inspect.getsource()尝试返回函数(此函数被以class方式实现的装饰器包裹起来)的源码时会得到一个TypeError异常。TypeError: __main__.function_wrapper object at is not a module, class, method,function, traceback, frame, or code objectThe terminal process terminated with exit code: 1包裹class方法和普通函数一样装饰器也可应用在class的方法上。Python内置的两个特殊装饰器——staticmethod和classmethod可将普通的实例方法(instance method)转化为class相关的特殊方法。虽然这些特殊方法也隐含着一些问题。class Class(object):function_wrapperdef method(self):passclassmethoddef cmethod(cls):passstaticmethoddef smethod():pass首先就算在你的装饰器里用上了 functools.wraps() 或 functools.update_wrapper()当你把这个装饰器放在 classmethod 或 staticmethod前面时依然会得到一个异常。这是因为依然有一些属性并未被functools.wraps()或functools.update_wrapper()Copy进来。以下为Python2的运行情况。class Class(object):function_wrapperclassmethoddef cmethod(cls):passTraceback (most recent call last):File , line 1, in File , line 3, in ClassFile , line 2, in wrapperFile .../functools.py, line 33, in update_wrappersetattr(wrapper, attr, getattr(wrapped, attr))AttributeError: classmethod object has no attribute __module__此为Python2的bug所致此bug已在Python3中得到修正。就算在Python3中运行依然有异常抛出。那是因为两个包裹类型(wrapper types即function_wrapper及classmethod)都期望被包裹函数(wrapped function)是可以被直接调用的(callable)。此被包裹的函数可称之为描述器(descriptor)。这意味为了返回一个可调用的描述器它(描述器)须先正确地与实例绑定起来。参考以下代码class Class(object):function_wrapperclassmethoddef cmethod(cls):pass Class.cmethod()Traceback (most recent call last):File classmethod.py, line 15, in Class.cmethod()File classmethod.py, line 6, in _wrapperreturn wrapped(*args, **kwargs)TypeError: classmethod object is not callable简单并非意味着正确虽然我们可以简单地实现装饰器并不见得这些装饰器必然正确及长久有效。至此比较突出的问题如下保留函数的 __name__ and __doc__。保留函数的参数定义。保留获取函数源码的能力。能够在带有描述器协议的其他装饰器上应用自己所写的装饰器。functools.wraps() 为我们解决了第一个问题但不能一劳永逸。例如不能解决内省相关的问题。就算能解决内省相关的问题简单实现的装饰器依然会破坏python对象的执行模型譬如被装饰器包裹着的带描述器协议的对象。第三方包(packages)如decorator模块尝试解决这些问题但只能解决前面两点问题。通用猴子补丁动态地应用函数包装器(function wrapper)时依然会发生问题。我们找出了一些问题后续博文中我们会看到如何解决这些问题。而且你也会写出优雅的装饰器。请继续关注我下期博文希望我能保持继续写博的冲劲。