
作者:再见紫罗兰 来源:http://www.cnblogs.com/linxiyue/p/8030604.html
在 Python中,实例对象是由类生成的,而类本身也是可以被传递和自省的对象。那么类对象是用什么创建和生成的呢?答案是元类,元类就是一种知道如何创建和管理类的对象。 让我们回顾一个内置函数type(),type不仅可以返回对象的类型,而且可以使用类名称、基类元组、类主体定义的字典作为参数来创建一个新类对象: >>> Foo = type('Foo',(object,),{'foo':lambda self:'foo'})
>>> Foo
>>> type(Foo)
实际上,新型类的默认元类就是type,类可以用metaclass类变量显示的指定元类,上述代码功能与下述相同: class Foo():
__metaclass__ = type
def foo(self):
return 'foo'
如果没有显式的指定元类,class语句会检查基类元组中的第一个基类的元类,比如新型类都是继承object类的,所以新型类与object类的元类相同,为type,继承object而不显式的指定元类: class Foo(object):
def foo(self):
return 'foo'
如果没有指定基类,class语句会检查全局变量metaclass,如果没有找到metaclass值,Python会使用默认的元类。 在python 2中,默认的元类是types.ClassType,就是所谓的旧样式类。python2.2以后已不提倡使用,比如不指定元类并且不继承object基类: class Foo():
def foo(self):
return 'foo'
>>> import types
>>> isinstance(Foo, types.ClassType)
True
python 3以后,默认的元类皆为type了,显式定义元类的时候需要在基类元组中提供metaclass关键字,class Foo(metaclass=type)如此定义。 使用元类的时候,一般会自定义一个继承自type的子类,并重新实现init()与new()方法: class ExampleType(type):
def __new__(cls, name, bases, dct):
print 'create class %s'%name
return type.__new__(cls, name, bases, dct)
def __init__(cls, name, bases, dct):
print 'Init class %s'%name
type.__init__(cls, name, bases, dct)
class Foo(object):
__metaclass__ = ExampleType
>>>
create class Foo
Init class Foo
>>> Foo
可见,使用class语句定义类后,元类就使用传递给元类的类名称、基类元组和类方法字典创建类。 因为元类创建的实例是类对象,所以init方法的第一个参数按惯例写为cls,其实与self功能相同。 面向切面编程在运行时,动态地将代码切入到类的指定方法、指定位置上的编程称为面向切面的编程(AOP)。 简单地说,如果不同的类要实现相同的功能,可以将其中相同的代码提取到一个切片中,等到需要时再切入到对象中去。这些相同的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。 比如,要为每个类方法记录日志,在python中一个可行的方法是使用装饰器: def trace(func):
def callfunc(self, *args, **kwargs):
debug_log = open('debug_log.txt', 'a')
debug_log.write('Calling %s: %s ,%sn'%(func.__name__, args, kwargs))
result = func(self, *args, **kwargs)
debug_log.write('%s returned %sn'%(func.__name__, result))
debug_log.close()
return result
return callfunc
def logcls(cls):
for k, v in cls.__dict__.items():
if k.startswith('__'):
continue
if not callable(v):
continue
setattr(cls, k, trace(v))
return cls
@logcls
class Foo(object):
num = 0
def spam(self):
Foo.num += 1
return Foo.num
另外一个可行的方法就是使用元类了: def trace(func):
def callfunc(self, *args, **kwargs):
debug_log = open('debug_log.txt', 'a')
debug_log.write('Calling %s: %s ,%sn'%(func.__name__, args, kwargs))
result = func(self, *args, **kwargs)
debug_log.write('%s returned %sn'%(func.__name__, result))
debug_log.close()
return result
return callfunc
class LogMeta(type):
def __new__(cls, name, bases, dct):
for k, v in dct.items():
if k.startswith('__'):
continue
if not callable(v):
continue
dct[k] = trace(v)
return type.__new__(cls, name, bases, dct)
class Foo(object):
__metaclass__ = LogMeta
num = 0
def spam(self):
Foo.num += 1
return Foo.num
元类的一个主要用途就是检查收集或者更改类定义的内容,包括类属性、类方法、描述符等等。 元类与基类元类中除了可以定义init和new方法外,还可以定义其它的属性和方法: class ExaMeta(type):
name = 'ExaMeta'
def get_cls_name(cls):
print cls.__name__
class Foo(object):
__metaclass__ = ExaMeta
那么,类可不可以访问元类定义的方法和属性呢? >>> Foo.get_cls_name()
Foo
>>> Foo.name
'ExaMeta'
这很好理解,类Foo是元类的一个实例,在实例的dict中查找不到要查询的属性时,就会到实例所属的类字典中去查找,而元类正是定义类Foo的类。 可以再尝试下使用类Foo的实例去访问元类的属性或者方法: >>> Foo().get_cls_name()
AttributeError: 'Foo' object has no attribute 'get_cls_name'
>>> Foo().name
AttributeError: 'Foo' object has no attribute 'name'
显然不能访问。 查找一个不与实例关联的属性时,即先在实例的类中查找,然后再在从所有的基类中查找,查找的顺序可以用mro属性查看: 元类并不在其中,毕竟,类与元类不是继承关系,而是实例与类的创造关系。 元类属性的可用性是不会传递的,也就是说,元类的属性是对它的类实例是可用的,但是对它的类实例的实例是不可用的,这正是元类与基类的主要不同。 有时候,一个类会同时有元类和基类: class M(type):
name = 'M'
class B(object):
name = 'B'
class A(B):
__metaclass__ = M
属性访问是这样的: >>> A.name
'B'
>>> A().name
'B'
可见类会先到继承的基类中去查找属性。 元类冲突假如有两个不同元类的类,要生成一个继承这两个类的子类,会产生什么情况呢? class MA(type):
pass
class A(object):
__metaclass__ = MA
class MB(type):
pass
class B(object):
__metaclass__ = MB
class C(A, B):
pass
结果会报错,提示元类冲突: TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
我们需要手动构造新子类的元类,让新子类的元类继承自A和B的元类: class MC(MA, MB):
pass
class C(A, B):
__metaclass__ = MC
题图:pexels,CC0 授权。
|