python装饰器详解

    科技2022-07-31  93

    python装饰器详解

    Python comes with many syntactic artefacts that enable developers to build applications faster and most importantly, with clean code. If you’re a programmer, you know how important code quality and reliability is! So in this article, I will give a brief introduction to python decorators. If you’re interested in a cleaner and efficient coding, have a look at my following article.

    Python带有许多语法伪像,使开发人员可以使用干净的代码更快,最重要的是构建应用程序。 如果您是一名程序员,您就会知道代码质量和可靠性是多么重要! 因此,在本文中,我将简要介绍python装饰器。 如果您对更简洁高效的编码感兴趣,请参阅我的以下文章。

    Before we start using python decorators, we need to understand how Python functions work. Python functions are considered as first-class functions, which means they can be treated as objects and passed around at your will.

    在开始使用python装饰器之前,我们需要了解Python函数的工作方式。 Python函数被认为是一流的函数 ,这意味着它们可以被视为对象并可以随意传递。

    Python can have functions defined within functions, known as inner functions. A function can also be returned from another function (This is one way of implementing a switch operator in Python).

    Python可以在函数内定义函数,称为内部函数 。 一个函数也可以从另一个函数返回(这是在Python中实现switch运算符的一种方式)。

    函数作为OOP对象的应用 (Function’s Applications as an OOP Object)

    切换案例实施 (Switch case implementation)

    Python dictionary is an object construction where an object will be returned to a key that it is referred with. Since Python does not have an explicit switch operator, we use the dict construct to make one. See this example.

    Python字典是一种对象构造,其中对象将返回到引用它的键。 由于Python没有显式的switch运算符,因此我们使用dict构造一个。 请参阅此示例。

    op_switch = { 'sqr': lambda x: x**2, 'sqrt': lambda x: x**0.5, 'abs': lambda x: abs(x)}

    Our switch case is based on a string to pick the operation. The dictionary returns a function. I have used lambda function definitions for code simplicity. They behave similarly to that of functions (not exactly the same!). They can be accessed as follows.

    我们的切换条件基于一个字符串来选择操作 。 字典返回一个函数。 为了简化代码,我使用了lambda函数定义。 它们的行为与功能相似(不完全相同!)。 可以按以下方式访问它们。

    >>> switch['sqr'](12)144>>> switch['sqrt'](25)5.0

    将一个函数传递给另一个函数 (Passing a function to another function)

    Consider a situation where you need to wrap another function. Imagine, the wrapper function can be shared across many other functions. Before I tell you about a real work example, let’s follow this pet scenario.

    考虑一种情况,您需要包装另一个函数。 想象一下,包装器功能可以在许多其他功能之间共享。 在我告诉您一个真实的工作示例之前,让我们关注一下这种宠物场景。

    Imagine you need to have a function that will either execute the function and return an answer. If an exception is thrown, None will be returned.

    假设您需要一个可以执行该函数并返回答案的函数。 如果抛出一个异常, None将被退回。

    def deco_function(func, *args): try: return func(*args) except: print("Error occured") return Nonedef divide(a, b): return a/b

    Our deco_function function will execute the passed function with the set of args passed as *args. I have omitted keyword arguments for simplicity. If we run this, we’ll see the following output for each of the following parameters we give.

    我们的deco_function函数将使用传递为*args的args集来执行传递的函数。 为了简单起见,我省略了关键字参数。 如果运行此命令,我们将看到以下给出的每个参数的输出。

    >>> deco_function(divide, 10, 2)5.0>>> deco_function(divide, 10, 0)Error occured

    Pretty neat right!

    很整洁吧!

    The main problem with this approach is that the function signature for the wrapper function must be well known. We need to pass the function itself and the arguments. This is less maintainable in a complex scenario!

    这种方法的主要问题是包装器函数的函数签名必须是众所周知的。 我们需要传递函数本身和参数。 在复杂的情况下,这不太容易维护!

    Python装饰器简介 (Introducing Python Decorators)

    The same function above can be sugar-coated with much better syntax using python decorators syntax. Let’s see how.

    上面的相同函数可以使用python装饰器语法以更好的语法进行糖衣。 让我们看看如何。

    def deco_function(func): def wrapped(*args): """ This is the wrapper for a function to be fail safe """ try: return func(*args) except: print("Error occured") return None return wrapped@deco_functiondef divide(a, b): """ This is a function to divide two numbers """ return a/b

    In this example, we decorate the divide function with deco_function decorator. Within the decorator, a wrapper is placed around the passed function and returns the wrapper. This is similar to the following statement.

    在此示例中,我们用deco_function装饰器装饰divide功能。 在装饰器内,将包装器放置在传递的函数周围,并返回wrapper 。 这类似于以下声明。

    divide = deco_function(divide)

    However, we now have the liberty to forget about the call_function implementation. That is pretty neat!

    但是,我们现在可以忘记call_function的实现了。 太整齐了!

    现实世界中的装饰器用法! (The real world decorator usage!)

    Flask Website Flask网站

    In case you aren’t familiar with a decorator use case; let’s have a look at the Flask server.

    如果您对装饰器用例不熟悉, 让我们来看看Flask服务器。

    Flask is a server implementation for python. The screenshot displays how routing is implemented in Flask using decorators. We only have to mention the route that the function shall be activated. So far we did not discuss how parameters can be passed on to decorators. We’ll discuss that soon!

    Flask是python的服务器实现。 屏幕截图显示了如何使用装饰器在Flask中实现路由。 我们仅需提及激活该功能的途径。 到目前为止,我们还没有讨论如何将参数传递给装饰器。 我们将尽快讨论!

    正确使用装饰器和函数名称 (Proper use of decorators and function names)

    One thing to remember is, wrapping a function can cause confusions about its identity. This is because the function is no longer itself once we wrap it with something else.

    要记住的一件事是,包装功能可能会导致其身份混乱。 这是因为一旦我们用其他东西包装了它,该函数便不再是自身。

    >>> divide.__name__wrapped>>> print(divide.__doc__)This is the wrapper for a function to be fail safe

    The __name__ attribute of a function returns the function name itself and printing __doc__ returns the docstring. However, we can see that for both of these attributes we get the values from the wrapper function, not from the referred function. This can cause severe confusion in large software. Here’s how to fix it.

    函数的__name__属性返回函数名称本身,而打印__doc__返回docstring。 但是,我们可以看到,对于这两个属性,我们都是从包装函数获得值,而不是从所引用的函数获得值。 这会在大型软件中引起严重的混乱。 解决方法如下。

    import functoolsdef deco_function(func):@functools.wraps(func) def wrapped(*args): """ This is the wrapper for a function to be fail safe """ try: return func(*args) except: print("Error occured") return None return wrapped@deco_functiondef divide(a, b): """ This is a function to divide two numbers """ return a/b

    Note the code in bold. We import functools and decorate our wrapper. This functiontools.wraps decorator injects the docstring and name attributes to the wrapper so that we get the proper attributes when we print __name__ and __doc__.

    注意以粗体显示的代码。 我们导入functools并装饰包装器。 这个functiontools.wraps装饰器将docstring和name属性注入包装器,以便在打印__name__和__doc__时获得正确的属性。

    >>> print(divide.__name__)divide>>> print(divide.__doc__)This is a function to divide two numbers

    了解参数和关键字参数的工作方式 (Understanding how arguments and keyword arguments work)

    Python accepts ordered arguments followed by keyword arguments. This can be demonstrated as follows.

    Python接受有序参数,后跟关键字参数 。 这可以证明如下。

    import functoolsdef print_args(func): @functools.wraps(func) def wrapped(*args, **kwargs): args_arr = [(n, a) for n, a in enumerate(args)] kwargs_arr = [(k, v) for k, v in kwargs.items()]for k, v in args_arr + kwargs_arr: print(k, v) return wrapped@print_argsdef test_function(*args, **kwargs): return a/b

    Calling the above test_function function gives the following result.

    调用上面的test_function函数将得到以下结果。

    >>> test_function('name', 'age', height=150, weight=50)0 name1 ageheight 150weight 50

    Following the above observation, it must be noted that arguments are organized before keyword arguments. The order must be preserved in the arguments and it does not matter in keyword arguments. In a wrapper, both arguments and keyword arguments must be passed into the function being wrapped. This ensures the wider usability of the wrapper.

    根据上述观察,必须注意, 参数是在关键字arguments之前组织的。 顺序必须保留在参数中,而与关键字参数无关。 在包装器中,参数和关键字参数都必须传递到要包装的函数中。 这确保了包装纸的更广泛的可用性。

    带参数的装饰器 (Decorators with Arguments)

    Now that we have a clear understanding of how decorators work, let’s see how we can use decorators with arguments.

    现在我们对装饰器的工作原理有了清晰的了解,让我们看看如何将装饰器与参数一起使用。

    def powered(power): def powered_decorator(func): def wrapper(*args): return func(*args)**power return wrapper return powered_decorator@powered(2)def add(*args): return sum(args)

    In the above example, we have a wrapper with an attribute. Simply, this wrapper asks the answer to be raised into a power designated by the attribute. Few more examples where you might find parameterized decorators are as follows.

    在上面的示例中,我们有一个带有属性的包装器。 简单地,此包装器要求将答案提升为该属性指定的幂。 可能在以下几个示例中找到参数化装饰器的示例如下。

    Validating input fields, JSON strings, file existence, etc.

    验证输入字段,JSON字符串,文件存在等。 Implementing switch cased decorators

    实现开关盒装饰器

    装饰器的高级用例 (Advanced Usecases of Decorators)

    Decorators can be used in a similar manner for classes. However, here we can talk about two ways we can use decorators; within a class, and for a class.

    装饰器可以类似的方式用于类。 但是,在这里我们可以讨论两种使用装饰器的方式: 在一个班级内,并为一个班级。

    一类中的装饰器 (Decorators within a Class)

    In the following example, I use a decorator on functions of the class Calculator. This helps me to gracefully obtain a value for an operation when it fails.

    在下面的示例中,我在Calculator类的函数上使用装饰器。 这有助于我在操作失败时优雅地获取操作的值。

    import functoolsdef try_safe(func): @functools.wraps(func) def wrapped(*args): try: return func(*args) except: print("Error occured") return None return wrappedclass Calculator: def __init__(self): pass @try_safe def add(self, *args): return sum(args) @try_safe def divide(self, a, b): return a/b

    The above code can be used as follows.

    上面的代码可以按如下方式使用。

    >>> calc = Calculator()>>> calc.divide(10, 2)5.0

    一类的装饰器 (Decorators for a Class)

    Using a decorator for a class will activate the decorator during the instantiation of the function. For example, the following code will check for graceful creation of the object with the constructor parameters. Should the operation fail, None will be returned in place of the object from Calculator class.

    为类使用装饰器将在函数实例化期间激活装饰器。 例如,以下代码将使用构造函数参数检查对象的正常创建。 如果操作失败,将返回None代替Calculator类中的对象。

    import functoolsdef try_safe(cls): @functools.wraps(cls) def wrapped(*args): try: return cls(*args) except: print("Error occured") return None return wrapped@try_safeclass Calculator: def __init__(self, a, b): self.ratio = a/b

    使用装饰器为功能注入状态 (Injecting State for a Function using Decorators)

    During the process of wrapping a function, a state could be injected into the function. Let’s see the following example.

    在包装功能的过程中,可以将状态注入到功能中。 让我们看下面的例子。

    import functoolsdef record(func): @functools.wraps(func) def wrapped(*args): wrapped.record += 1 print(f"Ran for {wrapped.record} time(s)") return func(*args) wrapped.record = 0 return wrapped@recorddef test(): print("Running")

    Running the above example gives us the following output.

    运行上面的示例将为我们提供以下输出。

    >>> test()Ran for 1 time(s)Running>>> test()Ran for 2 time(s)Running>>> test()Ran for 3 time(s)Running

    This is useful in creating a singleton using decorators. Let’s see that next.

    这在使用装饰器创建单例时很有用。 接下来,让我们看看。

    使用Python装饰器的Singleton (Singleton using Python Decorators)

    Singleton refers to an instance that is shared between calls, and would not duplicate for any reason. In simple terms, at first, an instance is created. In the following calls to make an instance, the existing instance will be returned.

    单例指的是在调用之间共享的实例,并且由于任何原因不会重复。 简单来说,首先创建一个实例。 在以下创建实例的调用中,将返回现有实例。

    Let’s see how can we implement a singleton using decorators.

    让我们看看如何使用装饰器实现单例。

    import functoolsdef singleton(cls): @functools.wraps(cls) def wrapped(*args, **kwargs): if not wrapped.object: wrapped.object = cls(*args, **kwargs) return wrapped.object wrapped.object = None return wrapped@singletonclass SingularObject: def __init__(self): print("The object is being created")

    We can confirm the functionality as follows.

    我们可以确认功能如下。

    >>> first = SingularObject()The object is being created>>> second = SingularObject()>>> second is firstTrue

    The objects refer to the same instance. Hence, we are guaranteed that no more objects are created. Thus a singleton!

    这些对象引用相同的实例。 因此,我们保证不再创建任何对象。 这样一个单身!

    制作包装器/装饰器类 (Making a Wrapper/Decorator Class)

    So far we only considered functions as wrappers or decorators. However, in an OOP program, classes may be preferred. We can make this happen with just a few modifications to our record example. Let’s see the code.

    到目前为止,我们仅将函数视为包装器或装饰器。 但是,在OOP程序中,类可能是首选。 我们可以通过对记录示例进行一些修改来实现此目的。 让我们看一下代码。

    import functoolsclass Record: def __init__(self, func):functools.update_wrapper(self, func) self.func = func self.record = 0def __call__(self, *args, **kwargs): self.record += 1 print(f"Ran for {self.record} time(s)") return self.func(*args, **kwargs)@Recorddef test(): print("Run")

    Note the sections in bold. We are using functools to update the function attributes. We use __call__ overload to define the action on a function call. The constructor __init__ initiates the variables similar to that we did after the wrapper function in the previous stateful decorator example. The outputs for the above example are as follows.

    注意以粗体显示的部分。 我们正在使用functools更新功能属性。 我们使用__call__重载来定义函数调用的操作。 构造函数__init__初始化变量,类似于在之前的有状态装饰器示例中包装函数之后所做的。 上面示例的输出如下。

    >>> test()Ran for 1 time(s)Run>>> test()Ran for 2 time(s)Run>>> test()Ran for 2 time(s)Run

    结束语 (Concluding Remarks)

    In this article, I showed how decorators can be used to implement wrappers in a much simpler and clean way. Following are a few examples you might use wrappers in your next project.

    在本文中,我展示了如何使用装饰器以一种更加简单和干净的方式实现包装器。 以下是一些示例,您可以在下一个项目中使用包装器。

    Implement a routing mechanism, for example, in AWS lambda functions

    在AWS Lambda函数中实施路由机制 API caching mechanism, where you might want to keep track of the last few function calls

    API缓存机制,您可能希望跟踪最近的几个函数调用 Debounce in an IoT application (restricting function calls that happen within a very short period of time)

    IoT应用程序中的反跳(限制在非常短的时间内发生的函数调用)

    Let me know if you could think of any other creative applications that you may have used decorators. I’d love to hear them.

    让我知道您是否可以想到使用装饰器的其他任何创意应用程序。 我很想听听他们的声音。

    I hope you enjoyed this article. Cheers!

    希望您喜欢这篇文章。 干杯!

    翻译自: https://levelup.gitconnected.com/python-decorators-b530bff0f3e3

    python装饰器详解

    Processed: 0.011, SQL: 8