元类到底是什么东东?

523 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

前言

最近在看Python的面向对象编程,卡在了元类这个知识点,经过各种资料查询和学习,就有了这篇文章,还是那句话,能力时间有限,如果有错误,还望批评指正,谢谢。

元类概念

其实元类的概念很简单。

生成类的类就是元类。

我们都知道,对象是由类实例化来的,如下。

class Foo:
    pass


foo = Foo()
print(foo)

<__main__.Foo object at 0x7fd9280ef250>

那我们思考下类又是谁产生的了,这时候我们就借助type函数,一看究竟。

class Foo:
    pass


foo = Foo()
print(type(foo))
print(type(Foo))

<class '__main__.Foo'>
<class 'type'>

我们发现Foo类的类型是type,我们再来看看python自带的数据类型是谁产生的。

print(type(int))
print(type(str))

<class 'type'>
<class 'type'>

我们可以发现,也是type,所以type就是元类。type元类实例化类(类对象),类再实例化对象,所以说Python万物皆对象,不管是用户自定义的类还是Python自带的类都是有type实例化来的。

type创建类

我们通常定义类都是使用class关键字。

class Foo:

    i = 1

    def test(self):
        print('test')

元类是实例化类的,那type元类就应该可以直接实例化类,其语法为:

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


def test(self):
    print('test')


type('Foo', (), {'i': i, 'test': test})

自定义元类

我们也可以自定义元类,我们需要做的就是在自定义元类中继承type类,然后在生成的类中用metaclass指定自定义元类。

class MyType(type):
    pass

class Foo(metaclass=MyType):
    pass

print(type(Foo))

<class '__main__.MyType'>

自定义元类的作用就是可以实现一些功能,应用于实例化类中,这样可以使实例化的类都具有相同的功能。在正式写一个自定义元类前,有必要讲解__new__、__init__和__call__魔术方法,只有了解和使用这些方法,才能让自定义元类实现我们想要的功能。

__new__魔术方法

介绍这些魔术方法的时候,我都会从普通类和自定义元类两个不同角度出发来讲解。

首先在普通类中,new魔术方法是构造方法,用于实例化对象。

class Foo:

    a = 1

    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)
        print(object.__new__(cls))
        return object.__new__(cls)

    def test(self):
        print('test')


foo = Foo()

<class '__main__.Foo'>
()
{}
<__main__.Foo object at 0x7f96380db160>
  • 触发时间:实例化对象时
  • 作用:实例化对象
  • 参数:cls为当前类,args,kwargs为初始化的参数。
  • 返回值:实例化的对象

在自定义元类中,new魔术方法就是来构造类(类对象)的了,其参数和type函数构造类是一样的。

class MyType(type):

    def __new__(mcs, name, bases, dicts):
        print(mcs)
        print(name)
        print(bases)
        print(dicts)
        print(super().__new__(mcs, name, bases, dicts))
        return super().__new__(mcs, name, bases, dicts)


class Foo(metaclass=MyType):
    a = 1

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

    def test(self):
        print('test')


foo = Foo()

<class '__main__.MyType'>
Foo
()
{'__module__': '__main__', '__qualname__': 'Foo', 'a': 1, '__new__': <function Foo.__new__ at 0x7fca60176790>, 'test': <function Foo.test at 0x7fca60176820>}
<class '__main__.Foo'>
  • 触发时间:实例化类时。
  • 作用:实例化类。
  • 参数:mcs为当前元类,name为实例化类名,bases为实例化类的继承类名,dicts为实例化类的属性和方法。
  • 返回值:实例化的类。

通过new魔术方法其实就可以实现很多自定义的功能了,例如我想让实例化的类的属性都变成大写。

class MyType(type):

    def __new__(mcs, name, bases, dicts):
        attrs = ((name, value) for name, value in dicts.items() if not name.startswith('__'))
        dicts = dict((name.upper(), value) for name, value in attrs)
        return super().__new__(mcs, name, bases, dicts)


class Foo(metaclass=MyType):
    a = 1

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

    def test(self):
        print('test')


print(Foo.__dict__)

{'A': 1, 'TEST': <function Foo.test at 0x7fb0580b6820>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
__init__魔术方法

在普通类中,就是初始化方法,self为类实例化的对象,这个很好理解,毕竟这个函数我们接触的很多。

class Foo:

    a = 1

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

    def test(self):
        print('test')


foo = Foo('li')
print(foo.name)

li

在自定义元类中,那就是初始化类对象。

class MyType(type):

    def __new__(mcs, name, bases, dicts):
        return super().__new__(mcs, name, bases, dicts)

    def __init__(cls, name, bases, dicts):
        print(cls)
        super().__init__(name, bases, dicts)


class Foo(metaclass=MyType):
    a = 1

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

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

    def test(self):
        print('test')

<class '__main__.Foo'>
__call__魔术方法

在普通类中,call方法会在实例化对象加括号时触发。

class Foo:

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

    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)


foo = Foo('li')
foo()

<__main__.Foo object at 0x7fbd2806f250>
()
{}

在自定义元类中,我们知道类是由元类生成,那类加括号就会触发call方法。

Foo = MyType()
Foo() 相当于 MyType()()
class MyType(type):

    def __new__(mcs, name, bases, dicts):
        return super().__new__(mcs, name, bases, dicts)

    def __init__(cls, name, bases, dicts):
        print(cls)
        super().__init__(name, bases, dicts)

    def __call__(cls, *args, **kwargs):
        # obj = cls.__new__(cls)
        # cls.__init__(obj, *args, **kwargs)
        return type.__call__(cls, *args, **kwargs)



class Foo(metaclass=MyType):
    a = 1

    def __new__(cls, *args, **kwargs):
        print('foo new')
        return object.__new__(cls)

    def __init__(self, name):
        print('foo init')
        self.name = name

    def test(self):
        print('test')


foo = Foo('li')
print(foo.__dict__)

<class '__main__.Foo'>
foo new
foo init
{'name': 'li'}

我们可以看出,调用type的call函数,其实和手动调动类的new和init方法是一样的结果。

单例模式

最后,我们来用元类实现单例模式。单例模式就是实例化的对象只有一个,简单的说,就是如果被实例化过了就返回该实例,这样就只会有一个实例。

class MyType(type):

    def __init__(cls, name, bases, dicts):
        print('init')
        cls.__instance = None
        super().__init__(name, bases, dicts)

    def __call__(cls, *args, **kwargs):
        print('call')
        if cls.__instance is None:
            cls.__instance = type.__call__(cls, *args, **kwargs)
        return cls.__instance


class Foo(metaclass=MyType):
    pass


foo1 = Foo()
foo2 = Foo()
print(id(foo1), id(foo2))

init
call
call
140402884588256 140402884588256

今天的分享就到这了,我们下期再见~