06.14 Python 元类 Metaclasses

Python 元类 Metaclasses

Python 的元类也是一个类,只不过它的实例不和普通的类一样,普通类的实例是对象,元类的实例也是一个类

元类定义了实际生成的类的行为,类似于普通的类定义了它的实例化对象的行为

元类有大量的使用场景:

  • 日志和分析
  • 接口检查
  • 运行时注册类
  • 自动添加方法
  • 代理
  • 锁和同步

定义一个元类


元类的定义和类一样,只不过它需要继承 type。另一个不同是,当使用元类的 class语句定义完成的时候,会自动执行。

Python 元类 Metaclasses

输出:

Python 元类 Metaclasses

上例中,定义了一个元类 Meta,类 B 继承类 A,同时使用元类 Meta。

使用元类的语法是 class B(metaclass=Meta) ,放在类名后的括号里面,使用命名参数 metaclass 传递元类名称。

从以上输出可以看出,定义了 B 之后,调用了元类的 __new__ 方法。参数分别是当前类 cls,类名 clsname,父类 superclasses 和最后的属性字典 attributedict。

使用元类创造一个单例模式类


单例模式是一个设计模式,只允许类有一个实例对象。下面的代码构造了一个单例模式的类:

Python 元类 Metaclasses

类 A 使用了元类 Singleton,所以 A 的两个实例对象相等 a1 == a2。 类 B 是一个普通的类,它实例化后的两个对象 b1 和 b2 不等。

元类 Singleton 做了什么,使得实例化的对象能够相等呢?

它只是定义了一个 __call__ 方法和一个私有属性 _instances。__call__ 方法当每次类实例化对象的时候调用。如上例,类 A() 调用了两次,两次进入 __call__ 方法。

第一次判断类 cls(也就是本例的类 A)属性 _instances 是否存在键值 cls(这里要实例化的类当做字典的键值),如果没有,调用上级 super().__call__(*args, **kwargs) 实例化,生成对象,对象保存在属性 _instances 中。此时属性包括一个字典映射 A -> 实例对象。

第二次判断 类 A 存在字典属性 _instances 中,就不重新生成对象了。所以 a1 == a2,他们指向同一个对象。

魔术方法


这里出现了两个元类的魔术方法 __new__ 和 __call__。

__new__ 方法当用在元类中时,定义了实现了元类的类的默认行为。当类定义完成时,自动执行方法里的代码。

__call__ 方法用在元类中时,每次实例化类都调用这个方法。

另外,他们还都可以用在普通的类中。功能和元类是不一样的,例如:

Python 元类 Metaclasses

输出:

Python 元类 Metaclasses

__new__ 方法用在正常的类中,用来实例化对象。

使用元类记录类方法访问的次数


在元类中包装子类的每一个方法,使用包装的方法中记录访问次数,最后再调用子类的方法,下面是实现的代码:

Python 元类 Metaclasses

输出:

Python 元类 Metaclasses

上例中,元类 Meta 的 __new__ 方法,遍历了子类的所有公开的方法,分别使用它的静态方法 counter 重新进行封装。

counter 方法返回一个新的帮助函数,这个帮助函数主要记录类的访问次数,通过在方法上添加 calls 属性实现。最后再调用子类的原始方法。所以我们就可以访问方法的属性 calls 获取访问次数了。

方法可以添加属性,因为在 Python 中,方法也是对象。


分享到:


相關文章: