美文网首页
深度解析Python内置装饰器

深度解析Python内置装饰器

作者: 木叶苍蓝 | 来源:发表于2023-01-04 10:57 被阅读0次

引入

还记得初学 C++ 或者 Java 时关于面向对象的三大思想吗?封装,继承,多态。你会发现,C++ 和 Java 都有相应的语法层面的机制来实现这三大思想,但是 Python 好像没有虚类(抽象类)吗?C++ 的静态函数好像 Python 中也无法实现吗?
别急,Python 提供的内置装饰器可以帮助你实现上面的特性,而且还带有一些神奇的效果。

装饰器

  • property
  • staticmethod
  • classmethod
  • abstractmethod (所属模块 abc)
  • wraps (所属模块functools)

property

porperty 用于装饰类函数,被装饰的类函数不可在类被实例化后被调用,只能通过访问与函数同名的属性进行调用。

class Test:
    num = 1

    @property
    def num_val(self):
        return self.num

test = Test()
print(test.num_val)
>>> 1

我们不妨解构这个类,来看看num_val到底是什么:

for i in dir(test):
    if not i.startswith("__"):
        print(i, type(getattr(test, 1)))
>>> num <class 'int'>
>>> nul_val <class 'int'>

可以看到,装饰过的 num_val 确实变成了一个属性。但是它与普通属性有很大的区别,就是它没有定义 set 方法,也就是它不能被赋值,这是一个只读变量:

test.num_val = 1
>>> Traceback (most recent call last):
        File "e:\python draft\test.py", line 9, in <module>
            test.num_val = 1
    AttributeError: can't set attribute

利用这个特性,我们可以实现对 Python 类成员的安全访问,还记得 Python 的私有成员怎么写的吗?通过双下划线前缀可以将一个类属性定义为私有属性。我们利用这些特性就可以实现下述的例子:

class Test:
    __number = 1

    @property
    def number(self):
        return self.__number

test = Test()

try:
    print(test.__numer)
except:
    print("访问 私有成员 失败")

try:
    print(test.number)
    print("访问 类属性 成功")
except:
    pass

try:
    test.number = 1
except:
    print("修改 类属性 失败")

>>> 访问 私有成员 失败
>>> 1
>>> 访问 类属性 成功
>>> 修改 类属性 失败

staticmethod

被这个装饰器过类函数就会被声明成一个函数的静态函数。静态函数不需要类实例化就能直接调用。被装饰的函数不需要代表对象实例的 self 参数。

class Test:
    @staticmethod
    def add(x, y):
        return x + y

    @staticmethod
    def minus(x, y):
        return x - y

print(Test.add(1, 1))
print(test.minus(1, 2))
>>> 2
>>> -1

在staticmethod下,你不仅可以实现曾经 C++,Java 中熟悉的 static ,还可以直接实现 Java 中的接口机制或者 C++ 中命名空间机制,比如这样:

class Game:
    win = 0
    loss = 0

    @staticmethod
    def I_win():
        Game.win += 1

    @staticmethod
    def I_loss():
        Game.loss += 1

Game.I_win()
Game.I_lose()
Game.I_win()
Game.I_win()
Game.I_lose()

print(Game.win, Game.loss)
>>> 3 2

直接定义在 Python 类中的属性为静态变量,比如上面的 win 和 loss
如果没有 staticmehod 装饰器,我们也能实现上面的效果:

  1. 创建文件名为 Game.py
  2. 定义各个变量和函数
  3. 在入口文件中引入 Game.py
  4. 剩余操作完全一样
    但是很显然,这样做比较麻烦,而且如果我们单个接口类实现成本低,那么就会创建若干 Python 文件,在运行项目时,还增加了读入这些文件的 IO 成本,使用 staticmethod 装饰器无疑更加灵活。

classmethod

这个装饰器很有意思,它与 staticmethod 的使用效果非常像,被装饰的类函数也是可以在没有被实例下直接定义的,只不过被 clasmethod 装饰的函数必须要有一个 cls 参数,代表类本身。来看一个实例:

class Test:
    n = 1
    def __init__(self, a) -> None:
        self.a = a

    @classmethod
    def add_a(cls):
        print(cls)
        print(cls == Test)
        print(cls.n)

Test.add_a()

由于被 classmethod 装饰的函数强制暴露了类自身,所以我们可以通过被 classmethod 装饰的函数对类的静态变量进行一定的操作(staticmethod 中也可以)
与staticmethod 不同的是,classmethod 更多是关乎这个类的静态变量的操作,而 staticmethod 则是与实例无关但与类封装功能有关。
结合 Python 的多态(比如鸭子类型),使用 classmethod 使得在 python 中开发抽象程序高于 class 的实体成为了可能。在许多 Python 内置库的原语实现代码中,经常能看到 classmethod 的影子。

abs.abstractmethod

初学 Python 时,大家肯定很疑惑:为啥 Python 没有抽象类机制呢?Python 中的虚函数怎么定义呀?
带着这些疑惑,恭喜你,今年迎来解答:为了补充 Python 原语无法实现抽象类的问题,Python提供了内置库 abc 来提供抽象类的机制,如果读者喜欢翻一些 Python 库的源代码,那么你应该经常会看到 abc 这个库。
abc 是 abstract base class 的缩写
由于本文只是讲内置装饰器的,所以不谈抽象类的实现,后面会有文章具体谈到。
abc模块提供了几个装饰器用于将 Python 父类需要实现的方法申明为虚函数,比如 abstractmethod ,它所在的类必须被申明为抽象类之后才能生效。
[abc -- 抽象基类]https://docs.python.org/zh-cn/3/library/abc.html
简单看一个例子:

from abc import abstractmethod, ABC

class Animal(ABC):
    def __init__(self, name):
        self.name = name

    @abstractmethod
    def get_home(self):
        pass

class Dog(Animal):
    ...

dog = Dog("bob")
dog.get_home()
>>>
Traceback (most recent call last):
  File "e:\python draft\test.py" line 14, in <module>
    dog = Dog("bob")
TypeError: Can't instantiate abstract class Dog with abstract methods get_home

abstractmethod 将父类中的 get_home 声明为一个虚函数,由于子类中未实现虚函数,所以报错,符合预期。
只需要在Dog类中实现父类的所有虚函数就不会报错:

class Dog(Animal):
    def get_home(self):
        print("陆地")
>>>
陆地

同样的,之前谈到的所有原生装饰器,都有抽象版本的,比如:

  • property -> abstractproperty
  • staticmethod -> abstractstaticmethod
  • classmethod -> abstractclassmethod

但是在Python3.3 之后,这三个额为的装饰器就可以丢弃了,因为@property 等原生装饰器可以在 @abstractmethod 上面堆叠,组成更为复杂的装饰效果。
不过,堆叠的时候一定要记住 @abstractmethod 在最下面

functools.wraps

在讲这个之前,我们先回顾一下装饰器的基本使用,比如我们现在完成一个装饰器,被它装饰的函数会执行之前打印函数的名称:

def printname(func):
    def inner(*args, **kwargs):
        print("execute", func.__name__)
        return func(*args, **kwargs)
    return inner

@printname
def add(x, y):
    return x + y

print(add(1, 1))
>>>
execute add
2

嗯,看似没有问题,但是如果你想访问add本身,你就会发现问题了

print(add.__name__)
>>>
inner

我们发现,装饰器的行为污染了被装饰器函数的成员变量,这是我们不希望看到的,那么有什么方法可以解决这个问题?答案就是使用functools 中 wraps 来包装传入装饰器的函数,我们只需要如此修改:

from functools import wraps

def printname(func):
    @wraps(func)
    def inner(*args, **kwargs):
        print("execute", func.__name__)
        return func(*args, **kwargs)
    return inner

@printname
def add(x, y):
    return x + y

print(add.__name__)
>>>
add

相关文章

  • 装饰器模式

    介绍 在python装饰器学习 这篇文章中,介绍了python 中的装饰器,python内置了对装饰器的支持。面向...

  • Python 装饰器深度解析

    参考:https://zhuanlan.zhihu.com/p/45458873 假设现在有一个 add 求和函数...

  • python的random模块

    random不是python解释器内置的模块,它不是Python解析器的内置模块。导入random模块的方法是: ...

  • Python内置装饰器

    1、staticmethod() a)描述 原文:staticmethod(function) -> method...

  • python内置装饰器

    1.@property把类内方法当成属性来使用,必须要有返回值,相当于getter。 2.@staticmetho...

  • PYTHON部分基础D4

    Decorator装饰器 装饰器自己可以有参数 内置函数 文件读写 Python3的继承机制 成员保护和访问限制 ...

  • Python中@property的使用讲解

    装饰器(decorator)可以给函数动态加上功能,对于类的方法,装饰器一样起作用。Python内置的@prope...

  • 36-@property装饰器

    @property装饰器 Python内置的@property装饰器可以把类的方法伪装成属性调用的方式 。 将一个...

  • 装饰器

    """@装饰器- 普通装饰器- 带参数的装饰器- 通用装饰器- 装饰器装饰类- 内置装饰器- 缓存装饰器- 类实现...

  • python-面试QA

    语言 讲讲日常开发中都用到了那些Python内置的模块 推荐一本看过较好的python书籍? 装饰器、迭代器、ye...

网友评论

      本文标题:深度解析Python内置装饰器

      本文链接:https://www.haomeiwen.com/subject/wgmvqdtx.html