Python MetaClass深入分析
python元类是比较难理解和使用的。但是在一些特定的场合使用MetaClass又非常的方便。本文本着先拿来用的精神,将对元类的概念作简要介绍,并通过深入分析一个元类的例子,来体会其功能,并能够在实际需要时灵活运用。
首先,先了解一下必要的知识点。
1. 函数__new__和__init__
元类的实现可以使用这两个函数。在创建类的过程中会调用这两个函数,类定义中这两个函数可有可无。具体可参照官网 Basic customization
先来简要说明一下两者的区别:
- __new__ 是在__init__之前被调用的特殊方法
- __new__是用来创建当前类的对象并返回,然后会自动调用__init__函数
- 如果自定义了__new__函数但是没有返回值,那么不会调用该类的__init__的函数
- 而__init__只是创建类对象过程中根据调用者传入的参数初始化对象
- 在__new__函数中可以控制并定义类的生成,这个一般可以通过在类中定义静态数据成员和成员函数的方式实现,因此很少用
- 如果生成的对象是类类型的话,__new__可以根据实际的需要进行类的定制并控制类的生成过程
- 如果你希望的话,也可以在__init__中做些事情
- 还有一些高级的用法会涉及到改写__call__特殊方法,定义有该函数会让对象具有可调用性,但是__call__函数并不牵涉到类对象的生成过程
通过下面的这个例子可以简单的介绍一下这两个函数:
class MTC(object):
STATIC_MEMBER = "STATIC MEMBER of MTC" def __new__(cls, *args, **kwargs):
print "this is MTC __new__ func"
print cls, args, kwargs
cls.NEW_STATIC_MEMBER = 'NEW STATIC MEMBER of MTC'
cls.test_func = lambda self, x = 'args': x
return super(MTC, cls).__new__(cls, *args, **kwargs) def __init__(self, *args, **kwargs):
print "this is MTC __init__ func"
print self, args, kwargs init_val = (1,2,3,4)
instance = MTC(*init_val, my_key = 'my_value')
print instance.NEW_STATIC_MEMBER
print instance.test_func('This func added in __new__ func!')
运行结果:
this is MTC __new__ func
<class '__main__.MTC'> (1, 2, 3, 4) {'my_key': 'my_value'}
this is MTC __init__ func
<__main__.MTC object at 0x029E5CD0> (1, 2, 3, 4) {'my_key': 'my_value'}
NEW STATIC MEMBER of MTC
This func added in __new__ func!
函数__new__可以使我们动态的定义类或者修改类的某些属性。实际定义Class很少使用到函数__new__,因为绝大多数的时候我们可以直接在定义类时修改类的定义,而不会使用到这个函数的一些特性。
2. 关于MetaClass
MetaClass是一个较为抽象的概念,可以从一个简单的角度来理解,否者还没讲明白,自己先绕晕了。先看一下官方给出的术语解释。metaclass
The class of a class. Class definitions create a class name, a class dictionary, and a list of base classes. The metaclass is responsible for taking those three arguments and creating the class. Most object oriented programming languages provide a default implementation. What makes Python special is that it is possible to create custom metaclasses. Most users never need this tool, but when the need arises, metaclasses can provide powerful, elegant solutions. They have been used for logging attribute access, adding thread-safety, tracking object creation, implementing singletons, and many other tasks.
意思是说metaclass可以通过指定:
- class name
- class dictionary
- a list of base classes
来改变类的默认生成方式,进行类的自定义。
简单来说就是MetaClass是用来创建类的,就好比类是用来创建对象的。如果说类是对象的模板,那么metaclass就是类的模板。
关于MetaClass是如何创建类的,可以参考官网简单精炼的解释:Customizing-class-creation
MetaClass作为创建类的类,可以通过定义__new__和__init__来分别创建类对象和初始化(修改)类的属性。实际定义metaclass的过程中只需要实现二者中的一个即可。
3. MetaClass应用
MetaClass可以应用于需要动态的根据输入参数创建类的场景。
The potential uses for metaclasses are boundless. Some ideas that have been explored including logging, interface checking, automatic delegation, automatic property creation, proxies, frameworks, and automatic resource locking/synchronization.
我们实际可能遇到这样一个场景,有一个类有若干个相互独立的属性集。我们可以使用组合(component)的方式来创建该类。
但是继续思考该类的设计,常见的组合有两种:
- 直接将需要的属性全部作为新组合类的成员列出来;
- 分别将各个独立的属性集定义成单个的类,然后通过在组合类添加每个属性类的实例(instance)的方式来引用各个属性类中定义的属性;
实际上第二种方式使用的较为广泛,因为相比于第一种方式,通过拆分的方式实现,降低了代码的耦合性,提高了可维护性,更便于代码的阅读。
但是实际上第二种方式也有其的缺陷:
- 因为作为组合类的属性,我们应该可以通过组合类的一个实例来直接访问其属性,现在需要通过一个间接proxy来访问其属性,显然并不直观。
- 最为关键的问题是我们人为的拆分一个类的属性,导致在一个属性类中无法访问其他属性类中的成员,也就是属性类之间是不可见的。
那python中有没有一种方式可以以自动添加的方式将各个属性类中定义的成员全部都绑定到组合类中?答案便是MetaClass.
下面我们通过介绍一个例子来说明如何使用元类的方式将各个类的属性绑定到组合类中。
3.1 使用__init__修改类属性
考虑一个房屋的构成,我们先假设房屋的组成为Wall和Door,下面简单定义他们的属性
class Wall(object):
STATIC_WALL_ATTR = "static wall" def init_wall(self):
self.wall = "attr wall" def wall_info(self):
print "this is wall of room" @staticmethod
def static_wall_func():
print 'static wall info' class Door(object): def init_door(self):
self.door = "attr door" def door_info(self):
print "this is door of room"
print self.door, self.wall, self.STATIC_WALL_ATTR
下面定义元类和房屋:
import inspect, sys, types class metaroom(type):
meta_members = ('Wall', "Door")
exclude_funcs = ('__new__', '__init__')
attr_types = (types.IntType, basestring, types.ListType, types.TupleType, types.DictType) def __init__(cls, name, bases, dic):
super(metaroom, cls).__init__(name, bases, dic) # type.__init__(cls, name, bases, dic)
for cls_name in metaroom.meta_members:
cur_mod = sys.modules[__name__]
# cur_mod = sys.modules[metaroom.__module__]
cls_def = getattr(cur_mod, cls_name)
for func_name, func in inspect.getmembers(cls_def, inspect.ismethod):
# 添加成员函数
if func_name not in metaroom.exclude_funcs:
assert not hasattr(cls, func_name), func_name
setattr(cls, func_name, func.im_func)
for attr_name, value in inspect.getmembers(cls_def):
# 添加静态数据成员
if isinstance(value, metaroom.attr_types) and attr_name not in ('__module__', '__doc__'):
assert not hasattr(cls, attr_name), attr_name
setattr(cls, attr_name, value)
下面是房屋Room的定义:
class Room(object):
__metaclass__ = MetaRoom def __init__(self):
self.room = "attr room"
# print self.__metaclass__.meta_members
self.add_cls_member() def add_cls_member(self):
""" 分别调用各个组合类中的init_cls_name的成员函数 """
for cls_name in self.__metaclass__.meta_members:
init_func_name = "init_%s" % cls_name.lower()
init_func_imp = getattr(self, init_func_name, None)
if init_func_imp:
init_func_imp()
我们看一下Class Room的属性列表:
['STATIC_WALL_ATTR', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add_cls_member', 'door_info', 'init_door', 'init_wall', 'wall_info']
作为区分,再看一下Room的实例的属性列表:
['STATIC_WALL_ATTR', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add_cls_member', 'door', 'door_info', 'init_door', 'init_wall', 'room', 'wall', 'wall_info']
这样我们便将类Wall和Door的属性绑定到了Room。如果后面新加属性,比如Window、Floor等,只需要各自实现其定义然后添加到metaroom的meta_members列表中,新定义的属性便可直接在Room中访问。
此外,这些属性类也可以直接访问其他属性类中定义的属性,比如我们在Class Door中可以直接通过self.wall和self.wall_info()的方式获取房屋Wall相关的属性。
注意:
- 被绑定到组合类中的各个子类如果直接访问其他的子类的属性,显然该子类将无法单独作为类创建对象;
- 通过元类metaroom,我们只能将Class Wall和 Door中的成员函数和静态数据成员绑定到了Class Room,因为在创建对象前无法访问类的非静态数据成员;
- 需要约定一种方式(某种样式的函数定义,如上面定义的init_cls_name),在创建组合类对象过程中,将所有子类中非静态数据成员绑定到组合类对象中;
- 上面属性类中定义的函数init_cls_name之外新绑定的数据成员将无法在该类之外被访问,因为其并没有绑定到组合类对象中;
- 在setattr(cls, func_name, func.im_func)中func是绑定函数,func.im_func的实际上相当于将原类中的成员函数解绑,然后绑定到组合类中,这样非静态成员函数中的self参数实际上表示的是新的组合类对象;
- 静态成员函数是无法进行绑定的;
- 元类metaroom中的函数__init__(cls, name, bases, dic)的参数分别表示:cls(创建的类Room),name(类Room的名称),bases(类Room的基类列表),dict(类Room的属性)
读到这里自然而然的会有这样一个问题,有没有什么方法将原类中的staticmethod和classmethod绑定到组合类当中。下面给出代码,可以花时间好好思考一下为什么可以这样写。
class MetaRoom(type):
... ...
def __init__(cls, name, bases, dic):
... ...
for cls_name in MetaRoom.meta_members:
... ...
for func_name, func in inspect.getmembers(cls_def, inspect.ismethod):
# 添加成员函数
if func_name not in MetaRoom.exclude_funcs:
if func.im_self:
# 添加原类中定义的classmethod
setattr(cls, func_name, classmethod(func.im_func))
else:
setattr(cls, func_name, func.im_func)
... ...
for func_name, func in inspect.getmembers(cls_def, inspect.isfunction):
# 添加静态成员函数
assert not hasattr(cls, func_name), func_name
setattr(cls, func_name, staticmethod(func))
3.2 使用__new__指定类的属性
下面使用__new__函数来重写一下元类MetaRoom。
如果说使用__init__函数是通过动态修改类属性的方式来定制类,那么使用__new__函数则是在类创建之前通过指定其属性列表的方式来创建类。
从本文最初对两个函数的介绍也可以看出,函数__new__返回被创建的对象,而后会自动调用__init__函数对__new__返回的对象根据传入的参数进行初始化。只不过元类中两个函数分别创建和初始化的是类。
class MetaRoom(type):
meta_members = ('Wall', "Door")
exclude_funcs = ('__new__', '__init__')
attr_types = (types.IntType, basestring, types.ListType, types.TupleType, types.DictType) def __new__(typ, name, bases, dic):
for cls_name in MetaRoom.meta_members:
cur_mod = sys.modules[__name__]
cls_def = getattr(cur_mod, cls_name)
for func_name, func in inspect.getmembers(cls_def, inspect.ismethod):
if func_name not in MetaRoom.exclude_funcs:
assert func_name not in dic, func_name
dic[func_name] = func.im_func
for attr_name, value in inspect.getmembers(cls_def):
if isinstance(value, MetaRoom.attr_types) and attr_name not in('__module__', '__doc__'):
assert attr_name not in dic, attr_name
dic[attr_name] = value
dic['room_mem_func'] = lambda self, x: x
dic['STATIC_ROOM_VAR'] = 'static room var'
return type.__new__(typ, name, bases, dic)
# return super(MetaRoom, typ).__new__(name, bases, dic)
在这段代码中我们额外添加了两个属性:
- dic['room_mem_func'] = lambda self, x: x
- dic['STATIC_ROOM_VAR'] = 'static room var'
其实是为了更直观的说明我们在属性字典中指定的属性,最终会成为被创建类的数据成员和成员函数。可以通过在类Room中定义静态数据成员STATIC_ROOM_VAR和成员函数room_mem_func的方式达到同样的效果。
class Room(object):
__metaclass__ = MetaRoom
STATIC_ROOM_VAR = "static room var" def room_mem_func(self, x):
return x ... ...
元类中通过修改属性列表dic的方式添加的成员为:静态数据成员和非静态成员函数。这个地方可能会有一些疑问。
静态数据成员可能会比较好理解一些,因为我们创建的是类,只能看到类的属性,非静态数据成员只和类对象有关系。
那为什么在dic中添加的函数是绑定到类实例的成员函数,而不是只和类相关的staticmethod。这个暂时没有很好的解释,唯一可以合理说明的可能只有使用显示的@staticmethod装饰的函数才会被作为类的静态成员函数。
如果在类Room中在添加定义一个静态成员函数和一个类函数:
class Room(object):
__metaclass__ = MetaRoom
... ... @staticmethod
def room_static_func():
print 'This is room static func' @classmethod
def room_cls_func(cls):
print 'This is room cls func'
那么在元类metaroom的函数__new__返回前,程序实际运行过程中获取的dic中的属性列表如下:
'STATIC_ROOM_VAR' (55094792):'static room var'
'STATIC_WALL_ATTR' (55094272):'static wall'
'__init__' (5497536):<function __init__ at 0x03493470>
'__metaclass__' (6049648):<class '__main__.MetaRoom'>
'__module__' (5499680):'__main__'
'add_cls_member' (55094592):<function add_cls_member at 0x03493130>
'door_info' (55337408):<function door_info at 0x034933B0>
'init_door' (55337312):<function init_door at 0x03493370>
'init_wall' (55337152):<function init_wall at 0x03493770>
'room_cls_func' (55094752):<classmethod object at 0x034C6DB0>
'room_mem_func' (55094832):<function <lambda> at 0x034936F0>
'room_static_func' (55094712):<staticmethod object at 0x034C6D90>
'wall_info' (55337248):<function wall_info at 0x034937B0>
__len__:13
上面十三个属性中除了'__module__'之外,其余均为我们自定义的属性。
创建的Class Room完整的属性列表如下:
['STATIC_ROOM_VAR', 'STATIC_WALL_ATTR', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add_cls_member', 'door_info', 'init_door', 'init_wall', 'room_cls_func', 'room_mem_func', 'room_static_func', 'wall_info']
对比一下两种方式,最直接也是最根本的差别就是:
- __new__是在生成类之前通过修改其属性列表的方式来控制类的创建,此时类还没有被创建;
- __init__是在__new__函数返回被创建的类之后,通过直接增删类的属性的方式来修改类,此时类已经被创建;
- __new__函数的第一个参数typ代表的是元类MetaRooM(注意不是元类的对象);
- __init__函数的第一个参数cls表示的是类Room,也就是元类MetaRoom的一个实例(对象);
为了更好的理解通过metaclass的方式创建类,强烈建议使用熟悉的Python IDE通过设置断点的方式来查看元类metaroom创建room的过程。
通过上面的这个例子应该能够比较清楚的理解元类创建类的过程,平时工作中能够在需要的时候灵活的使用元类处理需求可以达到事半功倍的效果。
如果还觉得不够透彻,可以参照python源码来更深入的学习元类,毕竟源码面前了无秘密。
但是我们学习的目的就是掌握知识来解决实际问题的,毕竟要先学会使用么。等到了一定程度需要的时候在阅读源码或许会有更好的效果。
Python MetaClass深入分析的更多相关文章
- 理解 python metaclass使用技巧与应用场景分析
理解python metaclass使用技巧与应用场景分析 参考: decorator与metaclass:http://jfine-python-classes.readthedocs. ...
- python metaclass 入门简介
http://cizixs.com/2015/08/30/metaclass-in-python 动态类型也是类型 python 是一种动态类型语言,换句话说每个变量可以在程序里任何地方改变它的类型. ...
- 【转】Python metaclass
转自: http://ju.outofmemory.cn/entry/32434 在回答了 yield关键字和 decorator的问题之后,我更明白了,我决定非常详细地回答这个问题. 读前警告:这个 ...
- python metaclass
看了很多类似的博客,这篇算是写的比较完善的,转载以备后期查看 原文: 一 你可以从这里获取什么? 1. 也许你在阅读别人的代码的时候碰到过metaclass,那你可以参考这里的介绍. 2. 或许你需要 ...
- python metaclass(元类)
metaclass(元类) 一.创建类的执行流程 二.元类的认识 什么是元类呢?在Python3中继承type的就是元类 二.元类的示例 方式一: # 方式一 class MyType(type): ...
- Python - metaclass元类
参考 https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319106919 ...
- Python - metaclass元类(图)
个人总结
- Python实现Singleton模式的几种方式
使用python实现设计模式中的单例模式.单例模式是一种比较常用的设计模式,其实现和使用场景判定都是相对容易的.本文将简要介绍一下python中实现单例模式的几种常见方式和原理.一方面可以加深对pyt ...
- Python进阶丨如何创建你的第一个Python元类?
摘要:通过本文,将深入讨论Python元类,其属性,如何以及何时在Python中使用元类. Python元类设置类的行为和规则.元类有助于修改类的实例,并且相当复杂,是Python编程的高级功能之一. ...
随机推荐
- 基于用户协同过滤--UserCF
UserCF 本系列文章主要介绍推荐系统领域相关算法原理及其实现.本文以项亮大神的<推荐系统实践>作为切入点,介绍推荐系统最基础的算法(可能也是最好用的)--基于用户的协同过滤算法(Us ...
- 201771010126 王燕《面向对象程序设计(Java)》第十六周学习总结
实验十六 线程技术 实验时间 2017-12-8 1.实验目的与要求 (1) 掌握线程概念: ‐多线程 是进程执行过中产生的多条线索. 是进程执行过中产生的多条线索. 是进程执行过中产生的多条线索. ...
- 算法与数据结构(八) AOV网的关键路径(Swift版)
上篇博客我们介绍了AOV网的拓扑序列,请参考<数据结构(七) AOV网的拓扑排序(Swift面向对象版)>.拓扑序列中包括项目的每个结点,沿着拓扑序列将项目进行下去是肯定可以将项目完成的, ...
- 逆天的 GRUB
参考资料 GRUB 的文档在这里:https://www.gnu.org/software/grub/manual/grub/ Linux 的启动过程和 GRUB 的地位 Linux 系统启动的过程是 ...
- Selenium自动化测试插件—Katalon的自述
Katalon-一款好用的selenium自动化测试插件 Selenium 框架是目前使用较广泛的开源自动化框架,一款好的.基于界面的录制工具对于初学者来说可以快速入门:对于老手来说可以提高开发自动化 ...
- 浅谈Java的主要学习要点_上海尚学堂java培训课程思维导图
Java是一种可以撰写跨平台应用程序的面向对象的程序设计语言.Java 技术具有卓越的通用性.高效性.平台移植性和安全性,广泛应用于PC.数据中心.游戏控制台.科学超级计算机.移动电话和互联网,同时拥 ...
- SUSE12SP3-Mysql5.7安装
1.将以下安装包复制到服务器 mysql-community-client-5.7.24-1.sles12.x86_64.rpm mysql-community-server-5.7.24-1.sle ...
- 优化之Source Qualifier组件
勾选Select Distinct选项,该选项可去除重复记录,以此达到减少数据量从而提高性能 ----------------------------------------------------- ...
- Python档案袋( 进程与协程 )
Python的进程和线程是使用的操作系统的原生线程和进程,其是去调用操作系统的相应接口实现 进程:之间不可直接共享数据,是资源的集合,进程必须有一个线程 线程:基于进程,之间可直接共享数据,可执行,只 ...
- Spark MLlib
MLlib 数据挖掘与机器学习 数据挖掘体系 数据挖掘:也就是data mining,是一个很宽泛的概念,也是一个新兴学科,旨在如何从海量数据中挖掘出有用的信息来. ...