Python知识

  • 元类
  • class机制分析
  • 自定义元类
  • __new__方法
  • __call__方法


元类

Python中有这么一句话,一切皆为对象,那么看下面的代码:

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

    def say(self):
        print('<%s:%s>' % (self.name, self.age))


obj = People('egon', 18)

obj是通过类People产生的对象,一切皆为对象,那么产生对象obj的类People也是对象,既然所有的对象都是调用类得到的,那么必然类People也是调用了一个类得到的,这个类称为元类。

元类就是实例化产生类的类。

关系:
元类–>(实例化)–>类People–>(实例化)–>对象obj

那元类到底是什么类?平时查看对象的类型都是用type方法查看,同里查看类的类也是使用type

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

    def say(self):
        print('<%s:%s>' % (self.name, self.age))


obj = People('egon', 18)
print(type(obj))
pirnt(type(People))

'''
<class '__main__.People'>
<class 'type'>
'''

type是内置的元类,用class关键字定义的所有类以及内置的类(例如:int)都是由内置的元类type来实例化产生

class机制分析

类的三大特征:类名,类的基类,执行类体代码拿到类的名称空间

class机制创造类的四个步骤

  1. 类名 class name ='People'
  2. 类的基类 class_bases =(object)
  3. 执行类体代码拿到类的名称空间
class_name = 'People'
class_bases = (object,)
class_dic = {}
class_body = '''
def __init__(self, name, age):
    self.name = name
    self.age = age

def say(self):
    print('<%s:%s>' % (self.name, self.age))
'''
exec(class_body, {}, class_dic)

print(class_dic)
'''
{'__init__': <function __init__ at 0x0000020B415AF040>, 
'say': <function say at 0x0000020B41A59430>}
'''
  1. 调用元类
People = type(class_name, class_bases, class_dic)

print(type(People))

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

此时,一个类就创造出来了,并且没有使用class机制。

obj = People('egon',18)
print(obj.__dict__)
'''
{'name': 'egon', 'age': 18}
'''

弄懂这个class机制为的是什么?
前三步都是可以在class机制下修改,而第四步调用元类就不同了,一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类。比如说,就想让类的命名方式是首字母大写,不大写就报错,如果使用内置的元类进行调用,无法进行控制,就需要自定义元类。

自定义元类

如何自定义元类?
调用Mymeta发生三件事:

  1. 先造一个空对象==》People
  2. 调用Mymeta这个类内的__init__方法,完成初始化对象的操作
  3. 返回初始化好的对象

metaclass关键字参数为一个类指定元类
如果直接运行会直接报错:

class Mymeta(type):  # 只有继承了type类的类才是元类
    def __init__(self):
        print('run')


class People(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print('<%s:%s>' % (self.name, self.age))


'''
TypeError: __init__() takes 1 positional argument but 4 were given
'''

在上述的class机制中,第四步调用元类,People = type(class_name, class_bases, class_dic) 就会调用Mymeta中的init方法,init会传入四个参数,默认传入对象本身class_name,class_bases,class_dic,所以就显示传入了四个值。
解决方法:

class Mymeta(type):  
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise NameError('类名首字母为大写')



class people(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print('<%s:%s>' % (self.name, self.age))
'''
  File "D:\python, line 37, in __init__
    raise NameError('类名首字母为大写')
NameError: 类名首字母为大写

'''

__new__方法

在上述提到产生新的对象的三个步骤中,只是对第二步的init方法进行了控制,而__new__方法是在第一步,返回了一个对象给到init方法,init才会完成对象的初始化操作。
何以证明?

没有预先设置__new__方法时,底层会有默认的__new__功能:

class Mymeta(type): 
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise NameError('类名首字母为大写')
        print('run2')

class People(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print('<%s:%s>' % (self.name, self.age))
'''
run2
'''

预先设置了__new__方法,但是并没有返回一个对象

class Mymeta(type):  # 只有继承了type类的类才是元类
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise NameError('类名首字母为大写')
        print('run2')

    def __new__(cls, *args, **kwargs):
        print('run1')


class People(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print('<%s:%s>' % (self.name, self.age))

'''
run1
'''

预先设置了__new__方法,也有返回一个对象

lass Mymeta(type):  
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise NameError('类名首字母为大写')
        print('run2')

    def __new__(cls, *args, **kwargs):
        print('run1')
        return super().__new__(cls, *args, **kwargs)  #从type中调用了__new__方法
       #return type.__new__(cls, *args, **kwargs)   #第二种返回对象的方法


class People(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print('<%s:%s>' % (self.name, self.age))
'''
run1
run2
'''

__call__方法

通过上述的结果得知,在调用Mymeta时,Mymeta会将参数先传给__new__方法,再传给__init__方法,但是为什么是这种顺序?并且调用Mymeta类的时候的第三步是返回一个已经初始化好的对象,而在第二步__init__方法,并没有返回任何的对象,那么这个初始化好的对象是谁返回的?

__call__的作用:
如果想让对象加括号调用,就可以加入一个call方法

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

    def say(self):
        print('<%s:%s>' % (self.name, self.age))

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


obj = People('egon', 18)
obj(1, 2, 5, 6, a=3, b=7)


'''
<__main__.People object at 0x000002CAEF800FD0> (1, 2, 5, 6) {'a': 3, 'b': 7}
'''

回头一想,在obj=People('egon',18)中,People也是对象,也是加了括号调用,所以在People的元类也有__call__方法。

对象()——>类内的__call__
类()——>自定义元类内的__call__
自定义元类()——>内置元类__call__

class Mymeta(type):  # 只有继承了type类的类才是元类
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise NameError('类名首字母为大写')
        print('run2')

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

    def __call__(self, *args, **kwargs):
        # 1、Mymeta.__call__函数内调用People内的__new__
        people_obj = self.__new__(self)
        # 2、Mymeta.__call__函数内调用People内的__init__
        self.__init__(people_obj, *args, **kwargs)
        # 3、Mymeta.__call__函数内返回一个初始化好的对象
        return people_obj


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

    def say(self):
        print('<%s:%s>' % (self.name, self.age))

    def __new__(cls, *args, **kwargs):
    #产生真正的对象
        return object.__new__(cls)


obj = People('egon', 18)
print(obj.__dict__)