美文网首页
Python中的元类

Python中的元类

作者: 莫辜负自己的一世韶光 | 来源:发表于2019-03-08 20:04 被阅读0次

    学懂python的元类,只要记住两句话:

    • 道生一,一生二,二生三,三生万物
    • 我是谁,我从哪里来,我要到哪里去?

    在python的世界里,一切皆是对象,而所有的对象的源头都是type,type其实是一个类,只是用了小写的形式.
    在python的世界里,type就是,python中所的一切都是由type创造出来的.

    道生一,一生二,二生三,三生万物

    • 1.道 -> 即使type类
    • 2.一 -> 就是元类(metaclass)或者说是类生成器
    • 3.二 -> Class(类,或者叫实例生成器)
    • 4.三 -> 实例(instance)
    • 5.万物 -> 即是实例的各种属性和方法.

    下面用Hello World来说明下以上的内容

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/8 16:31'
    
    # 创建一个Hello类,拥有属性say_hello -- 二的起源(类的起源)
    class Hello(object):
        def say_hello(self,name='world'):
            print('Hello,{}'.format(name))
    
    # 从Hello类创建一个实例hello -->  二生三
    hello = Hello()
    
    # 使用实例hello调用方法say_hello  --> 三生万物
    hello.say_hello()
    

    这是一个标准的二生三,三生万物的过程.

    下面研究一下这个二是怎么来的?其实上述代码只是一种语义化的简称,Python的解释器会翻译以上的代码

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/8 16:37'
    
    def fn(self,name='world'):
        print('Hello,{}'.format(name))
    
    Hello = type('Hello',(object,),(dict(say_hello=fn)))
    
    hello = Hello()
    hello.say_hello()
    

    这样的写法和刚才的写法的效果完全一样,你可以尝试一下

    我们回头看下最精彩的部分:道直接生出了二

    Hello = type('Hello', (object,), dict(say_hello=fn))
    

    这就是“道”,python世界的起源,你可以为此而惊叹。
    注意它的三个参数!暗合人类的三大永恒命题:我是谁,我从哪里来,我要到哪里去。

    • 第一个参数: 我是谁? 类的名称,区分和其他类的不同,Hello
    • 第二个参数: 我从哪里来,我继承自谁,这里会传入我的父类是谁?
    • 第三个参数: 我要到哪里去?这里将属性和参数当成一个字典传入.创建字典的方式,可以使用dict()的方式

    值得注意的是,三大永恒的命题,是一切的类,一切的实例,甚至一切的属性和方法都具有的,毕竟这些都是继承过来的.
    但是平常,一般不是用这种方式传入的,而是用下面这种方式传入的.

    Class Hello(object):
              def say_hello(self):
                  pass
    # Class 后面是我是谁?
    # 类名后面()中放置的我从哪里来.
    # 下面的代码区域就是我要到哪里去
    
    • type可以创建一切的类,但是这样比较麻烦.道可以直接生出二,但是一般的它会先生出一,然后再由一创造二.
    • type可以直接生出类(class),也可以生出元类(metaclass),再使用元类批量定制类

    一般来说,元类均被命名后缀为Metalass。想象一下,我们需要一个可以自动打招呼的元类,它里面的类方法呢,有时需要say_Hello,有时需要say_Hi,有时又需要say_Sayolala,有时需要say_Nihao。

    如果每个内置的say_xxx都需要在类里面声明一次,那将是多么可怕的苦役! 不如使用元类来解决问题。

    以下是创建一个专门“打招呼”用的元类代码:
    元类的创建

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/8 16:50'
    
    
    class SayMetaClass(type):
        def __new__(cls, name, bases, attrs):
            attrs['say_' + name] = \
                lambda self, value, saying=name: print(saying + ',' + value + '!')
            return type.__new__(cls, name, bases, attrs)
    

    记住两点:

    • 元类是由“type”衍生而出,所以父类需要传入type。【道生一,所以一必须包含道】
    • 元类的操作都在 new中完成,它的第一个参数是将创建的类,之后的参数即是三大永恒命题:我是谁,我从哪里来,我将到哪里去。 它返回的对象也是三大永恒命题,接下来,这三个参数将一直陪伴我们。

    __new__中,我们只进行了一个操作,就是

    attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
    

    它根据类的名字,创建一个类方法.比如我们由元类创建的类叫'Hello',那创建时候,就自动有了一个叫'say_hello'的类方法,然后又将name值'Hello'作为默认参数saying,传递到方法里面.

    那么元类,该怎么使用呢?

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/8 17:33'
    # 定义元类
    # 道生一,传入type
    class SayMetaClass(type):
    
        def __new__(cls, name,bases,attrs):
            print('调用一次')
            # 创造天赋
            attrs['say_'+name] = lambda self,value,saying=name:print(saying+','+value+'!')
            return type.__new__(cls,name,bases,attrs)
    
    # 一生二: 创建类
    class Hello(object,metaclass=SayMetaClass):
        pass
    
    # 二生三:创建实例
    hello = Hello() # 注意元类中的__new__方法只会别调用一次.Hello()以后调用的是__call__方法
    hello1 = Hello()
    
    # 三生万物:调用实例方法
    hello.say_Hello('world!')
    hello1.say_Hello('baby')
    

    理解类也是一个对象

    理解元类之前,你必须了解,类也是对象.

    class ObjectCreator(object):
         pass
    

    将在内存中创建一个对象,名字就是ObjectCreator.这个对象(类)自身拥有创建对象(类实例)的能力,这就是它作为一个类的原因.但是它本质上也是一个对象,你可以将它赋值给一个变量,你可以拷贝它,你可以给它增加属性,你可以将它作为函数参数进行传递.

    print (ObjectCreator)     # 你可以打印一个类,因为它其实也是一个对象
    #输出:<class '__main__.ObjectCreator'>
    
    Idef echo(o):
         print (o)
    
    echo(ObjectCreator)                 # 你可以将类做为参数传给函数
    #输出:<class '__main__.ObjectCreator'>
    print (hasattr(ObjectCreator, 'new_attribute'))
    #输出:False
    
    ObjectCreator.new_attribute = 'foo' #  你可以为类增加属性
    print (hasattr(ObjectCreator, 'new_attribute'))
    #输出:True
    print (ObjectCreator.new_attribute)
    #输出:foo
    
    ObjectCreatorMirror = ObjectCreator # 你可以将类赋值给一个变量
    print (ObjectCreatorMirror())
    #输出:<__main__.ObjectCreator object at 0x108551310>
    

    动态的创建类

    1.通过return class动态的构建类
    因为类也是对象,你可以在运行的时候动态的创建它们,就像其他任何对象一样.首先,你可以在函数中创建一个类,然后通过return返回.

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/8 18:08'
    
    def create_class(name):
        if name == 'foo':
            class Foo(object):
                pass
            return Foo  # 返回的是类对象,不是类的实例
        else:
            class Bar(object):
                pass
            return Bar
    
    MyClass = create_class('foo') # 函数返回的是类,不是实例
    # 输出 <class '__main__.create_class.<locals>.Foo'>
    print(MyClass)
    print(MyClass()) # 可以通过这个类创建类实例,也就是类的对象
    # 输出 <__main__.create_class.<locals>.Foo object at 0x000002B642B1EA20>
    

    2.通过type构造类
    但是这还不够动态,python中可以使用type语法构建一个类,返回一个类对象.
    type语法:

    type(类名,父类元组,属性字典)

    class MyClass(object):
          pass
    MyClass = type('MyClass',(object,),{}) # 返回一个类对象
    print(MyClass)
    输出:<class '__main__.MyShinyClass'>
    print(MyClass())
    #输出:<__main__.MyShinyClass object at 0x1085cd810>
    

    通过type创建类的示例:

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/8 18:34'
    # 构建Foo类
    class Foo(object):
        bar = True
    # 使用type构建
    Foo = type('Foo',(object,),dict(bar=True))
    
    # 继承Foo类
    class FooChild(Foo):
        pass
    
    # 使用Type构建
    FooChild = type('FooChild',(Foo,),{})
    
    print(FooChild) # 输出<class '__main__.FooChild'>
    print(FooChild.bar) # 输出True
    
    # 为FooChild增加新方法
    def echo_bar(self):
        print(self.bar)
    FooChild = type('FooChild',(Foo,),dict(echo_bar=echo_bar))
    print(hasattr(Foo,'echo_bar')) # 输出False
    print(hasattr(FooChild,'echo_bar')) # 输出True
    
    my_foo = FooChild() 
    my_foo.echo_bar() # 输出True
    

    元类

    什么是元类
    我们知道Python的类也是对象了,那么这些类是由谁创建的呢,答案就是元类.创建类的类就是元类,也就是说是类的类.
    元类是由type创建的.type是源头

    Class = MetaClass()  # 元类创建类对象
    myobject = Class()  # 类创建我们的实例对象
    

    函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查class属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来。


    那么任何一个__class____class__属性又是什么呢?

    因此,python的元类就是创建类的这种东西,而type就是python的内建元类,当然你也可以创建自己的元类.

    metaclass属性
    Python2中使用给metaclass赋值的方式创建元类,而Python3则通过在类的括号中通过metaclass=的方式来指定元类.

    使用函数创建元类

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/8 19:05'
    
    def upper_attr(name, bases, attrs):
        '''返回一个类对象,将属性都转换为大写形式'''
        attrs = {name: value for name, value in attrs.items() if not name.startswith('__')}
        # 将它们转为大写形式
        attrs = {name.upper(): value for name, value in attrs.items()} # 这里改变它的属性的大小写
        return  type(name,bases,attrs) # 返回一个类
    
    class Foo(object,metaclass=upper_attr):
        bar = '牛逼'
        PHT = '哄哄'
    
    print(hasattr(Foo,'bar')) # 属性已经大写,这里会变成False
    print(hasattr(Foo,'BAR')) # 这里会变成True
    f = Foo()
    print(f.BAR) # 这里打印牛逼
    

    使用类创建元类

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/8 19:15'
    # 1.请记住,'type'实际上是一个类,就像'str'和'int'一样。所以,你可以从type继承
    # 2. __new__ 是在__init__之前被调用的特殊方法,
    #    __new__是用来创建对象并返回之的方法,__new_()是一个类方法
    # 3. 而__init__只是用来将传入的参数初始化给对象,它是在对象创建之后执行的方法。
    # 4. 你很少用到__new__,除非你希望能够控制对象的创建。这里,创建的对象是类,我
    #    们希望能够自定义它,所以我们这里改写__new__如果你希望的话,
    #    你也可以在__init__中做些事情。还有一些高级的用法会涉及到改写__call__特殊方法,
    #    但是我们这里不用,下面我们可以单独的讨论这个使用
    
    class UpperAttrMetaClass(type):
        def __new__(cls, name,bases,attrs):
            attrs = {name.upper():value for name,value in attrs.items() if not name.startswith('__')}
            return type.__new__(cls,name,bases,attrs)
    

    使用super方法使得其更加的清晰

    class UpperAttrMetaClass(type):
        def __new__(cls, name, bases, attrs):
            attrs = {name.upper(): value for name, value in attrs.items() if not name.startswith('__')}
            return super(UpperAttrMetaClass,cls).__new__(cls,name,bases,attrs)
    

    使用元类实现Django的ORM功能

    我们通过创建一个类似Django中的ORM来熟悉一下元类的使用,通常元类用来创建API是非常好的选择.使用元类的编写很复杂,但是使用者可以非常简单的调用API

    #我们想创建一个类似Django的ORM,只要定义字段就可以实现对数据库表和字段的操作。
    class User(Model):
        # 定义类的属性到列的映射:
        id = IntegerField('id')
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')
    

    使用的时候

    # 创建一个实例:
    u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
    # 保存到数据库:
    u.save()
    

    元类实现ORM的类似功能

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/8 19:37'
    
    
    # 首先来定义Field类,它负责保存数据库表的字段名和字段类型
    class Field(object):
        def __init__(self, name, column_type):
            self.name = name
            self.column_type = column_type
    
        def __str__(self):
            return '<{}:{}>'.format(self.__class__.__name__, self.name)
    
    
    class StringField(Field):
        def __init__(self, name):
            super(StringField, self).__init__(name, 'varchar(100)')
    
    
    class IntegerField(Field):
        def __init__(self, name):
            super(IntegerField, self).__init__(name, 'bigint')
    
    
    # 定义元类,控制Model对象的创建
    class ModelMetaClass(type):
        def __new__(cls, name, bases, attrs):
            if name == 'Model':
                return super(ModelMetaClass, cls).__new__(cls, name, bases, attrs)
    
            mapping = dict()
            for k, v in attrs.items():
                if isinstance(v, Field):
                    # 保存属性和列的映射关系到mappings字典
                    print("找到映射关系: {}==>{}".format(k, v))
                    mapping[k] = v
    
            for k in mapping.keys():
                # 将类属性移除,使定义的类字段不污染用户的类属性,只有在实例中可以访问这些key
                attrs.pop(k)
    
            attrs['__table__'] = name.lower()
            attrs['__mapping__'] = mapping
    
            return super(ModelMetaClass, cls).__new__(cls, name, bases, attrs)
    
    
    # 编写Model的积累
    class Model(dict, metaclass=ModelMetaClass):
        def __init__(self, **kwargs):
            super(Model, self).__init__(**kwargs)
    
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Model' object has no attribute '%s'" % key)
    
        def __setattr__(self, key, value):
            self[key] = value
    
        def save(self):
            fields = []
            params = []
            args = []
            for k, v in self.__mapping__.items():
                fields.append(v.name)
                params.append('?')
                args.append(getattr(self, k, None))
            sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
            print('SQL: %s' % sql)
            print('ARGS: %s' % str(args))
    
    # 最后,我们使用定义好的ORM接口,使用起来非常方便
    class User(Model):
        id = IntegerField('id')
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')
    
    # 创建一个实例
    u = User(id=12345,name='Fioman',email='test@orm.org',password='123456')
    # 保存到数据库
    u.save()
    

    输出

    找到映射关系: id==><IntegerField:id>
    找到映射关系: name==><StringField:username>
    找到映射关系: email==><StringField:email>
    找到映射关系: password==><StringField:password>
    SQL: insert into user (id,username,email,password) values (?,?,?,?)
    ARGS: [12345, 'Fioman', 'test@orm.org', '123456']
    

    使用__new__方法和元类(metaclass)方式分别实现单例模式

    首先弄明白这三个函数的作用.
    1.__new__() 它负责创建一个实例对象,一个对象创建的时候,它会先去调用__new__()方法,如果__new__返回一个对象的实例,则它就会执行__init__()方法对这个实例进行初始化,如果__new__不返回一个实例,则不会调用__init__方法.

    2.__init__() 它负责对一个创建的实例进行初始化,在对象创建之后调用该方法,在__new__方法返回一个实例对象时对对象进行初始化.__init__方法可以没有返回值

    3.__call__()方法,其实和类的创建过程和实例化没有太大的关系,一个类的__call__方法,只有当由它所创建的对象被当作像函数那样调用的时候才会调用.也就是说,如果一个对象使用这种方式来调用object(),则它调用的就是这个对象所对应的创建它的类的__call__方法.

    例如:
    class A(object):
        def __call__(self):
            print("__call__ be called")
    
    a = A() #  因为A的元类应该是type,所以这里A()调用的应该是type的`__call__方法`,A也是对象,它是type元类创建的对象
    a()  # 因为a是A类创建的对象,所以这里调用的是A的`__call__方法`
    #输出
    #__call__ be called 
    

    子类如果重写了__new__方法,一般依然要调用父类的__new__方法

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/9 14:08'
    
    class Foo(object):
        def __new__(cls, *args, **kwargs):
            print('Foo 的 __new__被调用.')
            return super().__new__(cls)
    
    class A(Foo):
        pass
    
    class B(Foo):
        def __new__(cls, *args, **kwargs):
            print('B 的__new__被调用.')
            return super().__new__(cls)
    a = A()
    b = B()
    
    # 结果
    Foo 的 __new__被调用.
    B 的__new__被调用.
    Foo 的 __new__被调用.
    

    注意,类的__new__方法之后,必须生成本类的实例才能自动调用本类的init方法进行初始化,否则不会自动调用init``

    class Foo(object):
        def __init__(self, *args, **kwargs):
            print ("Foo __init__")
        def __new__(cls, *args, **kwargs):
            return object.__new__(Stranger)
    
    class Stranger(object):
        def __init__(self,name):
            print ("class Stranger's __init__ be called")
            self.name = name
    
    foo = Foo("test")
    print type(foo) #<class '__main__.Stranger'>
    print foo.name #AttributeError: 'Stranger' object has no attribute 'name'
    
    #说明:如果new方法返回的不是本类的实例,那么本类(Foo)的init和生成的类(Stranger)的init都不会被调用
    

    使用__new__()方法来创建单例模式
    步骤: 在__new__()方法中,判断有没有这个实例,用一个类的属性来表示实例,如果这个实例存在,直接返回,如果不存在就调用父类的__new__()方法进行创建,并保存在_instance属性当中

    # encoding:utf-8
    __author__ = 'Fioman'
    __time__ = '2019/3/9 14:00'
    import threading
    
    class Singleton(object):
    
        _thread_lock = threading.Lock()
        def __init__(self):
            pass
        def __new__(cls, *args, **kwargs):
            if not hasattr(cls,'_instance'):
                with Singleton._thread_lock as lock:
                    if not hasattr(cls,'_instance'):  # 如果没有就创建
                        Singleton._instance = super(Singleton,cls).__new__(cls)
    
            return Singleton._instance  #  返回这个实例
    

    使用元类实现单例模式
    知识点主要有两点

    • 当我们在用一个类实例化对象的时候A(),相当于是调用了创建A这个类的元类的__call__方法.
    • 创建一个单例类,在元类中限定它在创建对象的时候只有一个对象,用它的元类的一个属性来表示,这个属性存在就直接返回,不存在再创建.
    # encoding:utf-8
    import threading
    
    import time
    
    __author__ = 'Fioman'
    __time__ = '2019/3/9 14:21'
    
    
    # 创建元类,一般继承自type
    class SingletonMetaClass(type):
        # 线程锁
        _thread_lock = threading.Lock()
    
        def __init__(self, *args, **kwargs):
            self._instance = None
            super().__init__(*args, **kwargs)
    
        def __call__(self, *args, **kwargs):
            if self._instance is None:
                with SingletonMetaClass._thread_lock:
                    if self._instance is None:
                        self._instance = super().__call__(*args, **kwargs)  # 注意这里调用的是父类的__call__方法创建实例
    
            return self._instance
    
    
    # 通过上面的元类,来创建类
    class Sing(metaclass=SingletonMetaClass):
        def sing(self,i):
            print('{}在唱歌'.format(i))
    
    
    def task(i):
        s = Sing()
        s.sing(id(s))
    
    
    if __name__ == '__main__':
        for i in range(10):
            t = threading.Thread(target=task, args=(i,))
            t.start()
    

    打印结果
    2012339149176在唱歌
    2012339149176在唱歌
    2012339149176在唱歌
    2012339149176在唱歌
    2012339149176在唱歌
    2012339149176在唱歌
    2012339149176在唱歌
    2012339149176在唱歌
    2012339149176在唱歌
    2012339149176在唱歌
    都是一个对象,多个线程创建也是同一个对象

    相关文章

      网友评论

          本文标题:Python中的元类

          本文链接:https://www.haomeiwen.com/subject/xsirpqtx.html