Python中的元类

学懂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在唱歌
都是一个对象,多个线程创建也是同一个对象

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容