Fassen Sie die Wissenspunkte von Dekorateuren in Python zusammen

WBOY
Freigeben: 2022-06-17 13:50:40
nach vorne
2373 Leute haben es durchsucht

Dieser Artikel bringt Ihnen relevantes Wissen über Python, in dem hauptsächlich Probleme im Zusammenhang mit Dekoratoren vorgestellt werden, einschließlich Schließungen, Dekoratoren, Verwendung mehrerer Dekoratoren, Dekoratoren mit Parametern usw. Schauen wir uns das gemeinsam an. Ich hoffe, es wird hilfreich sein an alle.

Fassen Sie die Wissenspunkte von Dekorateuren in Python zusammen

Empfohlenes Lernen: Python-Video-Tutorial

1. Abschluss

Um zu verstehen, was ein Decorator (Dekorateur) ist, müssen wir zunächst das Konzept von Closure (Abschluss) kennen.

Abschluss, auch Abschlussfunktion oder geschlossene Funktion genannt: Wenn eine Funktion als Objekt zurückgegeben wird und auch externe Variablen mitnimmt, bildet sie einen Abschluss.

Nehmen Sie als Beispiel das Drucken von Hello World. Schauen wir uns zunächst an, wie die Struktur der verschachtelten Funktion aussehen sollte:

def print_msg(msg):

    def printer():
        print(msg)

    printer()print_msg('Hello World')# Hello World
Nach dem Login kopieren

Das Ausführen von print_msg('Hello World') entspricht dem Ausführen von printer(), d. h. print(msg) wird ausgeführt, sodass Hello World ausgegeben wird. print_msg('Hello World') 相当于执行了 printer(),也就是执行 print(msg),所以将输出 Hello World

我们再来看一下如果是闭包,该是什么样的结构:

def print_msg(msg):

    def printer():
        print(msg)

    return printer


my_msg = print_msg('Hello World')my_msg()# Hello World
Nach dem Login kopieren

本例中的 printer 函数就是闭包。

执行 print_msg('Hello World') 实际上是返回了如下这样一个函数,它夹带了外部变量 'Hello World'

def printer():
    print('Hello World')
Nach dem Login kopieren

于是调用 my_msg 就相当于执行 printer()


那么如何判断一个函数是否是闭包函数呢?闭包函数的 __closure__ 属性里面定义了一个元组用于存放所有的cell对象,每个cell对象保存了这个闭包中所有的外部变量。而普通函数的 __closure__ 属性为 None

def outer(content):

    def inner():
        print(content)

    return innerprint(outer.__closure__)
    # Noneinner = outer('Hello World')print(inner.__closure__)
    # (<cell at 0x0000023FB1FD0B80: str object at 0x0000023FB1DC84F0>,)
Nach dem Login kopieren

由此可见 outer 函数不是闭包,而 inner 函数是闭包。

我们还可以查看闭包所携带的外部变量:

print(inner.__closure__[0].cell_contents)# Hello World
Nach dem Login kopieren

说了那么多,那么闭包究竟有什么用呢?闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,那么就和普通的函数没有任何区别。

闭包的优点如下:

  • 局部变量无法共享和长久的保存,而全局变量可能造成变量污染,闭包既可以长久的保存变量又不会造成全局污染。
  • 闭包使得函数内局部变量的值始终保持在内存中,不会在外部函数调用后被自动清除。

二、装饰器

我们先考虑这样一个场景,假设先前编写的一个函数已经实现了4个功能,为简便起见,我们用 print 语句来代表每一个具体的功能:

def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')
Nach dem Login kopieren

现在,由于某种原因,你需要为 module 这个函数新增一个 功能5,你完全可以这样修改:

def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')
    print('功能5')
Nach dem Login kopieren

但在现实业务中,直接做出这样的修改往往是比较危险的(会变得不易于维护)。那么如何在不修改原函数的基础上去为它新添一个功能呢?

你可能已经想到了使用之前的闭包知识:

def func_5(original_module):

    def wrapper():
        original_module()
        print('功能5')

    return wrapper
Nach dem Login kopieren

func_5 代表该函数主要用于实现 功能5,我们接下来将 module 传入进去来观察效果:

new_module = func_5(module)new_module()# 功能1# 功能2# 功能3# 功能4# 功能5
Nach dem Login kopieren

可以看出,我们的新模块:new_module 已经实现了 功能5

在上面的例子中,函数 func_5 就是一个装饰器,它装饰了原来的模块(为它新添了一个功能)。

当然,Python有更简洁的写法(称之为语法糖),我们可以将@符号与装饰器函数的名称一起使用,并将其放置在要装饰的函数的定义上方:

def func_5(original_module):

    def wrapper():
        original_module()
        print('功能5')

    return wrapper@func_5def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5
Nach dem Login kopieren

基于此,我们可以在不修改原函数的基础上完成计时任务(计算原函数的运行时间),如下:

def timer(func):

    def wrapper():
        import time
        tic = time.time()
        func()
        toc = time.time()
        print('程序用时: {}s'.format(toc - tic))

    return wrapper@timerdef make_list():
    return [i * i for i in range(10**7)]my_list = make_list()# 程序用时: 0.8369960784912109s
Nach dem Login kopieren

事实上,my_list 并不是列表,直接打印会显示 None,这是因为我们的 wrapper 函数没有设置返回值。如果需要获得 make_list 的返回值,可以这样修改 wrapper 函数:

def wrapper():
    import time
    tic = time.time()
    a = func()
    toc = time.time()
    print('程序用时: {}s'.format(toc - tic))
    return a
Nach dem Login kopieren

三、使用多个装饰器

假如我们要为 module 新添 功能5功能6

Schauen wir uns an, wie die Struktur aussehen würde, wenn es ein Abschluss wäre:

def func_5(original_module):
    def wrapper():
        original_module()
        print('功能5')
    return wrapperdef func_6(original_module):
    def wrapper():
        original_module()
        print('功能6')
    return wrapper@func_6@func_5def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5# 功能6
Nach dem Login kopieren
Die Funktion printer in diesem Beispiel ist ein Abschluss.

Das Ausführen von print_msg('Hello World') gibt tatsächlich eine Funktion wie die folgende zurück, die die externe Variable 'Hello World' mitnimmt:

def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')new_module = func_6(func_5(module))new_module()
Nach dem Login kopieren
So Der Aufruf von my_msg entspricht der Ausführung von printer().
Wie kann man also feststellen, ob eine Funktion eine Abschlussfunktion ist? Das Attribut __closure__ der Abschlussfunktion definiert ein Tupel zum Speichern aller Zellobjekte. Jedes Zellobjekt speichert alle externen Variablen im Abschluss. Das Attribut __closure__ einer normalen Funktion ist None.

@func_5@func_6def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能6# 功能5
Nach dem Login kopieren

Man sieht, dass die Funktion outer kein Abschluss ist, die Funktion inner jedoch ein Abschluss.

Wir können auch die externen Variablen anzeigen, die vom Verschluss getragen werden:

def pide(a, b):
    return a / b
Nach dem Login kopieren
Nach dem Login kopieren

Nachdem ich so viel gesagt habe: Was nützen Verschlüsse? Die Existenz einer Schließung bedeutet, dass sie externe Variablen (private Güter) trägt. Wenn sie keine privaten Güter trägt, unterscheidet sie sich nicht von einer gewöhnlichen Funktion. 🎜🎜Die Vorteile von Schließungen sind wie folgt: 🎜
  • Lokale Variablen können nicht über einen längeren Zeitraum gemeinsam genutzt und gespeichert werden, während globale Variablen eine variable Verschmutzung verursachen können. Schließungen können Variablen für eine lange Zeit speichern, ohne eine globale Verschmutzung zu verursachen.
  • Durch das Schließen bleibt der Wert lokaler Variablen innerhalb der Funktion immer im Speicher und wird nach dem Aufruf der externen Funktion nicht automatisch gelöscht.
🎜 2. Dekorator 🎜🎜 Betrachten wir zunächst ein Szenario, in dem eine zuvor geschriebene Funktion 4 Funktionen implementiert hat. Der Einfachheit halber verwenden wir die print-Anweisung, um jede spezifische Funktion darzustellen: 🎜
def smart_pide(func):
    def wrapper(a, b):
        if b == 0:
            return '被除数不能为0!'
        else:
            return func(a, b)
    return wrapper
Nach dem Login kopieren
Nach dem Login kopieren
🎜Jetzt müssen Sie aus irgendeinem Grund eine Funktion 5 zur Funktion module hinzufügen. Sie können es wie folgt ändern: 🎜
@smart_pidedef pide(a, b):
    return a / bprint(pide(3, 0))# 被除数不能为0!print(pide(3, 1))# 3.0
Nach dem Login kopieren
Nach dem Login kopieren
🎜Aber im echten Geschäft, Es ist oft gefährlich, solche Änderungen direkt vorzunehmen (die Wartung wird schwierig). Wie kann man also eine neue Funktion hinzufügen, ohne die ursprüngliche Funktion zu ändern? 🎜🎜🎜Vielleicht haben Sie daran gedacht, das vorherige Abschlusswissen zu verwenden: 🎜
def decorator(func):
    def wrapper(*args, **kwargs):
        # ...
        res = func(*args, **kwargs)
        # ...
        return res  # 也可以不return
    return wrapper
Nach dem Login kopieren
Nach dem Login kopieren
🎜func_5 bedeutet, dass diese Funktion hauptsächlich zur Implementierung von Funktion 5 verwendet wird, wir werden als nächstes verwenden Übergeben Sie das Modul, um den Effekt zu beobachten: 🎜
def func_5_with_name(name=None):
    def func_5(original_module):
        def wrapper():
            original_module()
            print('功能5由{}实现'.format(name))
        return wrapper    return func_5
Nach dem Login kopieren
Nach dem Login kopieren
🎜Es ist ersichtlich, dass unser neues Modul: new_module Funktion 5 implementiert hat. 🎜
🎜Im obigen Beispiel ist die Funktion func_5 ein Dekorator, der das Originalmodul dekoriert (eine neue Funktion hinzufügt). 🎜
🎜Natürlich gibt es in Python eine prägnantere Schreibweise (syntaktischer Zucker genannt). Wir können das @-Symbol mit dem Namen der Dekoratorfunktion verwenden und es über der Definition der zu dekorierenden Funktion platzieren: 🎜
@func_5_with_name(name='若水')def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
Nach dem Login kopieren
Nach dem Login kopieren

🎜Auf dieser Grundlage können wir die Timing-Aufgabe (die Laufzeit der ursprünglichen Funktion berechnen) wie folgt abschließen, ohne die ursprüngliche Funktion zu ändern: 🎜
class Timer:

    def __init__(self, func):
        self.func = func    def __call__(self):
        import time
        tic = time.time()
        self.func()
        toc = time.time()
        print('用时: {}s'.format(toc - tic))@Timerdef make_list():
    return [i**2 for i in range(10**7)]make_list()# 用时: 2.928966999053955s
Nach dem Login kopieren
Nach dem Login kopieren
🎜Tatsächlich my_list Wenn es sich nicht um eine Liste handelt, wird beim direkten Drucken None angezeigt. Dies liegt daran, dass unsere wrapper-Funktion keinen Rückgabewert festlegt. Wenn Sie den Rückgabewert von make_list benötigen, können Sie die wrapper-Funktion wie folgt ändern: 🎜
class Timer:

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

        import time
        tic = time.time()
        res = self.func(num)
        toc = time.time()
        print('用时: {}s'.format(toc - tic))

        return res@Timerdef make_list(num):
    return [i**2 for i in range(num)]my_list = make_list(10**7)# 用时: 2.8219943046569824sprint(len(my_list))# 10000000
Nach dem Login kopieren
Nach dem Login kopieren
🎜3 Verwenden Sie mehrere Dekoratoren🎜🎜Wenn wir module code> hat Function 5 und Function 6 (in numerischer Reihenfolge) hinzugefügt. Was soll ich also tun? 🎜🎜Glücklicherweise erlaubt Python die gleichzeitige Verwendung mehrerer Dekoratoren: 🎜
class Func_5:

    def __init__(self, name=None):
        self.name = name    def __call__(self, func):

        def wrapper():
            func()
            print('功能5由{}实现'.format(self.name))

        return wrapper@Func_5('若水')def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
Nach dem Login kopieren
Nach dem Login kopieren
🎜Der obige Prozess entspricht tatsächlich: 🎜
class A:

    num = 100

    def func1(self):
        print('功能1')

    @classmethod
    def func2(cls):
        print('功能2')
        print(cls.num)
        cls().func1()A.func2()# 功能2# 100# 功能1
Nach dem Login kopieren
Nach dem Login kopieren
🎜Darüber hinaus ist zu beachten, dass bei Verwendung mehrerer Dekoratoren 🎜der Dekorator der Funktion am nächsten kommt Definition Die Funktion wird zuerst dekoriert. Wenn wir die Dekorationsreihenfolge ändern, ändert sich auch das Ausgabeergebnis: 4. Die dekorierte Funktion hat Parameter. Wie wird die Dekoration erstellt? Gerät? 🎜🎜Stellen Sie sich eine Funktion wie diese vor: 🎜
def pide(a, b):
    return a / b
Nach dem Login kopieren
Nach dem Login kopieren

b=0 时会出现 ZeropisionError。如何在避免修改该函数的基础上给出一个更加人性化的提醒呢?

因为我们的 pide 函数接收两个参数,所以我们的 wrapper 函数也应当接收两个参数:

def smart_pide(func):
    def wrapper(a, b):
        if b == 0:
            return '被除数不能为0!'
        else:
            return func(a, b)
    return wrapper
Nach dem Login kopieren
Nach dem Login kopieren

使用该装饰器进行装饰:

@smart_pidedef pide(a, b):
    return a / bprint(pide(3, 0))# 被除数不能为0!print(pide(3, 1))# 3.0
Nach dem Login kopieren
Nach dem Login kopieren

如果不知道要被装饰的函数有多少个参数,我们可以使用下面更为通用的模板:

def decorator(func):
    def wrapper(*args, **kwargs):
        # ...
        res = func(*args, **kwargs)
        # ...
        return res  # 也可以不return
    return wrapper
Nach dem Login kopieren
Nach dem Login kopieren

五、带参数的装饰器

我们之前提到的装饰器都没有带参数,即语法糖 @decorator 中没有参数,那么该如何写一个带参数的装饰器呢?

前面实现的装饰器都是两层嵌套函数,而带参数的装饰器是一个三层嵌套函数。

考虑这样一个场景。假如我们在为 module 添加新功能时,希望能够加上实现该功能的开发人员的花名,则可以这样构造装饰器(以 功能5 为例):

def func_5_with_name(name=None):
    def func_5(original_module):
        def wrapper():
            original_module()
            print('功能5由{}实现'.format(name))
        return wrapper    return func_5
Nach dem Login kopieren
Nach dem Login kopieren

效果如下:

@func_5_with_name(name='若水')def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
Nach dem Login kopieren
Nach dem Login kopieren

对于这种三层嵌套函数,我们可以这样理解:当为 func_5_with_name 指定了参数后,func_5_with_name(name='若水') 实际上返回了一个 decorator,于是 @func_5_with_name(name='若水') 就相当于 @decorator

六、使用类作为装饰器

将类作为装饰器,我们需要实现 __init__ 方法和 __call__ 方法。

以计时器为例,具体实现如下:

class Timer:

    def __init__(self, func):
        self.func = func    def __call__(self):
        import time
        tic = time.time()
        self.func()
        toc = time.time()
        print('用时: {}s'.format(toc - tic))@Timerdef make_list():
    return [i**2 for i in range(10**7)]make_list()# 用时: 2.928966999053955s
Nach dem Login kopieren
Nach dem Login kopieren

如果想要自定义生成列表的长度并获得列表(即被装饰的函数带有参数情形),我们就需要在 __call__ 方法中传入相应的参数,具体如下:

class Timer:

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

        import time
        tic = time.time()
        res = self.func(num)
        toc = time.time()
        print('用时: {}s'.format(toc - tic))

        return res@Timerdef make_list(num):
    return [i**2 for i in range(num)]my_list = make_list(10**7)# 用时: 2.8219943046569824sprint(len(my_list))# 10000000
Nach dem Login kopieren
Nach dem Login kopieren

如果要构建带参数的类装饰器,则不能把 func 传入 __init__ 中,而是传入到 __call__ 中,同时 __init__ 用来初始化类装饰器的参数。

接下来我们使用类装饰器来复现第五章节中的效果:

class Func_5:

    def __init__(self, name=None):
        self.name = name    def __call__(self, func):

        def wrapper():
            func()
            print('功能5由{}实现'.format(self.name))

        return wrapper@Func_5('若水')def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
Nach dem Login kopieren
Nach dem Login kopieren

七、内置装饰器

Python中有许多内置装饰器,这里仅介绍最常见的三种:@classmethod@staticmethod@property

7.1 @classmethod

@classmethod 用于装饰类中的函数,使用它装饰的函数不需要进行实例化也可调用。需要注意的是,被装饰的函数不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,它可以来调用类的属性,类的方法,实例化对象等。

cls 代表类本身,self 代表实例本身。

具体请看下例:

class A:

    num = 100

    def func1(self):
        print('功能1')

    @classmethod
    def func2(cls):
        print('功能2')
        print(cls.num)
        cls().func1()A.func2()# 功能2# 100# 功能1
Nach dem Login kopieren
Nach dem Login kopieren

7.2 @staticmethod

@staticmethod 同样用来修饰类中的方法,使用它装饰的函数的参数没有任何限制(即无需传入 self 参数),并且可以不用实例化调用该方法。当然,实例化后调用该方法也是允许的。

具体如下:

class A:

    @staticmethod
    def add(a, b):
        return a + bprint(A.add(2, 3))# 5print(A().add(2, 3))# 5
Nach dem Login kopieren

7.3 @property

使用 @property 装饰器,我们可以直接通过方法名来访问类方法,不需要在方法名后添加一对 () 小括号。

class A:

    @property
    def printer(self):
        print('Hello World')a = A()a.printer# Hello World
Nach dem Login kopieren

除此之外,@property 还可以用来防止类的属性被修改。考虑如下场景

class A:

    def __init__(self):
        self.name = 'ABC'a = A()print(a.name)# ABCa.name = 1print(a.name)# 1
Nach dem Login kopieren

可以看出类中的属性 name 可以被随意修改。如果要防止修改,则可以这样做

class A:

    def __init__(self):
        self.name_ = 'ABC'

    @property
    def name(self):
        return self.name_
    
    
a = A()print(a.name)# ABCa.name = 1print(a.name)# AttributeError: can't set attribute
Nach dem Login kopieren

推荐学习:python视频教程

Das obige ist der detaillierte Inhalt vonFassen Sie die Wissenspunkte von Dekorateuren in Python zusammen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:csdn.net
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!