Python-装饰

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python-装饰相关的知识,希望对你有一定的参考价值。

装饰

介绍

装饰器功能是软件设计模式。它们动态地改变函数,方法或类的功能,而不必直接使用子类或改变修饰函数的源代码。当使用正确时,装饰器可以成为开发过程中强大的工具。本主题涵盖了Python中装饰器函数的实现和应用。

 

装饰器功能

装饰器增强其他函数或方法的行为。这需要一个函数作为参数,并返回一个增强功能的任何功能可以用作装饰

# This simplest decorator does nothing to the function being decorated. Such
# minimal decorators can occasionally be used as a kind of code markers.
def super_secret_function(f):
    return f

@super_secret_function
def my_function():
    print("This is my secret function.")

@-notation是语法糖,它等效于以下内容:

my_function = super_secret_function(my_function)

重要的是要记住这一点,以了解装饰工作如何工作。这个“unsugared”语法清楚了为什么装饰器函数将一个函数作为一个参数,为什么它应该返回另一个函数。这也表明,如果你会发生什么,返回一个函数:

def disabled(f):
    """
    This function returns nothing, and hence removes the decorated function
    from the local scope.
    """
    pass

@disabled
def my_function():
    print("This function can no longer be called...")

my_function()
# TypeError: ‘NoneType‘ object is not callable

因此,我们通常定义了一个新的功能,装饰内部,并将其返回。这个新函数将首先执行它需要做的事情,然后调用原始函数,最后处理返回值。考虑下面一个简单的logger装饰器的例子:

def log_function_calls(func):
    """
    Augment the target function, so that whenever someone calls it we will
    log a message to the stdout.
    """
    def wrapped_func(*args, **kwargs):
        # Print a log message before the function is called.
        str_args = ", ".join(repr(a) for a in args)
        str_kwargs = ", ".join("%s=%r" % (k, v) for k, v in kwargs.items())
        str_all = ", ".join([str_args, str_kwargs])
        print("Calling %s(%s)..." % (func.__name__, str_all))
        try:
            # Call the original function.
            res = func(*args, **kwargs)
            # Log the returned result.
            print("%s(...) returned: %r" % (func.__name__, res))
            # Return the original result.
            return res
        except Exception as e:
            # Log the fact that the function threw an exception.
            print("Raised an exception %s" % e)
            # Re-raise the original exception.
            raise
    # Return the wrapped_func, which will replace the function being decorated.
    return wrapped_func

@log_function_calls
def testfunc(*args, **kwargs):
    print("  This is my function I‘d like to debug")
    print("  Got %d *args and %d **kwargs" % (len(args), len(kwargs)))
    return "".join(args) * kwargs.get("count", 1)

# Try it out:
hoho = testfunc("h", "o", count=10)
print()
apples = testfunc("apple", count="orange")

输出:

Calling testfunc(h, o, count=10)
  This is my function Id like to debug
  Got 2 *args and 1 **kwargs
testfunc(...) returned: hohohohohohohohohoho

Calling testfunc(apple, count=orange)
  This is my function Id like to debug
  Got 1 *args and 1 **kwargs
Raised an exception cant multiply sequence by non-int of type strTypeError: cant multiply sequence by non-int of type str

 

装饰类

如在介绍中提到的,装饰器是可以应用于另一个函数以增强其行为的函数。该语法糖等同于以下内容:my_func = decorator(my_func)但是,如果decorator是不是类?语法,仍能正常工作,但现在my_func获取与实例代替decorator类。如果此类实现了__call__()魔术方法,那么这将仍然有可能使用my_func,就好像它是一个功能:

class Decorator(object):
    """Simple decorator class."""

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print(Before the function call.)
        res = self.func(*args, **kwargs)
        print(After the function call.)
        return res

@Decorator
def testfunc():
    print(Inside the function.)

testfunc()
# Before the function call.
# Inside the function.
# After the function call.

注意,用类装饰器装饰的函数将不再被视为来自类型检查透视的“函数”:

import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class ‘__main__.Decorator‘>

装饰方法

对于装饰方法,你需要定义一个额外的__get__-method:

from types import MethodType

class Decorator(object):
    def __init__(self, func):
        self.func = func
        
    def __call__(self, *args, **kwargs):
        print(Inside the decorator.)
        return self.func(*args, **kwargs)
    
    def __get__(self, instance, cls):
        # Return a Method if it is called on an instance
        return self if instance is None else MethodType(self, instance)

class Test(object):
    @Decorator
    def __init__(self):
        pass
    
a = Test()

里面的装饰。

警告!

类装饰器仅为特定函数生成一个实例,因此使用类装饰器装饰方法将在该类的所有实例之间共享相同的装饰器:

from types import MethodType

class CountCallsDecorator(object):
    def __init__(self, func):
        self.func = func
        self.ncalls = 0    # Number of calls of this method
        
    def __call__(self, *args, **kwargs):
        self.ncalls += 1   # Increment the calls counter
        return self.func(*args, **kwargs)
    
    def __get__(self, instance, cls):
        return self if instance is None else MethodType(self, instance)

class Test(object):
    def __init__(self):
        pass
    
    @CountCallsDecorator
    def do_something(self):
        return something was done
    
a = Test()
a.do_something()
a.do_something.ncalls   # 1
b = Test()
b.do_something()
b.do_something.ncalls   # 2

 

参数装饰器(装饰工厂)

装饰器只需要一个参数:要装饰的函数。没有办法传递其他参数。

但是通常需要额外的参数。诀窍是然后做一个函数,它接受任意参数并返回一个装饰器。

装饰器函数

def decoratorfactory(message):
    def decorator(func):
        def wrapped_func(*args, **kwargs):
            print(The decorator wants to tell you: {}.format(message))
            return func(*args, **kwargs)
        return wrapped_func
    return decorator

@decoratorfactory(Hello World)
def test():
    pass

test()

装饰师想告诉你:Hello World

重要的提示:

有了这样的工厂装饰你必须调用一对括号的装饰:

@decoratorfactory # Without parentheses
def test():
    pass

test()

TypeError:decorator()missing 1必需的位置参数:‘func‘

装饰类

def decoratorfactory(*decorator_args, **decorator_kwargs):
    
    class Decorator(object):
        def __init__(self, func):
            self.func = func

        def __call__(self, *args, **kwargs):
            print(Inside the decorator with arguments {}.format(decorator_args))
            return self.func(*args, **kwargs)
        
    return Decorator

@decoratorfactory(10)
def test():
    pass

test()

在装饰器里面有参数(10,)

 

使装饰器看起来像装饰函数

装饰器通常会剥离函数元数据,因为它们不一样。当使用元编程动态访问函数元数据时,这可能导致问题。元数据还包括函数的docstrings及其名称。 functools.wraps通过将几个属性复制到包装器函数,使装饰函数看起来像原始函数。

from functools import wraps

包装装饰器的两种方法在隐藏中实现相同的事情,原始函数已经被装饰。没有理由喜欢函数版本到类版本,除非你已经使用一个。

作为功??能

def decorator(func):
    # Copies the docstring, name, annotations and module to the decorator
    @wraps(func)
    def wrapped_func(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapped_func

@decorator
def test():
    pass

test.__name__

‘测试‘

作为一个类

class Decorator(object):
    def __init__(self, func):
        # Copies name, module, annotations and docstring to the instance.
        self._wrapped = wraps(func)(self)
        
    def __call__(self, *args, **kwargs):
        return self._wrapped(*args, **kwargs)

@Decorator
def test():
    """Docstring of test."""
    pass

test.__doc__

“测试的字符串。

 

使用Decorator创建Singleton类

单例是将类的实例化为一个实例/对象的模式。使用装饰器,我们可以通过强制类返回类的现有实例或创建一个新实例(如果它不存在)来将类定义为单例,

def singleton(cls):    
    instance = None
    def wrapper(*args, **kwargs):
        if instance is None:
            instance = cls(*args, **kwargs)
        return instance

    return wrapper

这个装饰器可以添加到任何类声明,并且将确保最多创建一个类的一个实例。任何后续调用都将返回已存在的类实例。

 

@singleton
class SomeSingletonClass:
    x = 2
    def __init__(self):
        print("Created!")

instance = SomeSingletonClass()  # prints: Created!
instance = SomeSingletonClass()  # doesn‘t print anything
print(instance.x)                # 2

instance.x = 3
print(SomeSingletonClass().x)    # 3

因此,无论是通过本地变量引用类实例还是创建另一个“实例”,都始终获得相同的对象。

以上是关于Python-装饰的主要内容,如果未能解决你的问题,请参考以下文章

Python之如何优雅的重试

Thymeleaf 模板 - 有没有办法装饰模板而不是包含模板片段?

python装饰器关键代码

python之装饰器

[TimLinux] Python 装饰器

python装饰器