python元类简述
面向对象编程
python 是一门按照面向对象思维设计的一门编程语言,而理解面向对象的语言关键在于一句话,"一切皆对象",函数是一个对象,一个实例是对象,一个类也是对象。如果对象被创建了,那么在这个程序管理的内存区域中,就会真实的存在一片区域去保存这个对象的信息。因此,我们在代码中定义一个函数,定义一个类,即使我们并没有调用这个函数或者去实例化这个类,在内存空间中,也已经存在了这个函数对象和类对象。例如下面的代码。
class A: def __init__(self, a, b): self.a = a self.b = b
代码执行后,内存中会创建一个类对象,且类对象上显式的绑定了__init__方法。
类
我们使用class A: pass
这样的语句可以定义一个类,但是我们并没有在意它是如何被创建的,实际上这个语句的完整写法为
class A(metaclass=type): pass
这里metaclass=type
的意思是这个A类使用type类来创造,也就是使用type类作为他的元类,如果我们不声明,就默认使用type来创建这个类,而type也是一个类对象,这个类对象的功能,就是创建一个类,所以他与我们自己定义的普通的类又有所区别,所以有了metaclass,也就是元类的称呼。
何为元类
元类:简单的一句话就是创建类的类。首先元类也是一个类,,所以元类也是由另一个元类创建的,但是创建一个元类必须指定这个元类将继承哪个元类。下面看一些元类和普通类的区别。
class MetaclassA(type): # 继承type类,并由type类创建,定义了一个元类 pass class MetaclassB(MetaclassA): # 继承MetaclassA,由type创建,定义了一个元类 pass class MetaclassB(MetaclassA, metaclass=MetaclassB): # 继承MetaclassA,由MetaclassB pass class C: # 由type 类创建,继承object。是一个普通类 pass class D(metaclass=MetaclassA): # 由metaclassA 创建,继承object,是一个普通类 pass
总结为两点
- 类在创建时可以指定自己被哪个元类创建,使用metaclass参数指定一个元类即可,没指定默认为type元类
- 定义的类是普通类还是元类,看他是直接继承自元类及其子类,还是继承自object及其子类。
元类的作用
元类的各类框架中使用较为广泛,由于自定义元类的参与,使得我们自己定义的类在创建时候,可以方便的进行一些自定义的初始化,完成我们想要的功能。在日常的编码中的一般很少使用元类,但是在阅读源码的过程中却常遇到,作者利用元类做一些非常精巧的设计去实现使用其他方法很难完成得功能。从下面的使用中体会元类的使用
元类的使用
默认情况下,类的创建都是交给 type元类 去创建,创建后的类有自己的默认的一些属性和方法,也有一部分从object类继承的属性和方法。如果我们想要在需要定义一个元类来实现类的自定义初始化,就必须继承自type或者及其子类。type类中默认创建类对象的方法为 __new__(cls) 方法。我们可以重写这个方法。
class MetaClassA(type): def __new__(mcs, name, base, attrs): ''' mcs,这个元类对象自己 name:将要被创建的类的名字,下面的代码中 定义了一个 A 类,则这个name 为 "A" 这个字符串 base: 被创建的类如果指定了需要继承某个类,base中将会纪录他所有的父类对象。 attrs:被创建的类中定义的类属性和方法的字典,字典的key 为属性的变量名,value为对应的属性值 ''' # 查看这四个参数的值 print(mcs, name, base, attrs) # 类名,父类元组,类属性字典。 '''输出的值 msc: <class '__main__.Metaclass'> name: A base: (<class 'object'>,) attrs: {'__module__': '__main__', '__qualname__': 'A', 'xxx': 1, 'x': <function A.<lambda> at 0x00000232DFF34620>, 'Meta': <class '__main__.A.Meta'>, '__init__': <function A.__init__ at 0x00000232DFF346A8>} ''' # 查看参数值后,需要调用type类的new方法创建因为python 只提供了type元类来调用系统调用来申请内存空间,创建一个类对象并返回 cls = type.__new__(mcs, name, base, attrs) # 返回值则是 A 这个类对象,我们可以使用dir查看类中的属性和方法,可以看到 上面attrs中的值,并新增了很多k-v对 print(cls, dir(cls)) return cls # 必须使用 return 将这个类对象的返回,下面定义的class A 的 A 变量才能收到这个cls 对象,否则 A 的值为None, 即使使用了class A这个语法。 # 我们使用MetaClassA 创建 类A class A(object, metaclass=MetaclassA): # 为 A定义两个类属性 xxx = 1 yyy = lambda x: x + 1 # 定义一个类, 这个类的也是A类的一个属性,可以通过A.Meta访问到这个类对象 class Meta: x = [] y = 1 # 定义一个方法 def __init__(self): self.a = 1 self.b = 2
通过上面的代码可以了解 一个类创建 的过程,在metaclassA的 __new__()
方法中,我们获取到了在 A 类中定义的所有类属性和方法,这些属性和方法都被收集到attrs 这个字典中,并交给 type.__new__() 方法用于创建类,所以想要操作类中的属性和方法,可以对attr字典进行想要的操作。例如我们为所有的类都添加一个方法。
def func(): print("我是被 MetaClassA 创建的") class MetaClassA(type): def __new__(mcs, name, base, attrs): print(mcs, name, base, attrs) # 类名,父类元组,类属性字典。 attrs["show"] = func # 将func 这个函数添加到A 的方法中,这样 A 类会有一个show方法,调用后会执行func函数。 return type.__new__(mcs, name, base, attrs)
动态的创建类
动态的创建类 是指 直接通过 元类的方法来创建类,而不在使用 class 这个关键字来创建类对象。
使用关键字定义
class A: a = 1 b = 2 def show(self): pass
动态的创建
A = type.__new__(type, "A", (), {"a":1, "b":2, "show": lambda :pass})
这样的代码和上述的结果是相同的,或者还可以使用更直接的方法。我们知道普通类创建实例时候,可以 ins = ClassName(*args)
的方式创建一个实例对象 ins,同样,元类也可以如此创造一个类
A = type("A", (), {"a":1, "b":2, "show": lambda :pass})
对一个元类使用类似实例化的方法,得到了一个类对象。
综合一下:
class Metaclass(type): def __new__(cls, class_name: str, bases: tuple, attrs: dict): print(1, cls) print(2, class_name) print(3, bases) return type.__new__(cls, class_name, bases, attrs) # 1. 创建一个类名为 A 的类, class A(metaclass=Metaclass): a = 100 # 2. 创建一个类名为 B 的类,使用 BClass变量接收 BClass = Metaclass.__new__(Meta, 'B', (), {"a":100}) # 创建一个B类 print(BClass) # 3. 创建一个类名为 C 的类,,使用 CClass变量接收 CClass = Metaclass("C", (BClass, ), {"a": 100}) # 创建一个C类,并继承B类 print(CClass)
上面三个类的创建过程完全一致,处理类名不同和内存中的储存的地址不同,所拥有的属性和方法都是一样的。
元类的使用实例(django-orm中元类的简单使用原理)
Django的ORM中的Model类使用了元类来实现“表-类”,“实例-一条记录”,“实例属性-字段”的对应关系
class MetaModel(type): def __new__(mcs, class_name: str, bases: tuple, attrs: dict): # 收集所有的字段,并添加一些默认需要的属性,并提取设置为主键的 字段 attrs.setdefault("db_table", class_name.lower()) # 设置表名 for key, field in attrs: if isinstance(field, Field): if field.pk: # 该字段被设置了主键,定义设置主键名字 __primary__ = field.name or key if not field.name: # 没有自定义名字,使用属性名作为在 field.name = key return type.__new__(mcs, class_name, bases, attrs) # 字段对象,记录可字段的名字,类型,是否为null, 是否为主键等等元信息,和数据库中字段的元数据对应 class Field: def __init__(self, name=None, typ="varchar", null=None, primary_key=False): self.name = name self.type = typ self.null = null self.pk = primary_key def __repr__(self): return "<{}-{}-{}-{}>".format( self.name, self.type, self.null, self.pk ) # 表对象,每一个类属性就是一个字段对象 class Person(metaclass=MetaModel): # 创建这个类时候,将会调用 MetaModel的new方法,这样定义的每个字段都会被 metaclass中attrs 收集并操作 id = Field("id", primary_key=True) name = Field("name", ) age = Field("age", null=False)
通过使用元类,在我们将这个Person类属性字典进行了操作,自动建立了一些属性信息,例如db_table
将使用该名字作为数据库中表名,指定了属性__primary__
作为主键。类似于Django中的做法,这里只是简单的演示元类的应用。