VB.net 2010 视频教程 VB.net 2010 视频教程 VB.net 2010 视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Python >
  • python基础教程之面向对象之元类(metaclass)

  • 2019-05-20 21:18 来源:未知

一、前言:

要搞懂元类必须要搞清楚下面几件事:

  • 类创建的时候,内部过程是什么样的,也就是我们定义类class 类名()的过程底层都干了些啥

  • 类的调用即类的实例化过程的了解与分析

  • 我们已经知道元类存在的情况下的属性查找新顺序分析

1、先来搞清楚我们创建一个类的过程:

复制代码
class Newclass():   # class定义一个类,类名为:Newclass
# 下面的代码都是类体代码,也就是类的属性们
    def __init__(self, name, age):
        self.name = name
        self.age = age
    coutry = 'China'
    def task(self):
        print('%s is sleeping' % self.name)
复制代码

  当我们用class声明要创建一个类的时候,实际上内部流程是这样的(以上面的为例):

  ①.定义类名Newclass:class_name = 'Newcalss'

  ②.设定这个类Newclass的父类(基类)们(一个类可以继承多个类):class_bases = (object,) 不设定默认继承object

  ③.执行类体代码,拿到类的名称空间:class_dic = {...} (这里的字典就是我们前面学习类的时候查看类里面的属性方法.__dict__的内容。

2、我们调用创建的类Newclass(也就是实例化对象)的过程:

t1 = Newclass('sgt', 30)  # 调用类,实例化t1这个对象

通过调用类Newclass实例化出对象t1时,会发生以下三件事:

  ①.先产生一个空的对象obj

  ②.调用__init__方法,对对象obj进行初始化,将默认属性丢进空对象对应的名称空间中

  ③.将初始化的对象obj返回,t1,就是返回结果的接受者,也就是说t1就是实例化出来的一个对象

二、开始认识元类

1、类产生的过程分析:

  还是先定义一个类来分析分析:

复制代码
class Newclass():  
    def __init__(self, name, age):
        self.name = name
        self.age = age

    coutry = 'China'

    def task(self):
        print('%s is sleeping' % self.name)
t1 = Newclass('sgt', 30) 
复制代码

  首先,所有的对象都是实例化或者说调用类而得到的(调用类的过程称之为实例化对象),比如对象t1是调用类Newclass得到的

  如果一切皆对象,那么Newclass本质也可以看成一个对象,既然所有的对象都是调用类得到的,那么是不是可以大胆的想象我们声明class创建一个类Newclass的时候,是否也是调用一个更高级的类得到的呢?

  事实就是我们想的那样,这个‘实例化类的类’就是我们今需要好好了解的‘元类’。

  于是我们可以大致有这么过程:产生Newclass的过程一定发生了:Newclass = 元类(...)

复制代码
# 我们来分别打印一下实例化出的对象t1和创建的类Newclass的类型:
print(type(t1))
print(type(Newclass))
# 结果是:
# <class '__main__.Newclass'>   
# <class 'type'>      
# 我们可以推出:                   
# t1是调用Newclass产生的
# Newclass是调用type产生的
复制代码

  一开始我们就提前了解了class出一个类Newclass时候发生的过程,这里再补充一下,如果我们不用class创建类的另外一种原始方法:

复制代码
# 引入函数exec(object, globals, locals)
exec用法:
object:类体代码,包含一系列python代码的字符串
globals:全局作用域(字典形式),如果不指定,默认为globals()
locals:局部名称空间(类的名称空间)
可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中

# 不依赖class关键字创建一个自定义类
类名class_name= 'Newcalss'
继承的类们:class_bases = (object,)
类体代码:class_body = '''
def __init__(self, name, age):
    self.name = name
    self.age = age
coutry = 'China'
def task(self):
    print('%s is sleeping' % self.name)

''' exec(class_body,{},class_dic) 

# 创建一个名称空间,将类体代码放入class_dict中,这个class_dict就代表exec创建的名称空间 
# 调用type得到自定义的类: 
Newclass = type(class_name, class_bases, class_dict)
复制代码

2、自定义元类,控制类Newclass的创建过程

  既然知道了类的创建过程,那么我们就可以自定义元类来控制类的创建

  首先:一个元类如果没有声明自己的元类,默认它的元类就是type,出了使用内置元类type,我们也可以通过集成type来自定义元类,然后使用metaclass关键字参数为一个类指定元类

复制代码
class MyMeta(type):  # 只有继承了type的类才能称之为一个元类,否则就是普通的类
    pass

class Newclass(object,metaclass=MyMeta):  # 继承基类object,为Newclass指定元类为MyMeta
    def __init__(self, name, age):
        self.name = name
        self.age = age

    coutry = 'China'

    def task(self):
        print('%s is sleeping' % self.name)
复制代码

  然后:自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即:

  Newclass = Mymeta(‘Newclass’,(object,),{.....}),

  一开始我们先预习了,调用类是发生的过程:同理调用MyMeta会先产生一个空对象Newclass,然后连同MyMeta括号内的参数一同传给MyMeta下的__init__方法,完成初始化,所以我们可以在这个过程中做一下事情:

复制代码
class MyMeta(type):  # 只有继承了type的类才能称之为一个元类,否则就是普通的类
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)

        if class_name.islower():    #  给类名做限制,必须为驼峰体,否则抛异常
            raise TypeError('类名必须为驼峰体')
        # 给类中注释的存在性加以限制,必须要有注释且不能为空,否则抛异常
        if '__doc__' not in class_dict or len(class_dict['__doc__'].strip(' \n')) == 0:
            raise TypeError('类必须要求有文档注释,且不能为空')

class Newclass(object,metaclass=MyMeta):  # 继承基类object,为Newclass指定元类为MyMeta
    '''
    这是Newclass类的注释
    '''
    def __init__(self, name, age):
        self.name = name
        self.age = age

    coutry = 'China'

    def task(self):
        print('%s is sleeping' % self.name)
复制代码

3、自定义元类,控制类Newclass的调用过程

  一开始提过:调用类就是实例化对象,那么调用这个行为,就必须要知道__call__这个知识点,所以先来说说__call__:

复制代码
class Newclass():
    coutry = 'China'
    def task(self):
        print('%s is sleeping' % self.name)

    def __call__(self, *args, **kwargs):
        print('__call__被调用了>>>', self)
        print('__call__被调用了>>>', args)
        print('__call__被调用了>>>', kwargs)
t1 = Newclass()

## 要想让t1这个对象可调用,需要在该对象的类中定义一个__call__方法,
#       该方法会在对象t1在调用时候自动触发
## 调用t1的返回值就是__call__方法的返回值
res = t1('a', 'b', 8, name = 'jason', age = 18)

# 右键运行结果:
__call__被调用了>>> <__main__.Newclass object at 0x000001AD69359828>  
__call__被调用了>>> ('a', 'b', 8)
__call__被调用了>>> {'name': 'jason', 'age': 18}
复制代码

  由上面的例子得知,调用一个对象,就会触发对象所在类中的__call__方法的执行,所以我们在调用类Newclass(这里将类也可以看成对象)实例化对象时候,也应该在类Newclass的元类中必然存在一个__call__方法。

复制代码
class MyMeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)

    def __call__(self, *args, **kwargs):
        print(self)    # <class '__main__.Newclass'>
        print(args)    # ('sgt', 18)
        print(kwargs)  # {}
        return 123

class Newclass(object,metaclass=MyMeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    coutry = 'China'
    def task(self):
        print('%s is sleeping' % self.name)

t1 = Newclass('sgt', 18)
print(t1)  # 123
复制代码

  通过上面例子可以总结出:

  • 调用Newclass就是在调用Newclass类中的__call__方法,(Newclass类中没有按照属性查找去基类中找)
  • 触发__call__方法后会将Newclass传给self,溢出的位置参数传给*,溢出的关键字参数传给**
  • 调用Newclass的返回值就是触发__call__方法函数的返回值,这里通过打印t1得出结果123可以得出。

   好了,我们在来回顾下,实例化对象t1的过程: 

  1. 先产生一个空的对象obj
  2. 调用__init__方法,对对象obj进行初始化,将默认属性丢进空对象对应的名称空间中
  3. 将初始化的对象obj返回,t1,就是返回结果的接受者,也就是说t1就是实例化出来的一个对象

  所以,对应的Newclass在实例化出对象t1时候也应该做上面三件事:

复制代码
class MyMeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)

    def __call__(self, *args, **kwargs):
        # 1 调用__new__产生一个空对象obj:
        obj = self.__new__(self)  # __new__会产生空对象的名称空间
        # 这里self.__new__是通过self(类Newclass)来调用__new__方法,通过属性查找默认在基类object中
        # 括号里self的意思是创建类Newclass的对象的名称空间。
        # 2 调用__init__初始化空对象obj
        self.__init__(obj, *args, **kwargs) # 注意这里的第一个参数是obj,因为我们初始化的是我么创建的空对象
        # 3 返回初始化好的对象obj
        return obj
class Newclass(object,metaclass=MyMeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    coutry = 'China'
    def task(self):
        print('%s is sleeping' % self.name)

t1 = Newclass('sgt', 18)   # 实例化对象
print(t1.__dict__)  # {'name': 'sgt', 'age': 18}  # 查看实例化对象的结果
复制代码

  上面就是我么通过调用类,实例化对象的过程元类中的__call__做的事情,既然知道了这个过程,我么也能自定义私人的元类,那么我们就可以从基础上改写__call__方法来控制Newclass类调用的过程,比如将Newclass类实例化的对象的属性变成我们想要的结果。

复制代码
class MyMeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)

    def __call__(self, *args, **kwargs):
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)  # 在初始化完的对象返回之前进行修改
        res = {k.upper(): v for k, v in obj.__dict__.items()}  # 将对象的属性名大写
        return res

class Newclass(object,metaclass=MyMeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    coutry = 'China'
    def task(self):
        print('%s is sleeping' % self.name)

t1 = Newclass('sgt', 18)
print(t1)  # {'NAME': 'sgt', 'AGE': 18}
复制代码

4、在知道元类存在的情况下的属性查找顺序

 

猿人学Python一个Python教程和Python爬虫教程的网站。
相关教程