Python使用技巧--python元类的使用
一、元类的使用原理
我们先从类的class语句协议说起,当我们用class语句协议创建一个类,当执行到class语句的末尾,python会自动调用type对象来创建class对象。
class = type(classname, superclasses, attributedict)
type对象定义了一个__call__运算符重载方法,当调用type对象的时候,该方法运行两个其他的方法:
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)
__new__方法创建并返回了新的class对象,并且随后__init__方法初始化了新创建的对 象。正如我们稍后将看到的,这是type的元类子类通常用来定制类的钩子。
例如,给定一个如下所示的类定义:
class Spam(Eggs): # Inherits from Eggs
data = 1 # Class data attribute
def meth(self, arg): # Class method attribute
pass
Python将会从内部运行嵌套的代码块来创建该类的两个属性(data和meth),然后在class语句的末尾调用type对象,产生class对象:
Spam = type('Spam', (Eggs,), {'data': 1, 'meth': meth, '__module__': '__main__'})
由于这个调用在class语句的末尾进行,它是用来扩展或处理一个类的、理想的钩子。 技巧在于,用将要拦截这个调用的一个定制子类来替代默认的type,这个定制子类我们称为元类,这就是元类背后的使用原理。
二、元类的声明
元类的声明在python2与python3中有些区别:
在python3.x中声明:
class Spam(metaclass=Meta):
如果该类继承超类,则把超类写在元类前面:
class Spam(Eggs, metaclass=Meta):
在python2.x中声明:
class spam(object):
__metaclass__ = Meta
从技术上讲,2.x中的一些类如果有 __metaclass__声明,则不必显式地继承object就能使用元类,因为该机制能产生新式类,会自动地让类成为新式类,并把object添加到它的__base__序列中。如果没有这个声明,2.x便会默认使用经典类。
当以这些方式声明的时候,创建类对象的调用在class语句的底部运行,修改为调用元类而不是默认的type:
class = Meta(classname, superclasses, attributedict)
由于元类是type的一个子类,所以如果元类定义了__new__和__init__方法的定制版本的话,那么type类的__class__就会把创建和初始化新的class对象的调用委托给元类:
Meta.__new__(Meta, classname, superclasses, attributedict)
Meta.__init__(class, classname, superclasses, attributedict)
为了展示,这里再次给出前面的例子,用Python 3.0的元类声明扩展:
class Spam(Eggs, metaclass=Meta): # Inherits from Eggs, instance of Meta
data = 1 # Class data attribute
def meth(self, arg): # Class method attribute
pass
在这条class语句的末尾,Python内部运行如下的代码来创建class对象:
Spam = Meta('Spam', (Eggs,), {'data': 1, 'meth': meth, '__module__': '__main__'})
如果元类定义了自己的__new__或__init__,在此处的调用期间,它们将依次由继承的type类的__call__方法调用,以创建并初始化新类。
三、元类的编写
基本元类
我们能够编写的最简单元类只是带有一个__new__方法的type的子类,该方法通过运行type中的默认版本来创建类对象。像这样的一个元类__new__,通过继承自type的 __new__方法而运行。它通常执行所需的任何定制并且调用type的超类的__new__方法来创建并运行新的类对象:
class Meta(type):
def __new__(meta, classname, supers, classdict):
# Run by inherited type.__call__
return type.__new__(meta, classname, supers, classdict)
这个元类实际并没有做任何事情(我们可能也会让默认的type类创建类),但是它展示了将元类接入元类钩子中以定制——由于元类在一条class语句的末尾调用,并且因为type对象的__call__分派到了__new__和__init__方法,所以我们在这些方法中提供的代码可以管理从元类创建的所有类。下面是应用中的实例,将打印添加到元类和文件以便追踪:
class MetaOne(type):
def __new__(meta, classname, supers, classdict):
print('In MetaOne.new:', classname, supers, classdict, sep='\n...')
return type.__new__(meta, classname, supers, classdict)
class Eggs:
pass
print('making class')
class Spam(Eggs, metaclass=MetaOne): # Inherits from Eggs, instance of Meta
data = 1 # Class data attribute
def meth(self, arg): # Class method attribute
pass
print('making instance')
X = Spam()
print('data:', X.data)
在这里,Spam继承自Eggs并且是MetaOne的一个实例,但是X是Spam的一个实例并且继承自它。当这段代码在Python 3.0下运行的时候,注意在class语句的末尾是如何调用元类的,在我们真正创建一个实例之前——元类用来处理类,并且类用来处理实例:
making class
In MetaOne.new:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x100f76940>}
making instance
data: 1
定制构建和初始化
元类也可以接入__init__协议,由type对象的__call__调用:通常,__new__创建并返回了类对象,__init__初始化了已经创建的类。元类也可以用做在创建时管理类的钩子:
class MetaOne(type):
def __new__(meta, classname, supers, classdict):
print('In MetaOne.new: ', classname, supers, classdict, sep='\n...')
return type.__new__(meta, classname, supers, classdict)
def __init__(Class, classname, supers, classdict):
print('In MetaOne init:', classname, supers, classdict, sep='\n...')
print('...init class object:', list(Class.__dict__.keys()))
class Eggs:
pass
print('making class')
class Spam(Eggs, metaclass=MetaOne): # Inherits from Eggs, instance of Meta
data = 1 # Class data attribute
def meth(self, arg): # Class method attribute
pass
print('making instance')
X = Spam()
print('data:', X.data)
在这个例子中,类初始化方法在类构建方法之后运行,但是,两者都在class语句最后运行,并且在创建任何实例之前运行:
making class
In MetaOne.new:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x1012e0af0>}
In MetaOne init:
...Spam
...(<class '__main__.Eggs'>,)
...{'__module__': '__main__', '__qualname__': 'Spam', 'data': 1, 'meth': <function Spam.meth at 0x1012e0af0>}
...init class object: ['__module__', 'data', 'meth', '__doc__']
making instance
data: 1
四、实例与继承的关系
元类与父类继承很相似,所以我们需要认清以下关键点:
- 元类继承自type类
- 元类声明由子类继承
- 元类属性不会被类实例继承
- 元类属性会被类获得
为了说明最后两点,考虑如下的例子:
class MetaOne(type):
def __new__(meta, classname, supers, classdict): # Redefine type method
print('In MetaOne.new:', classname)
return type.__new__(meta, classname, supers, classdict)
def toast(self):
print('toast')
class Super(metaclass=MetaOne): # Metaclass inherited by subs too
def spam(self): # MetaOne run twice for two classes
print('spam')
class C(Super): # Superclass: inheritance versus instance
def eggs(self): # Classes inherit from superclasses
print('eggs') # But not from metclasses
X = C()
X.eggs() # Inherited from C
X.spam() # Inherited from Super
X.toast() # Not inherited from metaclass
当这段代码运行的时候,元类处理两个客户类的构建,并且实例继承类属性而不是元类属性:
In MetaOne.new: Super
In MetaOne.new: C
eggs
spam
AttributeError: 'C' object has no attribute 'toast'
五、元类的运用
示例一:使用元类向类添加方法
def eggsfunc(obj):
return obj.value * 4
def hamfunc(obj, value):
return value + 'ham'
class Extender(type):
def __new__(meta, classname, supers, classdict):
classdict['eggs'] = eggsfunc
classdict['ham'] = hamfunc
return type.__new__(meta, classname, supers, classdict)
class Client1(metaclass=Extender):
def __init__(self, value):
self.value = value
def spam(self):
return self.value * 2
class Client2(metaclass=Extender):
value = 'ni?'
运行测试:
X = Client1('Ni!')
print(X.spam())
print(X.eggs())
print(X.ham('bacon'))
Y = Client2()
print(Y.eggs())
print(Y.ham('bacon'))
输出:
Ni!Ni!
Ni!Ni!Ni!Ni!
baconham
ni?ni?ni?ni?
baconham
注意,这个示例中的元类仍然执行相当静态的工作:把两个已知的方法添加到声明了元类的每个类。实际上,如果我们所需要做的总是向一组类添加相同的两个方法,我们也可以将它们编写为常规的超类并在子类中继承它们。然而,实际上,元类结构支持更多的动态行为。例如,主体类也可以基于运行时的任意逻辑配置:
class MetaExtend(type):
def __new__(meta, classname, supers, classdict):
if sometest():
classdict['eggs'] = eggsfunc1
else:
classdict['eggs'] = eggsfunc2
if someothertest():
classdict['ham'] = hamfunc
else:
classdict['ham'] = lambda *args: 'Not supported'
return type.__new__(meta, classname, supers, classdict)
元类与类装饰器的关系
- 在class语句的末尾,类装饰器把类名重新绑定到一个函数的结果。
- 元类通过在一条class语句的末尾把类对象创建过程路由到一个对象来工作。
类装饰器可以用来管理一个类的实例以及类自身。尽管装饰器可以自然地管理类,然而,用元类管理实例有些不那么直接。元类可能最好用于类对象管理。
使用类装饰器向类添加方法
def eggsfunc(obj):
return obj.value * 4
def hamfunc(obj, value):
return value + 'ham'
def Extender(aClass):
aClass.eggs = eggsfunc # Manages class, not instance
aClass.ham = hamfunc # Equiv to metaclass __init__
return aClass
@Extender
class Client1: # Client1 = Extender(Client1)
def __init__(self, value): # Rebound at end of class stmt
self.value = value
def spam(self):
return self.value * 2
@Extender
class Client2:
value = 'ni?'
运行测试:
X = Client1('Ni!') # X is a Client1 instance
print(X.spam())
print(X.eggs())
print(X.ham('bacon'))
Y = Client2()
print(Y.eggs())
print(Y.ham('bacon'))
类装饰器可以管理类和实例,元类可以管理类和实例,但是管理实例需要一些额外工作:
使用类装饰器管理实例
def Tracer(aClass): # On @decorator
class Wrapper:
def __init__(self, *args, **kargs): # On instance creation
self.wrapped = aClass(*args, **kargs) # Use enclosing scope name
def __getattr__(self, attrname):
print('Trace:', attrname) # Catches all but .wrapped
return getattr(self.wrapped, attrname) # Delegate to wrapped object
return Wrapper
@Tracer
class Person: # Person = Tracer(Person)
def __init__(self, name, hours, rate): # Wrapper remembers Person
self.name = name
self.hours = hours
self.rate = rate # In-method fetch not traced
def pay(self):
return self.hours * self.rate
bob = Person('Bob', 40, 50) # bob is really a Wrapper
print(bob.name) # Wrapper embeds a Person
print(bob.pay()) # Triggers __getattr__
这段代码运行的时候,装饰器使用类名重新绑定来把实例对象包装到一个对象中,该对象在如下的输出中给出跟踪行:
Trace: name
Bob
Trace:pay
2000
使用元类管理实例
元类明确地设 计来管理类对象创建,并且它们有一个为此目的而设计的接口。要使用元类来管理实例,我们必须依赖一些额外力量。如下的元类和前面的装饰器具有同样的效果和输出:
def Tracer(classname, supers, classdict): # On class creation call
aClass = type(classname, supers, classdict) # Make client class
class Wrapper:
def __init__(self, *args, **kargs): # On instance creation
self.wrapped = aClass(*args, **kargs)
def __getattr__(self, attrname):
print('Trace:', attrname) # Catches all but .wrapped
return getattr(self.wrapped, attrname) # Delegate to wrapped object
return Wrapper
class Person(metaclass=Tracer): # Make Person with Tracer
def __init__(self, name, hours, rate): # Wrapper remembers Person
self.name = name
self.hours = hours
self.rate = rate # In-method fetch not traced
def pay(self):
return self.hours * self.rate
这也有效,但是它依赖于两个技巧。首先,它必须使用一个简单的函数而不是一个类,因为type子类必须附加给对象创建协议。其次,必须通过手动调用type来手动创建主体类;它需要返回一个实例包装器,但是元类也负责创建和返回主体类。其实,在这个例子中,我们将使用元类协议来模仿装饰器,而不是按照相反的做法。由于它们都在一条class语句的末尾运行,所以在很多用途中,它们都是同一方法的变体。元类版本运行的时候,产生与装饰器版本同样的输出:
Trace: name
Bob
Trace:pay
2000
我们可以研究这两个示例版本,以权衡其利弊。通常,元类可能更适合于类管理,因为它们就设计为如此。类装饰器可以管理实例或类,然而,它们不是更高级元类用途的最佳选择.
示例二:对方法应用装饰器
使用元类对方法应用装饰器
from types import FunctionType
def tracer(func): # Use function, not class with __call__
calls = 0 # Else self is decorator instance only
def onCall(*args, **kwargs):
nonlocal calls
calls += 1
print('call %s to %s' % (calls, func.__name__))
return func(*args, **kwargs)
return onCall
class MetaTrace(type):
def __new__(meta, classname, supers, classdict):
for attr, attrval in classdict.items():
if type(attrval) is FunctionType: # Method?
classdict[attr] = tracer(attrval) # Decorate it
return type.__new__(meta, classname, supers, classdict) # Make class
class Person(metaclass=MetaTrace):
def __init__(self, name, pay):
self.name = name
self.pay = pay
def giveRaise(self, percent):
self.pay *= (1.0 + percent)
def lastName(self):
return self.name.split()[-1]
运行测试:
bob = Person('Bob Smith', 50000)
sue = Person('Sue Jones', 100000)
print(bob.name, sue.name)
sue.giveRaise(.10)
print(sue.pay)
print(bob.lastName(), sue.lastName())
输出
call 1 to __init__
call 2 to __init__
Bob Smith Sue Jones
call 1 to giveRaise
110000.00000000001
call 1 to lastName
call 2 to lastName
Smith Jones
类装饰器也与元类有交叉。如下的版本,用一个类装饰器替换了前面的示例中的元类。 它定义并使用一个类装饰器,该装饰器把一个函数装饰器应用于一个类的所有方法。然而,前一句话听起来更像是禅语而不像是技术说明,这所有的工作相当自然——Python的装饰器支持任意的嵌套和组合:
使用装饰器对方法应用装饰器
from types import FunctionType
def tracer(func): # Use function, not class with __call__
calls = 0 # Else self is decorator instance only
def onCall(*args, **kwargs):
nonlocal calls
calls += 1
print('call %s to %s' % (calls, func.__name__))
return func(*args, **kwargs)
return onCall
def decorateAll(decorator):
def DecoDecorate(aClass):
for attr, attrval in aClass.__dict__.items():
if type(attrval) is FunctionType:
setattr(aClass, attr, decorator(attrval)) # Not __dict__
return aClass
return DecoDecorate
@decorateAll(tracer) # Use a class decorator
class Person: # Applies func decorator to methods
def __init__(self, name, pay): # Person = decorateAll(..)(Person)
self.name = name # Person = DecoDecorate(Person)
self.pay = pay
def giveRaise(self, percent):
self.pay *= (1.0 + percent)
def lastName(self):
return self.name.split()[-1]
运行测试
bob = Person('Bob Smith', 50000)
sue = Person('Sue Jones', 100000)
print(bob.name, sue.name)
sue.giveRaise(.10)
print(sue.pay)
print(bob.lastName(), sue.lastName())
输出
call 1 to __init__
call 2 to __init__
Bob Smith Sue Jones
call 1 to giveRaise
110000.00000000001
call 1 to lastName
call 2 to lastName
Smith Jones
元类和类装饰器不仅常常可以交换,而且通常是互补的。它们都对于定制和管理类和实例对象,提供了高级但强大的方法,因为这二者最终都允许我们在类创建过程中插入代码。尽管某些高级应用可能用一种方式或另一种方式编码更好,但在很多情况下,我们选择或组合这两种工具的方法,很大程度上取决于你。
浙公网安备 33010602011771号