Python元类(metaclass)

Python 是一种强大的编程语言,一部分得益于其语言设计中独特的元类(Metaclass)机制。尽管元类的概念在刚开始接触时可能会让人感到困惑,但一旦理解了它们的工作原理和应用方式,我们就可以用它们做出强大且灵活的抽象。

元类的定义

在 Python 中,一切皆对象,包括类本身。类定义了对象的行为,而元类则定义了类的行为。简而言之,元类就是创建类的“类”。

符合Python对象的条件是:

  1. 能够直接赋值给一个变量
  2. 可以添加到集合对象中
  3. 能作为函数参数进行传递
  4. 可以作为函数返回值

从这里,大家就可以看出,Python中一切皆为对象,一切都符合这些条件

Python对象都会有三个特征

  • 身份,即是存储地址,可以通过id()方法查询
  • 类型,即对象所属的类型,可以用type()方法来查询
  • 值,都会有各自的数据
class Job(object):
    pass


j = Job()
print(j) #<__main__.Job object at 0x10f4f9750>
print(id(j)) #4553611344
print(j.__class__) #
print(Job) #
print(id(Job)) #140637422913424
print(Job.__class__) #
print(type(Job)) #
print(Job.__base__) #
print(Job.mro()) #[, ]
print(id(Job.__class__)) #4373786024
print(type(Job.__class__)) #
print(type(int)) #
print(type.__base__) #

从上面的结果我们看看出来:

  • j是Job类的实例
  • j.__class__和Job是等价的,都表示Job类型
  • Job.__class__与type(Job)是等价的
  • 类型本身也是一个对象,也有id
  • Job作为对象,他的类型是type,或者说,Job是type的实例
  • Job作为类型,Job继承于object
  • 而类型type同时也是int等其他类型对象的类型,他也有自己的id,说明他也是一个对象,type的类型是type本身
  • type作为类型是object的子类

可以看出来,我们以前学习的所有的类型都是type类型的实例(对象),或者说,都是由type类型创建的类对象,我们把type类型称为元类。

type、class、object的关系如下:

Python元类(metaclass)_第1张图片

我们也可以自己构建元类,改变类的创建方式。

通过继承 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

print(type(int)) #
print(type(MyClass)) #
print(MyClass.__base__) #

上面的例子可以看到:

  • 已经将MyClass的类型改为MyMeta
  • MyClass的基类仍然是object
  • 自定义的元类中,添加了一个属性 attr,使用 MyMeta 作为元类,定义了一个类 MyClass,MyClass 确实拥有了 attr 属性。

正常的类都是由type创建出来的,但我们也可以创建自己的元类,让他继承自type,并设定某个类使用它.而这个类的子类也就同样是这个元类产生出的类.一个基本的元类如下:

class MetaClass(type):
    def __new__(cls,name,bases,namespace):
        #执行操作…
        #早于__init__方法执行,必须返回空对象,由于该方法是调用类后第一个运行的方法,此时并没有对象产生,因此该方法的第一个参数必须是类本身(MyMeta),*args, **kwargs用来接收调用元类产生对象所需的参数(类名 类的基类 名称空间)
        #其实下面那行也可以改成return type(name,bases,namespace),但这样保持了代码的一致性.
        return super().__new__(cls,name,bases,namespace)

    @classmethod
    def __prepare__(cls,name,bases,**kwargs):
        return super().__prepare__(name,bases,**kwargs)
    
    #通过__init__控制类的产生,调用自定义元类与调用内置元类type的方式相同,需要传入类名、类的父类们、类体代码的名称空间,__init__方法中第一个参数来自于__new__方法产生的空对象。
    def __init__(self,name,bases,namespace,**kwargs):
        #执行操作,注意这里虽然是self,但其实就是指我们的类,因为这个类的实例是类
        return super().__init__(name,bases,namespace,**kwargs)

    def __call__(self,*args,**kwargs):
        return super().__call__(*args,**kwargs)

只要调用类,就会依次调用类中的__new__方法和__init__方法;
__new__方法返回一个空对象,就类似一张白纸;
__init__获取__new__方法中产生的白纸在上面画不同的图案。

使用元类创建新类型

使用type()创建新类型

除通过直接定义外,也可以通过type()来定义一个新类型,语法为:

type(clsname, bases, dict, **kwds) -> a new type

其中:

  • clsname:为要创建的新类
  • bases:以元组的形式,声明父类
  • dict:以字典的形式声明类的属性

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函数")

实际上,解释器在解析类的声明后,也是采用type的方式创建的类对象。

使用type创建新类型往往应用在要创建许多类型的创建中,比如我们再ORM场景中,需要将许多表映射为对象类型,这个时候就可以使用该功能,自动化的创建所有表对象类型,并且能为这些表对象类型提供公共方法。

函数做元类

使用函数也可以作为元类,实现一个将类里面的所有属性名称转换为大写:

# 元类会自动获取通常传给`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)

类作为元类

类做元类:

class Meta(type):

    def __new__(cls, name, bases, attrs):
        print("执行了元类 Meta!")
        attrs['author'] = "xiaoyang-sir"
        spam_class = super().__new__(cls, name, bases, attrs)
        print(spam_class)
        return spam_class



class Spam(metaclass=Meta):

    def __init__(self, website):
        self.website = website

        
print(Spam.author)


"""
Out:
    执行了元类 Meta!
    
    xiaoyang-sir

"""

元类的使用场景

尽管元类是非常强大的工具,但它也是非常复杂的工具,所以应该在需要的时候才使用。以下是一些元类的典型应用场景:

  • 自动添加属性或方法:如果你希望所有类都具有某些属性或方法,可以使用元类自动添加。
  • 类的注册:如果你希望在创建类时做一些事情,如注册类,可以使用元类。
  • 强制 API 一致性:如果你正在构建一个框架或库,并希望用户定义的类遵循特定的规则(例如必须有某些方法或属性),则可以使用元类来强制执行这些规则。

单例

所谓单例,就是这个类型的实例对象有且只能有一个,不能使用对象声明或其他方式创建该类的多个实例,可以通过元类实现单例:

class SingleMeta(type):
    def __init__(self, *args, **kwargs):
        print('SingleMeta.__init__ ')
        super().__init__(*args, **kwargs)
        self.instance = None


    def __new__(cls, *args, **kwargs):
        print('SingleMeta.__new__')
        new_obj = super().__new__(cls, *args, **kwargs)
        return new_obj

    def __call__(self, *args, **kwargs):
        print('SingleMeta.__call__ ')
        if not self.instance:
            self.instance = self.__new__(self)
        self.__init__(self.instance, *args, **kwargs)
        return self.instance



class Foo(metaclass=SingleMeta):
    def __init__(self, *args, **kwargs):
        print('Foo.__init__ ')
        self.__name = kwargs['name']

    def __new__(cls, *args, **kwargs):
        print('Foo.__new__ ')
        return super().__new__(cls)

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, value):
        self.__name = value


foo1 = Foo(name='python')
foo2 = Foo(name='java')

print(id(foo1))
print(id(foo2))

‘’'
SingleMeta.__new__
SingleMeta.__init__ 
SingleMeta.__call__ 
Foo.__new__ 
Foo.__init__ 
SingleMeta.__call__ 
Foo.__init__ 
4391594832
4391594832
‘''

meta.__call__()方法的调用是在实例对象__new__()、__init__()之前执行。

ORM中的应用

class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)


class IntegerField(Field):
    def __init__(self, name, column_type='int(11)'):
        super(IntegerField, self).__init__(name, column_type)


class StringField(Field):
    def __init__(self, name, column_type='varchar(100)'):
        super(StringField, self).__init__(name, column_type)


class ModelMetaClass(type):
    def __new__(cls, class_name, class_parents, class_attr):
        if class_name == 'Model':
            return type.__new__(cls, class_name, class_parents, class_attr)
        print('found model %s' % class_name)
        mappings = {}
        for name, value in class_attr.items():
            if isinstance(value, Field):
                mappings[name] = value
        for k in mappings.keys():
            class_attr.pop(k)
        class_attr['__mappings__'] = mappings
        class_attr['__table__'] = class_name.lower()
        return type.__new__(cls, class_name, class_parents, class_attr)


class Model(dict, metaclass=ModelMetaClass):

    def __init__(self, **kwargs):
        super(Model, self).__init__(**kwargs)

    def __getattr__(self, key):
        try:
            return str(self[key])
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = str(value)

    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            print(k, v, v.name, v.column_type)
            fields.append(v.name)
            args.append(getattr(self, k))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))


class User(Model):
    id = IntegerField('id')
    name = StringField('name')
    age = IntegerField('age')
    address = StringField('address')


user = User(id='1', name='daocoder', age=27, address='anhui')
user.save()

User类定义继承自父类Model,且有4个属性,4个属性分别继承自IntegerField和StringField,这两个继承自Field,这个不谈。聚焦Model。

1、实例化User时,去找父类Model,发现父类拥有metaclass属性值为ModelMetaClass,即它是由一个自定义的元类来创建的类,向上寻找ModelMetaClass,这个类是继承自type。需要先创建它的实例对象。调用其静态方法new,这里面4个参数(cls, class_name, class_parents, class_attr),分别为ModelMetaClass的类对象、Model类名、父类(dict, )元组、自身内置属性。类名为Model时,直接创建type.__new__(cls, class_name, class_parents, class_attr)并返回。再调用Model类的init方法,调用了父类dict的init的方法。父类Model作为类对象创建完成。

2、开始User类对象的创建,Model已有,然后开始创建User,还是向上找到了ModelMetaClass,这时的4个参数分别是(cls, class_name, class_parents, class_attr),分别为ModelMetaClass的类对象、User类名、父类(Model, )元组、自身内置属性包含id,name,age,address等。然后判断类名不是Model,继续向下,将User属性遍历,其实例自Field的属性封装为User类对象的mappings属性,类名User为User类对象的table属性。

你可能感兴趣的:(python,javascript)