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

​ 元类和类装饰器不仅常常可以交换,而且通常是互补的。它们都对于定制和管理类和实例对象,提供了高级但强大的方法,因为这二者最终都允许我们在类创建过程中插入代码。尽管某些高级应用可能用一种方式或另一种方式编码更好,但在很多情况下,我们选择或组合这两种工具的方法,很大程度上取决于你。

posted on 2021-11-07 15:51  xufat  阅读(214)  评论(0)    收藏  举报

导航

/* 返回顶部代码 */ TOP