Metaclass in python

Metaclass in python

What you need to know before understanding metaclasses

If we say let's create a class, the first thing that comes to mind must be to classcreate. When we use to classcreate a class, the python interpreter automatically creates this object, but python also provides a manual method to create a class, which is to use Python's self-built function type().

The function of the well-known type()function is to return the type of a parameter, but in fact, it also has a completely different ability, that is, it accepts some description of a class as a parameter, and then returns a class.

type()The syntax of the function is this:

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

for example:

class ReedSun(ShuaiGe):
    shuai = True
    def test(x):
        return x+2
# 就等价于
type("ReedSun", (ShuaiGe,), {"shuai":True, "test":lambda x: x+2})
# (属性和方法本质上都是方法)

In python, classes are also objects. When we use the class keyword to create a class, the Python interpreter just scans the syntax defined by the class, and then calls type()the function to create the class.

what is a metaclass

What is a metaclass? A metaclass is really what is used to create a class. To help us understand, we can think of it this way, we create classes to create instances of classes, in the same way that we create metaclasses to create classes. A metaclass is a class of a class (instance) like the following

Metaclass() = class
class() = object  # object==>实例

To understand what a metaclass is, let's take a look at the type() function.

In fact, type is a metaclass, and type is the metaclass we use to create all classes. (If we want to create our own defined metaclass, we must also inherit from type)

How metaclasses work

Let's look at the following example

class ReedSunMetaclass(type):
    pass

class Foo(object, metaclass = ReedSunMetaclass): 
    pass

class Bar(Foo):
    pass
  1. First, we create a metaclass ReedSunMetaclass(note! By default, the class name of a metaclass always Metaclassends with it, to make it clear that this is a metaclass) .

  2. Then, we ReedSunMetaclasscreate a class with the Foometaclass, (at the same time, Foothe properties __metaclass__of the class become ReedSunMetaclass) .

  3. 最后,我们创建了一个子类Bar继承自Foo

我们来试着理解一下在python内部是怎么执行这几个步骤的:

  • 对于父类Foo,Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。很显然,它找到了。

  • 对于子类Bar, python会先在子类中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Bar,如果没有找到,就再从父类中寻找,直到type。显然,它在父类中找到了。

我们可以看到使用元类的一个好处了,即他可以让子类隐式的继承一些东西。

自定义元类

元类的主要目的就是为了当创建类时能够自动地改变类。创建类我们需要定义__new__()函数,__new__ 是在__init__之前被调用的特殊方法,是用来创建对象并返回之的方法。我们举个例子来说明定义自定义元类的方法。

__new__()方法接收到的参数依次是: 
1. 当前准备创建的类的对象; 
2. 类的名字; 
3. 类继承的父类集合; 
4. 类的方法集合。

class ReedSunMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # 添加一个属性
        attrs['哈哈哈'] = True
        return type.__new__(cls, name, bases, attrs)

我们用一个实际例子来说明元类的用法

ORM就是一个典型的使用元类的例子。ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。下面我就用这个ORM的例子来说明一下元类的用法。

#ORM:object relational mapping 对象-关系映射
#把关系数据库的一行映射为一个对象,也就是一个类对应一个表
#ORM框架所有的类只能动态定义


# 定义Field(定义域:元类遇到Field的方法或属性时即进行修改)
class Field(object):

    def __init__(self, name, column_type):  # column==>列类型
        self.name = name
        self.column_type = column_type

    # 当用print打印输出的时候,python会调用他的str方法
    # 在这里是输出<类的名字,实例的name参数(定义实例时输入)>
    # 在ModelMetaclass中会用到
    def __str__(self):
        return "<%s:%s>" % (self.__class__.__name__, self. name)  # __class__获取对象的类,__name__取得类名



# 进一步定义各种类型的Field
class StringField(Field):

    def __init__(self, name):
        # super(type[, object-or-type])  返回type的父类对象
        # super().__init()的作用是调用父类的init函数
        # varchar(100)和bigint都是sql中的一些数据类型
        super(StringField, self).__init__(name, "varchar(100)")  

class IntegerField(Field):

    def __init__(self, name):
        super(IntegerField, self).__init__(name, "bigint")


# 编写ModelMetaclass
class ModelMetaclass(type):

    # __new__方法接受的参数依次是:
    # 1.当前准备创建的类的对象(cls)
    # 2.类的名字(name)
    # 3.类继承的父类集合(bases)
    # 4.类的方法集合(attrs)
    def __new__(cls, name, bases, attrs):
        # 如果说新创建的类的名字是Model,那直接返回不做修改
        if name == "Model":
            return type.__new__(cls, name, bases, attrs)
        print("Found model:%s" % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print("Found mappings:%s ==> %s" % (k, v))  # 找到映射, 这里用到上面的__str__
                mappings[k] = v
            # 结合之前,即把之前在方法集合中的零散的映射删除,
            # 把它们从方法集合中挑出,组成一个大方法__mappings__
            # 把__mappings__添加到方法集合attrs中
        for k in mappings.keys():
                attrs.pop(k)
        attrs["__mappings__"] = mappings
        attrs["__table__"] = name # 添加表名,假设表名与类名一致
        return type.__new__(cls, name, bases, attrs)


# 编写Model基类继承自dict中,这样可以使用一些dict的方法
class Model(dict, metaclass=ModelMetaclass):

    def __init__(self,  **kw):
        # 调用父类,即dict的初始化方法
        super(Model, self).__init__(**kw)

    # 让获取key的值不仅仅可以d[k],也可以d.k
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    # 允许动态设置key的值,不仅仅可以d[k],也可以d.k
    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        # 在所有映射中迭代
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append("?")
            args.append(getattr(self, k, None))
        sql = "insert into %s (%s) values (%s)" % (self.__table__, ",".join(fields), ",".join(params))
        print("SQL: %s" % sql)
        print("ARGS: %s" % str(args))


# 这样一个简单的ORM就写完了


# 下面实际操作一下,先定义个User类来对应数据库的表User
class User(Model):
    # 定义类的属性到列的映射
    id = IntegerField("id")
    name = StringField("username")
    email = StringField("email")
    password = StringField("password")


# 创建一个实例
u = User(id=12345, name="ReedSun", email="[email protected]", password="nicaicai")
u.save()

参考资料

  1. 使用元类-廖雪峰的官方网站
  2. 深刻理解python中的元类

出处:https://blog.csdn.net/weixin_35955795/article/details/52985170

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325815931&siteId=291194637