wow饯别礼,95580网上银行,文化驿站
本篇博客学习 python 中一种高级概念,元类。在《说文》中这样描述元:元,始也(ps:这也太简短了)。三个字,最后一个字还是语气词。。很形象的说明了元的意思,最开始的意思,在道教中元的意思是:开始的,最初。第一。为首的。主要,根本,整体的意思。那么很显然,元类是最开始的类的意思。我们都知道在 python 中一切皆为对象,而对象都继承与 object 类,那么元类与 object 的关系是什么呢?
可以看出元类也是继承自 object 类的。
那么元类和类的关系是什么呢?
简单来讲就是:类生成对象,元类生成自己本身同时实例化其他所有的类。
之前已经说过 python 中一切皆对象,那么类同样也是对象,而且类是元类的对象。当使用关键词class定义一个类时,在代码执行阶段就会创建一个 空的object,并使用元类的__init__方法来出初始化一个类。这个对象(类)本身可以创建对象,因为它是一个类。
# 在内存中创建一个 foo 对象 class objectcreator(object): pass
但是同样的它也是一个对象,一个元类的实例对象。
所以从对象层面将和它自己实例出来的对象没有什么不同,因此:
例如:
>>> print(objectcreator) # you can print a class because it's an object <class '__main__.objectcreator'> >>> def echo(o): ... print(o) ... >>> echo(objectcreator) # you can pass a class as a parameter <class '__main__.objectcreator'> >>> print(hasattr(objectcreator, 'new_attribute')) false >>> objectcreator.new_attribute = 'foo' # you can add attributes to a class >>> print(hasattr(objectcreator, 'new_attribute')) true >>> print(objectcreator.new_attribute) foo >>> objectcreatormirror = objectcreator # you can assign a class to a variable >>> print(objectcreatormirror.new_attribute) foo >>> print(objectcreatormirror()) <__main__.objectcreator object at 0x8997b4c>
由于类也是对象,因此可以像任何对象一样动态创建它们。 在python中,type 除了可以查看对象的类型,还有一个很强大的功能,type 可以动态的创建类。type 可以将类的描述作为参数并返回一个类。
使用 class关键词时,python 会自动的创建此对象,就像实例化一个对象时,调用的是类中的__init__方法,创建对象时同样的调用了元类中的__init__方法:
该函数有四个参数,第一个参数为 cls表示这是一个由类调用的初始化函数,python 在检测语法的时候发现 class则会把 class 后面的类名当做参数传给 what,bases 表示继承的父类是谁,是个元祖类型(因为会有多继承),dict 是个字典类型,是类的名称空间,可以通过__dict__查看,使用 type来创建一个类:
what = 'music' bases = (object,) dict = {'music_name': '南山忆'}
这和我们使用 class关键词定义一个类没有什么不同:
之前在学习类的时候,我们知道创建一个对象是调用了类中__init__方法,同理在创建类的时候是调用了元类中也就是 type 中的__init__方法,所以我们可以通过改写 type 中的元类来自定义创建类,比如加些判断或者其他的属性:
可惜想象是美好的,这招根本行不通,那么有没有别的办法呢?当然有啦,哈哈哈哈哈
在学习类的三大特性的时候,从父类继承的属性可以改写(多态的思想),那么改写不了元类是因为元类很特殊,那我继承自元类的类肯定可以改写吧。这个到后面再讲,突然发现什么是元类还没讲清楚。。。
我把元类称之为 类的类。元类是创建类的‘’东西‘’,我们定义类来创建对象,类也是对象,所以定义了一个元类用来创建对象。type是 python 用来创建所有类的元类(不包括 object 类)。其实这和用 str创建字符串对象,int创建整数对象一致的。type只是创建类对象的类。
一切,all thing都是 python 中的一个对象。这包括整数、字符串、函数和类。所有这些都是对象,所有这些都是从一个类创建的。因此,元类是创建类对象的东西。
除了使用 type 动态创建类以外,要控制类的创建行为,可以使用 metaclass,这也是自定义元类的方法。
metaclass 的意思就是元类:当我们定义了类以后,就可以根据这个类创建出实例,所以先定义类,然后创建实例。但是如果想创建出类呢?那就必须根据 metaclass 创建出类,所以:先定义元类(不自定义时,默认用 type),然后创建类。(大部分情况使用不到 metaclass,除非想自定义元类)。
默认情况下,类是使用 type 构造的。类主体在一个新的名称空间中执行,类名在本地绑定到类型的结果(名称,基,名称空间)。
可以通过类定义行中传递元类关键字参数来定制类的创建过程,或者从包含此类参数的现有类继承。
class foo(bar): pass
当解释器执行这行代码时,执行以下操作:
foo 有__metaclass__属性吗?如果有的话,python 会通过 __metaclass__在内存中创建一个名称为 foo 的类对象。如果 python 没有找到__metaclass__,它会继续在 bar 中寻找__metaclass__属性,并尝试做和前面同样的操作。如果 python 在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。如果还找不到__metaclass__,python 就会用内置的 type 来创建这个类对象。
那么在自定义类是,可以在__metaclass__中放置什么代码呢?
可以放用来创建一个类的东西,type 或者 type的子类都可以放。
以上面的代码为例,我们实例化一个对象obj=foo(),会先执行 foo 类中的__new__方法,没有则使用父类的__new__方法,创建一个空对象并返回,然后执行__init__方法(自己有就用自己的,没有就用父类的,这里分两种情况,如果是创建一个类的对象,那就是使用父类的,因为自己没有;如果是创建类,自己有就是用自己的,否则就是用父类的),为创建的对象进行初始化。
obj()会执行 foo 类的__call__方法,没有则用父类的。现在已经知道。类同样也是对象,是元类的对象,即实例化一个对象(类)时,调用其父类(元类)的__call__方法。
元类处理过程:定义一个类时,使用声明或者默认的元类对该类进行创建,对元类求 type 运算,得到父元类(该类声明元类的父元类),调用父元类的__call__方法,在父元类的__call__方法中,调用该类声明的元类的__new__来创建一个空对象(该方法需要返回一个类对象实例),然后再调用该元类的__init__方法初始化该类对象,最终返回一个类。
元类的__new__方法和__init__影响的是创建类的对象行为(不是创建类),父元类的__call__控制对子元类的__new__,__init__的调用,就是说控制类对象的创建和初始化。父元类的__new__和__init__由更上层的元类控制,一般来说,原始 type 是最初的父元类,其__new__和__init__是最具有普遍意义的,即应该是分配内存、初始化相关信息等。元类的__call__方法影响的是创建类的实例对象的行为,所以这时候自定义__call__就可以控制创建类对象的实例对象的行为了。比如单例模式的创建。
__new__和__init__影响的是创建对象的行为,当这些函数在元类中时,影响创建的是类;同理,当这两个函数在普通类中时,影响的是创建普通的对象实例行为。
__call__影响()调用行为,__call__是在创建类的时候调用,即:定义类时就是创建类,此时会调用元类的__call__,如果元类有继承,子元类定义时执行的是父元类的__call__。如果是普通类实例化,调用的是普通类的__call__。(昨晚上卡在这里了,其实实例化一个普通的对象时,都是调用其父类的__call__方法,除了元类,普通类中不会有__call__方法。)
元类的主要目的是在创建类时自动更改类,比如想要将创建的所有类都变成首字母大写的:
class mymetaclass(type): def __call__(cls, *args, **kwargs): if type(args[0]) != str: raise typeerror('参数必须为字符串类型') obj = object.__new__(cls) obj.__init__(*args, **kwargs) return obj class foo(metaclass=mymetaclass): def __init__(self, name): self.name = name res = foo(123)
这就是自定义元类的好处,可以在__call__来对传入的参数进行一些判断来做一些自定义操作。
# the metaclass will automatically get passed the same argument # that you usually pass to `type` def upper_attr(future_class_name, future_class_parents, future_class_attr): """ return a class object, with the list of its attribute turned into uppercase. """ # pick up any attribute that doesn't start with '__' and uppercase it uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # let `type` do the class creation return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr # this will affect all classes in the module class foo(): # global __metaclass__ won't work with "object" though # but we can define __metaclass__ here instead to affect only this class # and this will work with "object" children bar = 'bip' print(hasattr(foo, 'bar')) # out: false print(hasattr(foo, 'bar')) # out: true f = foo() print(f.bar) # out: 'bip'
# remember that `type` is actually a class like `str` and `int` # so you can inherit from it class upperattrmetaclass(type): # __new__ is the method called before __init__ # it's the method that creates the object and returns it # while __init__ just initializes the object passed as parameter # you rarely use __new__, except when you want to control how the object # is created. # here the created object is the class, and we want to customize it # so we override __new__ # you can do some stuff in __init__ too if you wish # some advanced use involves overriding __call__ as well, but we won't # see this def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type(future_class_name, future_class_parents, uppercase_attr)
因为 type 的__new__不会被覆盖,所以可以使用:
class upperattrmetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # reuse the type.__new__ method # this is basic oop, nothing magic in there return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
class upperattrmetaclass(type): def __new__(cls, clsname, bases, dct): uppercase_attr = {} for name, val in dct.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return super(upperattrmetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
使用元类的代码复杂性背后的原因不是因为元类,而是因为你通常使用元类来依赖于内省,操纵继承,变量__dict__等等来做扭曲的东西。实际上,元类特别适用于制作黑魔法,因此也很复杂。但他们本身很简单:
既然__metaclass__可以接收任何调用,那么为什么要使用一个类,因为类显然比函数要复杂。
有几个原因:
感谢
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
新手学习Python2和Python3中print不同的用法
Python基于os.environ从windows获取环境变量
网友评论