元类与面向切面编程
元类
在 Python中,实例对象是由类生成的,而类本身也是可以被传递和自省的对象。那么类对象是用什么创建和生成的呢?答案是元类,元类就是一种知道如何创建和管理类的对象。
让我们回顾一个内置函数type(),type不仅可以返回对象的类型,而且可以使用类名称、基类元组、类主体定义的字典作为参数来创建一个新类对象:
1 2 3 4 5 | >>> Foo = type ( 'Foo' ,( object ,),{ 'foo' : lambda self : 'foo' }) >>> Foo < class '__main__.Foo' > >>> type (Foo) < type 'type' > |
实际上,新型类的默认元类就是type,类可以用__metaclass__类变量显示的指定元类,上述代码功能与下述相同:
1 2 3 4 5 | class Foo(): __metaclass__ = type def foo( self ): return 'foo' |
如果没有显式的指定元类,class语句会检查基类元组中的第一个基类的元类,比如新型类都是继承object类的,所以新型类与object类的元类相同,为type,继承object而不显式的指定元类:
1 2 3 | class Foo( object ): def foo( self ): return 'foo' |
如果没有指定基类,class语句会检查全局变量__metaclass__,如果没有找到__metaclass__值,Python会使用默认的元类。
在python 2中,默认的元类是types.ClassType,就是所谓的旧样式类。python2.2以后已不提倡使用,比如不指定元类并且不继承object基类:
1 2 3 | class Foo(): def foo( self ): return 'foo' |
1 2 3 | >>> import types >>> isinstance (Foo, types.ClassType) True |
python 3以后,默认的元类皆为type了,显式定义元类的时候需要在基类元组中提供metaclass关键字,class Foo(metaclass=type)如此定义。
使用元类的时候,一般会自定义一个继承自type的子类,并重新实现__init__()与__new__()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 '__main__.Foo' > |
可见,使用class语句定义类后,元类就使用传递给元类的类名称、基类元组和类方法字典创建类。
因为元类创建的实例是类对象,所以__init__方法的第一个参数按惯例写为cls,其实与self功能相同。
面向切面编程
在运行时,动态地将代码切入到类的指定方法、指定位置上的编程称为面向切面的编程(AOP)。
简单地说,如果不同的类要实现相同的功能,可以将其中相同的代码提取到一个切片中,等到需要时再切入到对象中去。这些相同的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。
比如,要为每个类方法记录日志,在python中一个可行的方法是使用装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | def trace(func): def callfunc( self , * args, * * kwargs): debug_log = open ( 'debug_log.txt' , 'a' ) debug_log.write( 'Calling %s: %s ,%s\n' % (func.__name__, args, kwargs)) result = func( self , * args, * * kwargs) debug_log.write( '%s returned %s\n' % (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 |
另外一个可行的方法就是使用元类了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | def trace(func): def callfunc( self , * args, * * kwargs): debug_log = open ( 'debug_log.txt' , 'a' ) debug_log.write( 'Calling %s: %s ,%s\n' % (func.__name__, args, kwargs)) result = func( self , * args, * * kwargs) debug_log.write( '%s returned %s\n' % (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__方法外,还可以定义其它的属性和方法:
1 2 3 4 5 6 7 8 | class ExaMeta( type ): name = 'ExaMeta' def get_cls_name( cls ): print cls .__name__ class Foo( object ): __metaclass__ = ExaMeta |
那么,类可不可以访问元类定义的方法和属性呢?
1 2 3 4 | >>> Foo.get_cls_name() Foo >>> Foo.name 'ExaMeta' |
这很好理解,类Foo是元类的一个实例,在实例的__dict__中查找不到要查询的属性时,就会到实例所属的类字典中去查找,而元类正是定义类Foo的类。
可以再尝试下使用类Foo的实例去访问元类的属性或者方法:
1 2 3 4 | >>> Foo().get_cls_name() AttributeError: 'Foo' object has no attribute 'get_cls_name' >>> Foo().name AttributeError: 'Foo' object has no attribute 'name' |
显然不能访问。
查找一个不与实例关联的属性时,即先在实例的类中查找,然后再在从所有的基类中查找,查找的顺序可以用__mro__属性查看:
1 2 | >>> Foo.__mro__ (< class '__main__.Foo' >, < type 'object' >) |
元类并不在其中,毕竟,类与元类不是继承关系,而是实例与类的创造关系。
元类属性的可用性是不会传递的,也就是说,元类的属性是对它的类实例是可用的,但是对它的类实例的实例是不可用的,这正是元类与基类的主要不同。
有时候,一个类会同时有元类和基类:
1 2 3 4 5 6 7 8 | class M( type ): name = 'M' class B( object ): name = 'B' class A(B): __metaclass__ = M |
属性访问是这样的:
1 2 3 4 | >>> A.name 'B' >>> A().name 'B' |
可见类会先到继承的基类中去查找属性。
元类冲突
假如有两个不同元类的类,要生成一个继承这两个类的子类,会产生什么情况呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class MA( type ): pass class A( object ): __metaclass__ = MA class MB( type ): pass class B( object ): __metaclass__ = MB class C(A, B): pass |
结果会报错,提示元类冲突:
1 2 | 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的元类:
1 2 3 4 5 | class MC(MA, MB): pass class C(A, B): __metaclass__ = MC |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探