23.python元类-元编程
python元类
python中的一切都是对象,类本身也是对象,元类则是创建类对象的类
元类(metaclass)可以干预类的创建,控制类的实例化。
通俗地理解就是,普通的类用来定义一般对象的属性和行为,元类用来定义普通的类及其实例对象的属性和行为。
type|object|class
- object: object类是所有类(class)的父类,包括type类,object类的父类为空
- type: type类是所有类的类型,即为所有类(class)都可由type实例化而来,包括type类和父类object
- class: 继承自object,同时,由type进行实例化。其中,type就是我们所要讲的元类(metaclass)
class MyClass(object):
pass
print(type(MyClass())) # 类的实例对象属于 MyClass
print(type(MyClass)) # 发现类的对象属于 type
print(type(int)) # 发现int类也属于 type
print(type.__base__) # 发现其父类是 object
类创建的两种方式
传统方式创建
# 我们传统的实现方式
class MyClass(object):
def get(self) -> None:
print("类获取了一个信息", self.info)
info = "hello"
c = MyClass()
c.get()
# 类获取了一个信息 hello
# 动态创建方式
def get(self) -> None:
"""定义类内部需要运行的函数"""
print("类获取了一个信息", self.info)
MyClass = type("MyClass", (object, ), {"get": get, "info": "hello"})
c = MyClass()
if hasattr(c, "get"): # 判断是否有get函数
getattr(c, "get", None)() # 获取get函数,并运行函数
else:
print("该类内部没有get函数")
# 类获取了一个信息 hello
动态创建类
type 两种使用方式:
type(object)
获取对象类型。type(name, bases, dict)
创建类对象。
Foo = type(
"Foo", #类的名字
(object,), #继承的类
#字典封装的成员
{
"v1": 123,
#通过lamba实现构造函数
"__init__": lambda self: print("创建Foo"),
"print": lambda self: print("调用实例函数"),
}
)
obj = Foo()
print(obj.v1)
obj.print()
# 创建Foo
# 123
# 调用实例函数
class A(object):
pass
print(A)
# <class '__main__.A'> 类
print(A())
# <__main__.A object at 0x000002044DDC24C0> 对象
B=type("B", (object, ), dict())
print(B)
oB=type("B", (object, ), dict())()
print(oB)
# <class '__main__.B'> 类
# <__main__.B object at 0x0000026356A8F0A0> 对象
import abc
import types
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
cls_dict = {
'__init__': __init__,
'cost': cost,
}
Stock = types.new_class(
'Stock', # 类名
(), # 父类
{'metaclass': abc.ABCMeta}, # 元类及类定义参数
lambda ns: ns.update(cls_dict) # ns 为 prepare 方法(见上)返回的字典对象
)
Stock.__module__ = __name__
s = Stock('abc', 200, 35)
print(s)
什么是元类
MetaClass
元类,本质也是一个类,但和普通类的用法不同,它可以对类内部的定义(包括类属性和类方法)进行动态的修改
主要目的就是为了当创建类时能够自动地改变类
创建类的流程
函数做为元类
# 元类会自动获取通常传给`type`的参数
def upper_attr(_class, _object, _attr):
"""
返回一个类对象,将其属性置为大写
"""
# 过滤出所有开头不为'__'的属性,置为大写
uppercase_attr = {}
for name, val in _attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# 利用'type'创建类,同时将这个类进行返回
return type(_class, _object, uppercase_attr)
class Foo(metaclass=upper_attr): # 创建对象时,其会经过 metaclass 来创建,再使用自身的方法进行实例化
bar = 'bip'
print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))
f = Foo()
print(f.BAR)
类做为元类
在有元类的情况下,每次创建类时,会都先进入 元类的 __new__
方法,如果你要对类进行定制,可以在这时做一些手脚。
综上,元类的__new__
和普通类的不一样:
元类的__new__
在创建类时就会进入,它可以获取到上层类的一切属性和方法,包括类名,魔法方法。
而普通类的__new__
在实例化时就会进入,它仅能获取到实例化时外界传入的属性。
class UpperAttrMetaClass(type): # 注意,元类必须要继承type这个类
"""
返回一个类对象,将其属性置为大写
"""
def __new__(cls, *args, **kwargs):
_class, _object, _attr = args # 分别对应 类名,继承的类,属性
uppercase_attr = {}
# 过滤出所有开头不为'__'的属性,置为大写
for name, val in _attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaClass, cls).__new__(cls, _class, _object, uppercase_attr)
# 使用type的new方法,相当于使用type创建类
class Foo(metaclass=UpperAttrMetaClass): # 创建对象时,其会经过 metaclass 来创建,再使用自身的方法进行实例化
bar = 'bip'
print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))
f = Foo()
print(f.BAR)
自定义元类
自定义元类主要是指,通过定义一个继承于 type
的元类,并重写 __new__
方法来对类的创建过程实现定制。
其具体步骤分为:
- 定义阶段:定义一个继承于
type
的元类,并编写__new__
方法 - 使用阶段:在需要被定制的类中添加
metaclass=<your metaclass>
关键字参数
可以通过继承 type 类来定义元类。元类应该定义一个 __new__ 方法。这个方法负责接收类定义的参数,并返回一个类对象
class MyMeta(type):
def __new__(cls, name, bases, dct):
x = super().__new__(cls, name, bases, dct)
x.attr = 100
return x
class MyClass(metaclass=MyMeta):
pass
print(MyClass.attr)
# 输出: 100
元类的应用场景
-
自动添加属性或方法,拦截类的创建
-
类初始化和注册,修改类的定义
-
强制API一致性
单实例-元类
"""
使用元类实现单例模式
注意: 这种写法非线程安全!
"""
class Singleton(type):
def __init__(cls, *args, **kwargs):
cls._instance = None
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class Test(metaclass=Singleton):
def test(self):
print("test")
test1 = Test()
test2 = Test()
print(id(test1), id(test2))
# 4473306472 4473306472
对象缓存
对象创建代价比较大的时候,做对象缓存是个不错的选择,很多缓存是用专门的缓存池来完成,这里也可以直接用元类来做缓存。
import weakref
class CacheMeta(type):
"""
使用元类做对象缓存处理
"""
def __init__(cls, name, bases, namespace, **kwargs):
super().__init__(name, bases, namespace)
cls._cache = weakref.WeakValueDictionary()
def __call__(cls, *args):
# 传入的参数相同的,就给同一个对象,如果对应参数的对象还没创建就先创建对象
if args in cls._cache:
return cls._cache[args]
obj = super().__call__(*args)
cls._cache[args] = obj
return obj
class CacheClass(metaclass=CacheMeta):
def __init__(self, name):
print('Creating instance({!r})'.format(name))
self.name = name
if __name__ == '__main__':
o1 = CacheClass('hello')
o2 = CacheClass('world')
o3 = CacheClass('hello')
print(o1 is o2)
print(o1 is o3)
# Creating instance('hello')
# Creating instance('world')
# False
# True
抽象类
抽象类是一种特殊的类,它只能有抽象方法,不能被实例化,
在子类继承抽象类时,不能通过实例化使用其抽象方法,必须实现该方法。
抽象类的作用
-
一种规范 规定子类应该具备的功能
-
抽象类可以实现多个子类中共用的部分,而不需要重复写到实现类中。
-
目的让别的类继承它以及实现特定的方法
-
从设计角度去看,抽象类是基于类抽象而来的,是实现类的基类。
-
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且实现子类必须继承并实现抽象类的方法。
# abc.ABCMeta
import abc
class Base(metaclass=abc.ABCMeta):
@abc.abstractmethod
def read(self):
pass
@abc.abstractmethod
def write(self):
pass
class Http(Base, abc.ABC):
pass
from abc import ABC, ABCMeta, abstractmethod
class Person(ABC):
@staticmethod
@abstractmethod
def age_fudging(age):
pass
@classmethod
@abstractmethod
def weight_fudging(cls, weight):
pass
@property
@abstractmethod
def age(self):
pass
@age.setter
@abstractmethod
def age(self, val):
pass
@property
@abstractmethod
def weight(self):
pass
@weight.setter
@abstractmethod
def weight(self, val):
pass
class BeiKe(Person):
def __init__(self,age,weight):
self._age = age
self._weight = weight
@staticmethod
def age_fudging(age):
return age - 10
@classmethod
def weight_fudging(cls, weight):
return weight - 20
@property
def age(self):
return BeiKe.age_fudging(self._age)
@age.setter
def age(self, age):
print('设置age属性')
self._age = age
@property
def weight(self):
return self.weight_fudging(self._weight)
@weight.setter
def weight(self):
return
y = BeiKe(31,100)
print("名字:贝壳 年龄:{0} 体重:{1}".format(y.age, y.weight))
setattr(y, "age", 49)
print("获取年龄:",y.age)
# 名字:贝壳 年龄:21 体重:80
# 设置age属性
# 获取年龄: 39