Python面向对象之元类

1. 元类

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。”

​ —— Python界的领袖 Tim Peters

1.1 什么是元类

  • 在python中一切皆对象,那么我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类,即元类可以简称为类的类

1.2 为什么用元类

  • 元类是负责产生类的,所以我们学习元类或者自定义元类的目的:是为了控制类的产生过程,还可以控制对象的产生过程

1.3 class关键字创建类的底层原理

1.3.1 内置函数exec(储备知识,与类的底层实现无关)

cmd = """
x=1
print('exec函数运行了')
def func(self):
    pass
"""
class_dic = {}
# 执行cmd中的代码,然后把产生的名字丢入class_dic字典中
exec(cmd, {}, class_dic)

1.3.2 class创建类的底层原理

  • 创建类的3个要素:类名,基类,类的名称空间
  • People = type(class_name,bases,dic)
class_name = 'People'  # 类名

class_bases = (object, )  # 基类

# 类的名称空间
class_dic = {}
class_body = """
country='China'
def __init__(self,name,age):
    self.name=name
    self.age=age
def eat(self):
    print('%s is eating' %self.name)
"""

exec(
    class_body,
    {},
    class_dic,
)

People = type(class_name, class_bases, class_dic)		# 用type产生People类的对象
p = People('zz', 18)		# 实例化对象
p.eat()		#调用对象方法

1.4 自定义元类控制类的创建

自定义一个元类,控制类产生,类的名称空间中必须有name字段才能创建成功,否则创建失败

class Mymeta(type):
    """自定义元类"""
    def __init__(self, name, bases, dic):
        super().__init__(name, bases, dic)
        # 判断创建的类是否包含name字段,如果没有就抛错
        if not dic.get('name'):
            raise Exception('必须有name字段')


class Person(metaclass=Mymeta):
    name = 'person'


print(Person.__dict__)
print(Person.name)

再例如,我们可以通过元类控制产生的类中属性名为全大写

class Mymeta(type):
     def __new__(cls, name, bases, dic):
         di = {}
         for key, value in dic.items():
             if not key.startswith('__'):
                 di[key.upper()] = value
         return type.__new__(cls, name, bases, di)


 class Person(metaclass=Mymeta):
    school = 'oldboy'
    name = 'zz'


print(Person.__dict__)

1.5 自定义元类控制类的调用过程

1.5.1 __call__

  • 要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法:__call__方法,该方法会在调用对象时自动触发
class Foo:
    def __call__(self, *args, **kwargs):
        print(args)
        print(kwargs)
        print('__call__实现了,实例化对象可以加括号调用了')


obj = Foo()
obj('zz', age=18)

1.5.2 __new__

我们之前说类实例化第一个调用的是__init__,但__init__其实不是实例化一个类的时候第一个被调用 的方法。当使用 Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法 其实是 __new__ 方法。

__new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。

注意:new() 函数只能用于从object继承的新式类。

class A:
    pass


class B(A):
    def __new__(cls):
        print("__new__方法被执行")
        return cls.__new__(cls)

    def __init__(self):
        print("__init__方法被执行")


b = B()

1.5.3 自定义元类控制类的调用过程(对象的产生)

自定义一个元类,定义一个类, 继承字典,使其具备点取值和赋值功能,通过元类控制对象的产生,把所有对象的属性都放到attr字典中,属性全删除


class Mymeta(type):
    def __call__(self, *args, **kwargs):
        obj = self.__new__(self)
        obj.__init__(*args, **kwargs)
        obj.__dict__['attr'] = kwargs
        return obj


class Mydict(dict, metaclass=Mymeta):
    def __getattr__(self, item):
        return self.__dict__['attr'][item]

    def __setattr__(self, key, value):
        self[key] = value
        self.__dict__['attr'][key] = value


di = Mydict(name='pp', age=68)
print(di)
print(di.__dict__)
print(di.name)
di.height = 180
print(di)

1.6 自定义元类后类的继承顺序

结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???

在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象OldboyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object

class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    n = 444

    def __call__(self, *args,
                 **kwargs):  #self=<class '__main__.OldboyTeacher'>
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        return obj


class Bar(object):
    n = 333


class Foo(Bar):
    n = 222


class OldboyTeacher(Foo, metaclass=Mymeta):
    n = 111

    school = 'oldboy'

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

    def say(self):
        print('%s says welcome to the oldboy to learn Python' % self.name)


print(
    OldboyTeacher.n
)  # 自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为OldboyTeacher->Foo->Bar->object->Mymeta->type
  • 查找顺序:
    1. 先对象层:OldoyTeacher->Foo->Bar->object
    2. 然后元类层:Mymeta->type

依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__的查找

class Mymeta(type):
    n = 444

    def __call__(self, *args,
                 **kwargs):  #self=<class '__main__.OldboyTeacher'>
        obj = self.__new__(self)
        print(self.__new__ is object.__new__)  #True


class Bar(object):
    n = 333

    # def __new__(cls, *args, **kwargs):
    #     print('Bar.__new__')


class Foo(Bar):
    n = 222

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


class OldboyTeacher(Foo, metaclass=Mymeta):
    n = 111

    school = 'oldboy'

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

    def say(self):
        print('%s says welcome to the oldboy to learn Python' % self.name)

    # def __new__(cls, *args, **kwargs):
    #     print('OldboyTeacher.__new__')


OldboyTeacher('nick',
              18)  # 触发OldboyTeacher的类中的__call__方法的执行,进而执行self.__new__开始查找

总结,Mymeta下的__call__里的self.__new__在OldboyTeacher、Foo、Bar里都没有找到__new__的情况下,会去找object里的__new__,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__

posted @ 2019-09-04 21:55  油饼er  阅读(364)  评论(0编辑  收藏  举报