python的元类

1.类是如何产生的

Python扫描到class的语法的时候,就会调用type函数进行类的创建

2.如何使用type创建类

# 准备一个基类(父类)
class BaseClass:
    def talk(self):
        print("i am people")

# 准备一个方法
def say(self):
    print("hello")

# 使用type来创建User类
User = type("User", (BaseClass, ), {"name":"user", "say":say})

用户自定义类,只不过是type类的__call__运算符重载

class MyClass:
    data = 1


instance = MyClass()
print(MyClass, instance)  # 输出   (__main__.MyClass, < __main__.MyClass instance at 0x7fe4f0b00ab8 >)
print(instance.data)  # 输出:1


MyClass = type('MyClass', (), {'data': 1})
instance = MyClass()
print(MyClass, instance)  # 输出 (__main__.MyClass, < __main__.MyClass at 0x7fe4f0aea5d0 >)
print(instance.data)  # 输出:1

metaclass是type 的子类,通过替换type的__call__运算符重载机制,“超越变形”正常的类

class = type(classname, superclasses, attributedict) 
# 变为了
class = MyMeta(classname, superclasses, attributedict)

3. 理解什么是元类

  • 元类(metaclass)就是创建类的模板

  • type是Python在背后用来创建所有类的元类 连type自己也是由type自己创建的

  • metaclass: 超越变形特性 , 类是元类的实例,所以在创建一个普通类时,其实会走元类的 __new__ , 对普通类进行实例化时,实际是对一个元类的实例(也就是普通类)进行直接调用,所以会走进元类的 __call__

    class MetaSingleton(type):
        def __call__(cls, *args, **kwargs):
            print("cls:{}".format(cls.__name__))
            print("====1====")
            if not hasattr(cls, "_instance"):
                print("====2====")
                cls._instance = type.__call__(cls, *args, **kwargs)
            return cls._instance
    
    
    class User(metaclass=MetaSingleton):
        def __init__(self, *args, **kw):
            print("====3====")
            for k, v in kw:
                setattr(self, k, v)
    
    
    user = User("nb")
    

4.使用元类的意义

对类进行定制修改, 使用元类来动态生成元类的实例, 元类的作用就是创建API,一个最典型的应用是 Django ORM

import numbers


class Field:
    pass


class IntField(Field):
    def __init__(self, name):
        self.name = name
        self._value = None

    def __get__(self, instance, owner):
        return self._value

    def __set__(self, instance, value):
        if not isinstance(value, numbers.Integral):
            raise ValueError("int value need")
        self._value = value


class StrField(Field):
    def __init__(self, name):
        self.name = name
        self._value = None

    def __get__(self, instance, owner):
        return self._value

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError("string value need")
        self._value = value


class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs):
        print(name)
        if name == "BaseModel":
            # 第一次进入__new__是创建BaseModel类,name="BaseModel"
            # 第二次进入__new__是创建User类及其实例,name="User"
            return super().__new__(cls, name, bases, attrs)

        # 根据属性类型,取出字段
        fields = {k: v for k, v in attrs.items() if isinstance(v, Field)}

        # 如果User中有指定Meta信息,比如表名,就以此为准
        # 如果没有指定,就默认以 类名的小写 做为表名,比如User类,表名就是user
        _meta = attrs.get("Meta", None)
        db_table = name.lower()
        if _meta is not None:
            table = getattr(_meta, "db_table", None)
            if table is not None:
                db_table = table

        # 注意原来由User传递过来的各项参数attrs,最好原模原样的返回,
        # 如果不返回,有可能下面的数据描述符不起作用
        # 除此之外,我们可以往里面添加我们自定义的参数
        attrs["db_table"] = db_table
        attrs["fields"] = fields
        return super().__new__(cls, name, bases, attrs)


class BaseModel(metaclass=ModelMetaClass):
    def __init__(self, *args, **kw):
        for k, v in kw.items():
            # 这里执行赋值操作,会进行数据描述符的__set__逻辑
            setattr(self, k, v)
        return super().__init__()

    def save(self):
        db_columns = []
        db_values = []
        for column, value in self.fields.items():
            db_columns.append(str(column))
            db_values.append(str(getattr(self, column)))
        sql = "insert into {table} ({columns}) values({values})".format(
            table=self.db_table, columns=','.join(db_columns),
            values=','.join(db_values))
        print(sql)
        pass


class User(BaseModel):
    id = IntField('id')
    name = StrField('username')
    email = StrField('email')
    password = StrField('password')

    class Meta:
        db_table = "user"


if __name__ == '__main__':
    # 实例化成一条记录
    u = User(id=20230327, name="nb", email="[email protected]", password="abc123")

    # 保存这条记录
    u.save()

综上,元类的__new__和普通类的不一样:

  • 元类的__new__ 在创建类时就会进入,它可以获取到上层类的一切属性和方法,包括类名,魔法方法。
  • 而普通类的__new__ 在实例化时就会进入,它仅能获取到实例化时外界传入的属性

所有的Python的用户定义类,都是 type 这个类的实例

import yaml
from yaml import Loader

registry = {}


def add_constructor(target_class):
    registry[target_class.yaml_tag] = target_class


class Monster(yaml.YAMLObject):
    yaml_tag = u'!Monster'

    def __init__(self, name, hp, ac, attacks):
        self.name = name
        self.hp = hp
        self.ac = ac
        self.attacks = attacks

    def __repr__(self):
        return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % (
            self.__class__.__name__, self.name, self.hp, self.ac,
            self.attacks)


class YAMLObjectMetaclass(type):
    def __init__(cls, name, bases, kwds):
        super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
        if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
            cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)

    # 省略其余定义


class YAMLObject(metaclass=YAMLObjectMetaclass):
    yaml_loader = Loader
    yaml_tag = ''
    from_yaml = ''


class Monster2(YAMLObject):
    yaml_tag = u'!Monster'

    def __init__(self, name, hp, ac, attacks):
        self.name = name
        self.hp = hp
        self.ac = ac
        self.attacks = attacks

    def __repr__(self):
        return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % (
            self.__class__.__name__, self.name, self.hp, self.ac,
            self.attacks)


if __name__ == '__main__':
    # add_constructor(Monster)
    #
    # yaml.load_all("""
    # --- !Monster
    # name: Cave spider
    # hp: [2,6]    # 2d6
    # ac: 16
    # attacks: [BITE, HURT]
    # """)
    #
    # Monster(name='Cave spider', hp=[2, 6], ac=16, attacks=['BITE', 'HURT'])
    print(yaml.dump(Monster2(name='Cave lizard', hp=[3, 6], ac=16, attacks=['BITE', 'HURT'])))

你可能感兴趣的:(python,python,django,开发语言)