![Python 元类 Metaclasses](http://p2.ttnews.xyz/loading.gif)
Python 的元类也是一个类,只不过它的实例不和普通的类一样,普通类的实例是对象,元类的实例也是一个类。
元类定义了实际生成的类的行为,类似于普通的类定义了它的实例化对象的行为。
元类有大量的使用场景:
- 日志和分析
- 接口检查
- 运行时注册类
- 自动添加方法
- 代理
- 锁和同步
定义一个元类
元类的定义和类一样,只不过它需要继承 type。另一个不同是,当使用元类的 class语句定义完成的时候,会自动执行。
![Python 元类 Metaclasses](http://p2.ttnews.xyz/loading.gif)
输出:
上例中,定义了一个元类 Meta,类 B 继承类 A,同时使用元类 Meta。
使用元类的语法是 class B(metaclass=Meta) ,放在类名后的括号里面,使用命名参数 metaclass 传递元类名称。
从以上输出可以看出,定义了 B 之后,调用了元类的 __new__ 方法。参数分别是当前类 cls,类名 clsname,父类 superclasses 和最后的属性字典 attributedict。
使用元类创造一个单例模式类
单例模式是一个设计模式,只允许类有一个实例对象。下面的代码构造了一个单例模式的类:
类 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__ 方法用在元类中时,每次实例化类都调用这个方法。
另外,他们还都可以用在普通的类中。功能和元类是不一样的,例如:
输出:
__new__ 方法用在正常的类中,用来实例化对象。
使用元类记录类方法访问的次数
在元类中包装子类的每一个方法,使用包装的方法中记录访问次数,最后再调用子类的方法,下面是实现的代码:
输出:
上例中,元类 Meta 的 __new__ 方法,遍历了子类的所有公开的方法,分别使用它的静态方法 counter 重新进行封装。
counter 方法返回一个新的帮助函数,这个帮助函数主要记录类的访问次数,通过在方法上添加 calls 属性实现。最后再调用子类的原始方法。所以我们就可以访问方法的属性 calls 获取访问次数了。
方法可以添加属性,因为在 Python 中,方法也是对象。
閱讀更多 趣喜歡編程 的文章