Python进阶到高级:元类编程

2023-08-1808:16:22编程语言入门到精通Comments736 views字数 3503阅读模式

1、动态属性和属性描述符

有些同学可能知道 @property ,它的主要用于将一个方法变成属性,访问的时候直接通过名称访问,不需要加括号。注意加了 @property 函数不能有参数,你想嘛,人家调用的时候都不用括号,怎么传参,对吧。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

举个小例子:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

class Mikigo:

    @property
    def age(self):
        return "我晕,今年30了"

print(Mikigo().age)
我晕,今年30了

你看,调用 age 方法没加括号吧,那我要修改 age 的值怎么做呢?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

class Mikigo:

    def __init__(self):
        self._age = 30

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise ValueError
        self._age = value

mi = Mikigo()
mi.age = 25
print(mi.age)
25

注意上例中装饰器的写法,setter 是固定写法,setter 前面是你定义的函数名。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

没什么问题哈,做了参数的类型检查,整体看起来不算复杂,其实了解到这里已经差不多了。但是,如果我们还有其他属性要处理,就得写好多个这样的,挺费劲不说,关键是不够优雅。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

这时候就需要请出属性描述符。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

这里又要介绍两个魔法函数:__get____set__文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

举个例子,讲解其用法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

class UserAttr:

    def __init__(self, user_age):
        self._age = user_age

    def __get__(self, instance, owner):
        print("get_instance:", instance)
        print("get_owner:", owner)
        return self._age

    def __set__(self, instance, value):
        print("set_instance:", instance)
        print("gse_value:", value)
        if not isinstance(value, int):
            raise ValueError
        self._age = value

真正使用的类:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

class Mikigo:
    age = UserAttr(30)

mi = Mikigo()
print(mi.age)
get_instance: <__main__.Mikigo object at 0x7fb4eff50e10>
get_owner: <class '__main__.Mikigo'>
30

在对象访问 age 的时候,首先是进入了 __get__ 方法,因为先打印了 get_instance 和 get_owner,instance 是 Mikigo 实例对象,也就是 mi,owner 是 Mikigo 类对象。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

因此,到这里,我们知道了第一个小知识,在访问值的时候,调用的是 __get__文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

再赋值看看:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

mi.age = 25
print(mi.age)
set_instance: <__main__.Mikigo object at 0x7fc7be222470>
set_value: 25
get_instance: <__main__.Mikigo object at 0x7fc7be222470>
get_owner: <class '__main__.Mikigo'>
25

第二个小知识,赋值是调用的 __set__ 方法,一般为了使属性描述符成为只读的,应该同时定义 __get__()__set__() ,并在 __set__() 中引发 AttributeError文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

还有一个魔法函数 __delete__ 也是属性描述符,使用 del 会调用,由于不咋使用,不讲了,还有网上好多区分数据描述符和非数据描述符的,我感觉不用管也没必要,咱们是通俗易懂版,不整那些。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

2、属性拦截器

属性拦截器就是在访问对象的属性时要做的一些事情,你想嘛,拦截就是拦路抢劫,拦截下来肯定要搞点事情才放你走。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

主要介绍 2 个魔法函数:__getattr____getattribute__文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

这两个函数特别神奇,两个函数功能相反,一个是找到属性要做的事,另一个是没找到属性要做的事。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

class Mikigo:

    def __init__(self):
        self.age = 30

    def __getattribute__(self, item):
        print(f"找到{item},我先搞点事情")

    def __getattr__(self, item):
        print(f"没找到{item},我想想能搞点啥事情")

定义了一个属性 age ,先来试试访问它文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

mi = Mikigo()
print(mi.age)
找到age,我先搞点事情
30

找到属性,会先调用 __getattribute__ ,并没有调用 __getattr__文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

好,现在访问一个不存在的属性:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

mi.name
找到name,我先搞点事情
没找到name,我想想能搞点啥事情

这里就需要注意了,访问一个不存在的属性,首先还是会进入 __getattribute__ ,说明它是无条件进入的,然后才是调用 __getattr__文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

再扩展一个 __setattr__ 用于修改属性值的:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

class Mikigo:
    def __init__(self):
        self.age = 30

    def __setattr__(self, key, value):
        print(f"修改{key}的值为{value}")
        self.__dict__[key] = value

mi = Mikigo()
mi.age = 25
print(mi.age)
修改age的值为30
修改age的值为25
25

你看,age 的值被修改了,但是 __setattr__ 貌似被调用了 2 次,那是因为在类实例化的时候就会进入一次,第一次是将 __init__ 里面的值添加到类实例的 __dict__ 属性中,第二次修改再次进入,将 __dict__ 属性中的值修改掉。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

属性拦截一定要谨慎使用,一般情况下不建议使用,因为如果处理不好,会造成类里面属性关系的混乱,抛异常往往不容易定位。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

项目实例,config 文件里面用到:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

class Config:
    default = {
        # for cases
        "SMB_URL": "SMB://10.8.10.214",
        "SMB_IP": "10.8.10.214",
    }

    def __getattr__(self, key):
        try:
            return Config.default[key]
        except KeyError:
            raise AttributeError(f"{key} is not a valid option!") from KeyError

    def __setattr__(self, key, value):
        if key not in Config.default:
            raise AttributeError(f"{key} is not a valid option!") from KeyError
        Config.default[key] = value

试着分析下他们的作用吧,逻辑很简单的,你一定能看懂。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

3、自定义元类

元类(metaclass)就是生成类的类,先定义metaclass,就可以创建类,最后创建实例。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

其实最开始讲 type 的时候已经有所接触了,type 生成了所有类,它就是顶层元类,metaclass 也是要继承 type的,排行顶多老二,是不是应该叫“元二类”,或者“元类二”,爱谁谁吧。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

来,咱们定义一个元类,用途是添加一个属性 age :文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

class AutoTestMetaClass(type):

    def __new__(cls, name, bases, dct):
        x = super().__new__(cls, name, bases, dct)
        x.age = 30
        return x

这里有 2 个知识点:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

  • __new__ 也是构造函数,和 __init__ 有区别,__new__ 是用来构造类对象的,你看它的参数是 cls,必须 return 一个对象。
  • name, bases, dct 这三个参数和 type 的三个参数是一个意思,不清楚可以回看前面讲 type 的章节。

元类有了,咱们使用一下,既然元类是用来生成类的类,那咱们就来生成一个类:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

class Mikigo(metaclass=AutoTestMetaClass):
    ...

mi = Mikigo()
print(mi.age)
print(Mikigo.age)
30
30

咱们定义一个类除了省略号没有任何属性,省略号也是一个对象,你也可以用 pass,但是仍然可以访问 age 属性。因为我们是通过元类,向 Mikigo 这个类添加了一个属性,元类有时称为类工厂。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html

作者:mikigo
来源:稀土掘金
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/53930.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/53930.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定