拓展:Python类中的装饰器

举报
乂氼S 发表于 2023/05/28 13:35:23 2023/05/28
【摘要】 拓展:Python类中的装饰器
  • Python中的装饰器是一种用来动态修改函数或类的行为的特殊函数,它可以接受一个函数或类作为输入,返回一个新的函数或类。 ``装饰器本质上是一个高阶函数,它可以将一个函数作为参数传递给另一个函数,并返回一个新的函数。装饰器通常被用来扩展或修改函数的功能,而无需修改函数的源代码。装饰器的语法比较简单,可以使用@符号来应用装饰器。例如:
def my_function():
    pass```
    

上面的代码中,@decorator就是一个装饰器,它将my_function函数作为参数,并返回一个新的函数,用来代替原始的my_function函数。
下面是一个简单的装饰器的示例,它可以计算函数的运行时间:
import time
def time_it(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time} seconds to run.")
        return result
    return wrapper
@time_it
def my_function():
    time.sleep(2)
my_function()```

上面的代码中,time_it是一个装饰器函数,它接受一个函数作为参数,并返回一个新的函数wrapper。wrapper函数会在运行原始函数之前和之后计算时间,并将结果打印出来。
使用@time_it装饰器来装饰my_function函数,就可以自动计算my_function函数的运行时间。当调用my_function函数时,它会输出以下信息:

Function my_function took 2.000375986099243 seconds to run.

装饰器可以帮助我们在不修改原始函数代码的情况下,对函数的行为进行修改。常见的装饰器用途包括函数的缓存、身份验证、日志记录、性能分析等等。
在装饰器中,return wrapper的作用是将装饰器函数返回一个新的函数,这个新的函数就是经过装饰后的原始函数。新的函数可以直接调用,它将会执行被装饰的原始函数,并在执行前后添加了额外的功能。
在上面的示例中,time_it装饰器函数返回的wrapper函数就是经过装饰后的my_function函数。当我们调用my_function函数时,实际上执行的是wrapper函数,而不是原始的my_function函数。
wrapper函数定义了一个内部函数,它使用了func参数来调用原始的函数,并添加了计时器的逻辑。最后,wrapper函数返回原始函数的返回值。因此,wrapper函数实现了在调用原始函数之前和之后添加额外功能的目的。
当我们在装饰器中使用return wrapper时,装饰器函数会返回wrapper函数对象,然后将其绑定到被装饰函数的名称上。这意味着,当我们调用被装饰的函数时,实际上会执行经过装饰器修饰的函数对象。
Python类中的装饰器和函数装饰器类似,也是用来动态修改类的行为的特殊函数。它们可以被用来扩展或修改类的功能,而无需修改类的源代码。
类装饰器的基本语法与函数装饰器相似,只不过它们接受的是一个类而不是一个函数作为参数。下面是一个简单的类装饰器的示例,它可以添加一个名为bar的属性到被装饰的类中:

def add_bar(cls):
    cls.bar = "bar"
    return cls
@add_bar
class Foo:
    pass
print(Foo.bar)  # 输出: "bar"

上面的代码中,add_bar是一个类装饰器函数,它接受一个类作为参数,并向该类添加一个名为bar的属性。使用@add_bar装饰器将add_bar装饰在Foo类上后,我们就可以在Foo类中访问bar属性了。
类装饰器与函数装饰器一样,可以用来修改类的行为。例如,我们可以使用类装饰器来实现单例模式:

def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance
@singleton
class MyClass:
    pass
a = MyClass()
b = MyClass()
print(a is b)  # 输出: True

上面的代码中,singleton是一个类装饰器函数,它使用一个字典来保存实例,并在需要时返回已有的实例。使用@singleton装饰器将singleton装饰在MyClass类上后,我们就可以创建单例对象了。
类装饰器还可以用来实现类的自动注册和扩展等功能。例如,我们可以定义一个类注册表,每次定义新类时将其自动注册到类注册表中:

class ClassRegistry:
    registry = {}
    def __init__(self, name):
        self.name = name
    def __call__(self, cls):
        self.registry[self.name] = cls
        return cls
@ClassRegistry("foo")
class Foo:
    pass
@ClassRegistry("bar")
class Bar:
    pass
print(ClassRegistry.registry)  # 输出: {"foo": <__main__.Foo object at 0x7f63a85ed490>, "bar": <__main__.Bar object at 0x7f63a85ed4c0>}

上面的代码中,ClassRegistry是一个类装饰器函数,它接受一个名称作为参数,并将被装饰的类注册到一个类注册表中。使用@ClassRegistry("foo")装饰器将ClassRegistry装饰在Foo类上后,我们就可以将Foo类注册到类注册表中,并使用ClassRegistry.registry访问所有注册的
类装饰器是一种特殊的函数,可以接受一个类作为参数,并在运行时动态地修改该类的行为,而无需修改类的源代码。这使得我们可以在不改变原始类定义的情况下,向类添加新的属性、方法或行为。
类装饰器可以用于许多场景,比如:

  1. 扩展类的功能:我们可以使用类装饰器来添加新的属性、方法或修改现有属性和方法的行为。
  2. 类的自动注册:我们可以使用类装饰器来实现自动将类注册到某个集合中,例如类注册表、插件列表等。
  3. 类的元编程:我们可以使用类装饰器来修改类的元信息,例如类名、文档字符串等。
  4. 类的单例模式:我们可以使用类装饰器来实现单例模式,即每次创建对象时都返回同一个实例。
    下面是一个例子,它展示了如何使用类装饰器来添加新的属性和方法:
def add_property(cls):
    cls.new_property = "new_property"
    return cls
def add_method(cls):
    def new_method(self):
        print("This is a new method.")
    cls.new_method = new_method
    return cls
@add_property
@add_method
class MyClass:
    pass
my_obj = MyClass()
print(my_obj.new_property) # 输出: "new_property"
my_obj.new_method() # 输出: "This is a new method."

上面的代码中,我们定义了两个类装饰器函数,分别用于添加新的属性和方法。我们使用这两个装饰器将它们装饰在MyClass类上,然后创建一个MyClass对象并调用新的属性和方法。
总之,类装饰器是一种强大的工具,可以帮助我们在不改变原始类定义的情况下,扩展或修改类的行为。它们使代码更加灵活、可扩展,并且可以帮助我们更好地应对不同的编程场景。
在使用类装饰器实现单例模式时,如果我们直接返回cls(),那么每次调用类都会创建一个新的对象实例。为了实现单例模式,我们需要确保只有一个对象实例,并在需要时返回该实例。
因此,我们可以将get_instance函数返回的是一个闭包函数,而不是直接返回cls(),闭包函数可以保存一个对实例的引用,并在需要时返回该实例。这里的get_instance函数就是用来返回这个闭包函数的。
而这个闭包函数则是在被调用时才返回类的实例,所以get_instance函数返回的不是一个类的实例对象,而是一个函数对象,因此后面不需要加括号。
例如,下面是一个简单的示例,展示了如何使用类装饰器实现单例模式:

def singleton(cls):
    instances = {}
    def get_instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return get_instance
@singleton
class MyClass:
    pass
a = MyClass()
b = MyClass()
print(a is b) # 输出: True

在这个示例中,singleton是一个类装饰器,它将被装饰的类变为单例模式。get_instance函数返回一个闭包函数,这个闭包函数保留了对MyClass实例的引用,以确保只有一个实例被创建。我们将singleton装饰在MyClass类上,并创建两个MyClass对象,然后判断它们是否相等,结果是True,表明它们是同一个对象。
在 Python 中,当我们使用装饰器来装饰一个类时,实际上是将这个类作为参数传递给装饰器函数,并将返回值重新绑定到这个类名上。这意味着,创建一个类的实例时,实际上是调用被装饰的类(而不是原始定义的类),因此,被装饰的类的行为可能已经被修改了。
例如,在下面的代码中,我们定义了一个装饰器函数my_decorator,它在被装饰的类中添加了一个新的属性,并重写了一个原有的方法:

def my_decorator(cls):
    cls.new_attribute = "new_attribute"
    def new_method(self):
        print("This is a new method.")
    cls.old_method = new_method
    return cls
@my_decorator
class MyClass:
    def old_method(self):
        print("This is the old method.")

在这个例子中,我们使用@my_decoratorMyClass类装饰,my_decorator函数会向该类添加一个新的属性new_attribute和一个新的方法old_method。在定义MyClass类时,我们已经定义了一个名为old_method的方法,但是在装饰器中,我们又将其重写了。因此,当我们创建MyClass实例并调用old_method方法时,实际上是调用装饰器中定义的new_method方法,而不是原始定义的old_method方法。
例如,以下代码创建了一个MyClass对象,并调用了old_method方法:

my_object = MyClass()
my_object.old_method() # 输出 "This is a new method."

这里,my_object实际上是调用被装饰的MyClass类创建的,因此调用old_method方法实际上是调用装饰器中定义的new_method方法。
总之,装饰器函数的目的是动态地修改类的行为。当一个类被装饰器装饰时,在创建类的实例时,实际上是调用被装饰的类,而不是原始定义的类,因此实例化后的类行为可能已经被修改了。
在 Python 中,函数名可以被当作一个对象来使用,就像其他对象一样。当我们将函数名作为返回值时,实际上是返回一个可调用的函数对象。
在装饰器函数中,通常会定义一个新的函数,并将其返回,返回的函数通常用于包装原始函数,并添加一些额外的功能。当我们在装饰器函数中使用return语句时,我们通常返回一个函数名,表示我们要返回一个可调用的函数对象,而不是调用该函数并返回其结果。因此,在return语句后不需要加上括号,因为我们不是在调用该函数,而是返回该函数名作为一个对象。
例如,考虑下面的代码,它定义了一个装饰器函数my_decorator,该函数返回一个包装了原始函数的新函数:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before the function is called.")
        result = func(*args, **kwargs)
        print("After the function is called.")
        return result
    return wrapper
@my_decorator
def my_function(x, y):
    return x + y

在这个例子中,my_decorator函数返回一个名为wrapper的函数对象。这个函数包装了原始函数func,并在它的前后打印一些额外的消息。@my_decorator语法实际上等价于执行my_function = my_decorator(my_function),这会将my_function重新绑定到装饰器函数返回的函数对象上。
当我们调用my_function时,实际上是调用wrapper函数。因此,调用my_function(1, 2)会输出以下内容:

Before the function is called.
After the function is called.

然后返回结果3。注意,在my_decorator函数中,我们没有调用wrapper函数,而是将函数名作为一个对象返回。当我们在使用@my_decorator语法时,实际上是将原始函数重新绑定到wrapper函数上,而不是将wrapper函数调用并返回其结果。
总之,返回一个函数名是为了在装饰器函数中定义并返回一个新的函数对象,该函数对象可以用于包装原始函数,并添加一些额外的功能。在返回函数名时,不需要加上括号,因为我们只是返回一个可调用的函数对象,而不是调用该函数并返回其结果。
在 Python 中,函数名可以被当作一个对象来使用,就像其他对象一样。当我们将函数名作为一个对象来使用时,实际上是返回了一个可调用的函数对象。
返回一个函数对象与调用函数是有很大区别的。当我们调用函数时,Python 解释器会在内存中为该函数创建一个新的栈帧,并将函数的参数和局部变量存储在栈帧中。然后,Python 解释器会执行函数体中的代码,直到遇到return语句或函数体的结尾为止。最后,Python 解释器会将函数的返回值从栈帧中弹出,并将栈帧销毁。这个过程称为函数调用。
而当我们返回一个函数对象时,我们实际上只是将函数名作为一个对象返回,并没有执行函数体中的代码。返回的函数对象可以像其他对象一样被存储、传递和调用,但只有在我们显式地调用这个函数对象时,它才会执行其函数体中的代码,并返回相应的结果。因此,返回一个函数对象并不会执行函数体中的代码,只有在我们调用该函数对象时才会执行。
考虑以下示例代码:

def my_func(x):
    return x * 2
def my_decorator(func):
    print("Inside my_decorator")
    return func
decorated_func = my_decorator(my_func)
result = decorated_func(3)

在这个例子中,my_decorator函数接受一个函数作为参数,并返回该函数本身。因此,当我们将my_func函数作为参数传递给my_decorator函数时,它会返回my_func本身。然后,我们将返回的函数对象保存在变量decorated_func中,并调用它来计算结果。
在这个过程中,my_decorator函数并没有执行my_func函数体中的代码,因为它只是简单地返回了my_func函数本身。只有当我们调用decorated_func函数时,它才会执行my_func函数体中的代码,并返回结果。
因此,返回一个函数对象与调用函数是有很大区别的。返回函数对象只是将函数名作为一个对象返回,而没有执行函数体中的代码。只有在我们显式地调用函数对象时,它才会执行其函数体中的代码,并返回相应的结果。
是的,在 Python 中,函数名可以被当作一个对象来使用,就像其他对象一样。当我们使用一个函数名时,如果不加括号,它将返回一个函数对象;如果加上括号,它将执行函数体中的代码,并返回相应的结果。
例如,考虑以下示例代码:

def my_func(x):
    return x * 2
func_obj = my_func  # 不加括号,指向函数对象
result = func_obj(3)  # 加括号,调用函数并计算结果
print(result)  # 输出:6

在这个例子中,我们首先定义了一个函数my_func,它将其参数x乘以2并返回结果。然后,我们将函数名my_func赋值给变量func_obj,这样func_obj就指向了my_func函数对象。
接下来,我们调用func_obj函数对象,将参数3传递给它,并将结果保存在变量result中。由于我们使用了括号,Python 解释器会执行my_func函数体中的代码,并返回计算结果6。最后,我们将result的值打印到控制台上。
因此,当我们使用一个函数名时,不加括号它指向函数对象,加上括号它调用函数并计算结果。
闭包函数是指在另一个函数内部定义的函数,这个内部函数可以访问到外部函数的变量,并且能够在外部函数调用结束之后,仍然保持对这些变量的访问。因此,闭包函数具有“记忆”效应,可以记录下其所在的外部函数的状态。
在 Python 中,闭包函数通常会使用嵌套函数的形式来实现。例如,以下代码定义了一个简单的闭包函数:
python def outer_func(x): def inner_func(y): return x + y return inner_func
在这个例子中,outer_func函数定义了一个内部函数inner_func,并返回了该函数的引用。inner_func函数使用了x变量,这个变量是在outer_func函数中定义的,因此inner_func可以访问到outer_func函数的变量。
接下来,我们可以使用outer_func函数来创建一个闭包函数。例如,以下代码创建了一个名为add_five的闭包函数:

add_five = outer_func(5)

这里,我们调用outer_func(5)并将其结果赋值给add_five变量。由于outer_func返回了inner_func函数的引用,因此add_five现在指向inner_func函数对象。
我们可以使用add_five闭包函数来计算一个数字加上5的结果,例如:

result = add_five(3)
print(result)  # 输出:8

在这个例子中,我们调用add_five闭包函数并将参数3传递给它。由于inner_func函数定义了x变量,因此add_five闭包函数可以使用这个变量来计算3 + 5的结果,并返回8。由于x变量的值在outer_func函数调用结束后仍然被保持,因此add_five闭包函数可以多次使用,每次计算出与5相加的结果。
因此,闭包函数是一种强大的工具,可以用于实现许多有趣的功能。它们可以帮助我们编写更加灵活和复杂的代码,同时保持代码的简洁性和可读性。`

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。