Python高级特性(3): Classes和Metaclasses(转)
原文:Python高级特性(3): Classes和Metaclasses
类和对象
类和函数一样都是Python中的对象。当一个类定义完成之后,Python将创建一个“类对象”并将其赋值给一个同名变量。类是type类型的对象(是不是有点拗口?)。
类对象是可调用的(callable,实现了 __call__方法),并且调用它能够创建类的对象。你可以将类当做其他对象那么处理。例如,你能够给它们的属性赋值,你能够将它们赋值给一个变量,你 可以在任何可调用对象能够用的地方使用它们,比如在一个map中。事实上当你在使用map(str, [1,2,3])的时候,是将一个整数类型的list转换为字符串类型的list,因为str是一个类。可以看看下面的代码:
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
|
>>> class C( object ): ... def __init__( self , s): ... print s ... >>> myclass = C >>> type (C) < type 'type' > >>> type (myclass) < type 'type' > >>> myclass( 2 ) 2 <__main__.C object at 0x10e2bea50 > >>> map (myclass, [ 1 , 2 , 3 ]) 1 2 3 [<__main__.C object at 0x10e2be9d0 >, <__main__.C object at 0x10e2bead0 >, <__main__.C object at 0x10e2beb10 >] >>> map (C, [ 1 , 2 , 3 ]) 1 2 3 [<__main__.C object at 0x10e2be950 >, <__main__.C object at 0x10e2beb50 >, <__main__.C object at 0x10e2beb90 >] >>> C.test_attribute = True >>> myclass.test_attribute True |
正因如此,Python中的“class”关键字不像其他语言(例如C++)那样必须出现在代码main scope中。在Python中,它能够在一个函数中嵌套出现,举个例子,我们能够这样在函数运行的过程中动态的创建类,很强大。看代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
>>> def make_class(class_name): ... class C( object ): ... def print_class_name( self ): ... print class_name ... C.__name__ = class_name ... return C ... >>> C1, C2 = map (make_class, [ "C1" , "C2" ]) >>> c1, c2 = C1(), C2() >>> c1.print_class_name() C1 >>> c2.print_class_name() C2 >>> type (c1) < class '__main__.C1' > >>> type (c2) < class '__main__.C2' > >>> c1.print_class_name.__closure__ (<cell at 0x10ab6dbe8 : str object at 0x10ab71530 >,) |
请注意,在这里通过make_class创建的两个类是不同的对象,因此通过它们创建的对象就不属于同一个类型。正如我们在装饰器中做的那样,我们 在类被创建之后手动设置了类名。同样也请注意所创建类的print_class_name方法在一个closure cell中捕捉到了类的closure和class_name。如果你对closure的概念还不是很清楚,那么最好去看看前篇,复习一下 closures和decorators相关的内容。
Metaclasses
如果类是能够制造对象的对象,那制造类的对象又该叫做什么呢(相信我,这并不是一个先有鸡还是先有蛋的问题)?答案是元类 (Metaclasses)。大部分常见的基础元类都是type。当输入一个参数时,type将简单的返回输入对象的类型,这就不涉及元类。然而当输入三 个参数时,type将扮演元类的角色,基于输入参数创建一个类并返回。输入参数相当简单:类名,父类及其参数的字典。后面两者可以为空,来看一个例子:
1
2
3
4
5
6
|
>>> MyClass = type ( "MyClass" , ( object ,), { "my_attribute" : 0 }) >>> type (MyClass) < type 'type' > >>> o = MyClass() >>> o.my_attribute 0 |
特别注意第二个参数是一个tuple(语法看起来很奇怪,以逗号结尾)。如果你需要在类中安排一个方法,那么创建一个函数并且将其以属性的方式传递作为第三个参数,像这样:
1
2
3
4
5
6
7
8
9
|
>>> def myclass_init( self , my_attr): ... self .my_attribute = my_attr ... >>> MyClass = type ( "MyClass" , ( object ,), { "my_attribute" : 0 , "__init__" : myclass_init}) >>> o = MyClass( "Test" ) >>> o.my_attribute 'Test' >>> o.__init__ <bound method MyClass.myclass_init of <__main__.MyClass object at 0x10ab72150 >> |
我们可以通过一个可调用对象(函数或是类)来自定义元类,这个对象需要三个输入参数并返回一个对象。这样一个元类在一个类上实现只要定义了它的__metaclass__属性。第一个例子,让我们做一些有趣的事情看看我们能够用元类做些什么:
1
2
3
4
5
6
7
8
9
10
|
>>> def mymetaclass(name, parents, attributes): ... return "Hello" ... >>> class C( object ): ... __metaclass__ = mymetaclass ... >>> print C Hello >>> type (C) < type 'str' > |
请注意以上的代码,C只是简单地将一个变量引用指向了字符串“Hello”。当然了,没人会在实际中写这样的代码,这只是为了演示元类的用法而举的 一个简单例子。接下来我们来做一些更有用的操作。在本系列的第二部分我们曾看到如何使用装饰器类来记录目标类每个方法的输出,现在我们来做同样的事情,不 过这一次我们使用元类。我们借用之前的装饰器定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
def log_everything_metaclass(class_name, parents, attributes): print "Creating class" , class_name myattributes = {} for name, attr in attributes.items(): myattributes[name] = attr if hasattr (attr, '__call__' ): myattributes[name] = logged( "%b %d %Y - %H:%M:%S" , class_name + "." )(attr) return type (class_name, parents, myattributes) class C( object ): __metaclass__ = log_everything_metaclass def __init__( self , x): self .x = x def print_x( self ): print self .x # Usage: print "Starting object creation" c = C( "Test" ) c.print_x() |
1
2
3
4
5
6
7
8
|
# Output: Creating class C Starting object creation - Running 'C.__init__' on Aug 05 2013 - 13 : 50 : 58 - Finished 'C.__init__' , execution time = 0.000s - Running 'C.print_x' on Aug 05 2013 - 13 : 50 : 58 Test - Finished 'C.print_x' , execution time = 0.000s |
如你所见,类装饰器与元类有着很多共同点。事实上,任何能够用类装饰器完成的功能都能够用元类来实现。类装饰器有着很简单的语法结构易于阅读,所以 提倡使用。但就元类而言,它能够做的更多,因为它在类被创建之前就运行了,而类装饰器则是在类创建之后才运行的。记住这点,让我们来同时运行一下两者,请 注意运行的先后顺序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def my_metaclass(class_name, parents, attributes): print "In metaclass, creating the class." return type (class_name, parents, attributes) def my_class_decorator( class_ ): print "In decorator, chance to modify the class." return class_ @my_class_decorator class C( object ): __metaclass__ = my_metaclass def __init__( self ): print "Creating object." c = C() |
1
2
3
4
|
# Output: In metaclass, creating the class . In decorator, chance to modify the class . Creating object . |
元类的一个实际用例
让我们来考虑一个更有用的实例。假设我们正在构思一个类集合来处理MP3音乐文件中使用到的ID3v2标签Wikipedia。 简而言之,标签由帧(frames)组成,而每帧通过一个四字符的识别码(identifier)进行标记。举个例子,TOPE标识了原作者帧,TOAL 标识了原专辑名称等。如果我们希望为每个帧类型写一个单独的类,并且允许ID3v2标签库用户自定义他们自己的帧类。那么我们可以使用元类来实现一个类工 厂模式,具体实现方式可以这样:
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
30
31
32
|
frametype_class_dict = {} class ID3v2FrameClassFactory( object ): def __new__( cls , class_name, parents, attributes): print "Creating class" , class_name # Here we could add some helper methods or attributes to c c = type (class_name, parents, attributes) if attributes[ 'frame_identifier' ]: frametype_class_dict[attributes[ 'frame_identifier' ]] = c return c @staticmethod def get_class_from_frame_identifier(frame_identifier): return frametype_class_dict.get(frame_identifier) class ID3v2Frame( object ): frame_identifier = None __metaclass__ = ID3v2FrameClassFactory pass class ID3v2TitleFrame(ID3v2Frame): __metaclass__ = ID3v2FrameClassFactory frame_identifier = "TIT2" class ID3v2CommentFrame(ID3v2Frame): __metaclass__ = ID3v2FrameClassFactory frame_identifier = "COMM" title_class = ID3v2FrameClassFactory.get_class_from_frame_identifier( 'TIT2' ) comment_class = ID3v2FrameClassFactory.get_class_from_frame_identifier( 'COMM' ) print title_class print comment_class |
1
2
3
4
5
6
|
# Output: Creating class ID3v2Frame Creating class ID3v2TitleFrame Creating class ID3v2CommentFrame < class '__main__.ID3v2TitleFrame' > < class '__main__.ID3v2CommentFrame' > |
当然了,以上的代码同样可以用类装饰器来完成,以下是对应代码:
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
30
31
32
33
|
frametype_class_dict = {} class ID3v2FrameClass( object ): def __init__( self , frame_id): self .frame_id = frame_id def __call__( self , cls ): print "Decorating class" , cls .__name__ # Here we could add some helper methods or attributes to c if self .frame_id: frametype_class_dict[ self .frame_id] = cls return cls @staticmethod def get_class_from_frame_identifier(frame_identifier): return frametype_class_dict.get(frame_identifier) @ID3v2FrameClass ( None ) class ID3v2Frame( object ): pass @ID3v2FrameClass ( "TIT2" ) class ID3v2TitleFrame(ID3v2Frame): pass @ID3v2FrameClass ( "COMM" ) class ID3v2CommentFrame(ID3v2Frame): pass title_class = ID3v2FrameClass.get_class_from_frame_identifier( 'TIT2' ) comment_class = ID3v2FrameClass.get_class_from_frame_identifier( 'COMM' ) print title_class print comment_class |
1
2
3
4
5
|
Decorating class ID3v2Frame Decorating class ID3v2TitleFrame Decorating class ID3v2CommentFrame < class '__main__.ID3v2TitleFrame' > < class '__main__.ID3v2CommentFrame' > |
如你所见,我们可以直接给装饰器传递参数,而元类却不能。给元类传递参数必须通过属性。正因如此,这里装饰器的解决方案更为清晰,同时也更容易维 护。然而,同时也需要注意当装饰器被调用的时候,类已经建立完毕,这意味着此时就不能够修改其属性了。例如,一旦类建立完成,你就不能够修改 __doc__。来看实际例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
>>> def mydecorator( cls ): ... cls .__doc__ = "Test!" ... return cls ... >>> @mydecorator ... class C( object ): ... """Docstring to be replaced with Test!""" ... pass ... Traceback (most recent call last): File "<stdin>" , line 2 , in <module> File "<stdin>" , line 2 , in mydecorator AttributeError: attribute '__doc__' of 'type' objects is not writable >>> def mymetaclass( cls , parents, attrs): ... attrs[ '__doc__' ] = 'Test!' ... return type ( cls , parents, attrs) ... >>> class D( object ): ... """Docstring to be replaced with Test!""" ... __metaclass__ = mymetaclass ... >>> D.__doc__ 'Test!' |
通过type生成元类
正如我们所说,最基本的元类就是type并且类通常都是type类型。那么问题很自然来了,type类型本身是一种什么类型呢?答案也是type。这也就是说type就是它自身的元类。虽然听起来有点诡异,但这在Python解释器层面而言是可行的。
type自身就是一个类,并且我们可以从它继承出新类。这些生成的类也能作为元类,并且使用它们的类可以得到跟使用type一样的类型。来看以下的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
>>> class meta( type ): ... def __new__( cls , class_name, parents, attributes): ... print "meta.__new__" ... return super (meta, cls ).__new__( cls , class_name, parents, attributes) ... def __call__( self , * args, * * kwargs): ... print "meta.__call__" ... return super (meta, self ).__call__( * args, * * kwargs) ... >>> class C( object ): ... __metaclass__ = meta ... meta.__new__ >>> c = C() meta.__call__ >>> type (C) < class '__main__.meta' > |
请注意当类创建对象时,元类的__call__函数就被调用,进而调用type.__call__创建对象。在下一节,我们将把上面的内容融合在一起。
要点集合
假定一个类C自己的元类为my_metaclass并被装饰器my_class_decorator装饰。并且,假定my_metaclass本身 就是一个类,从type生成。让我们将上面提到的内容融合到一起做一个总结来显示C类以及它的对象都是怎么被创建的。首先,让我们来看看代码:
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
30
31
32
33
|
class my_metaclass( type ): def __new__( cls , class_name, parents, attributes): print "- my_metaclass.__new__ - Creating class instance of type" , cls return super (my_metaclass, cls ).__new__( cls , class_name, parents, attributes) def __init__( self , class_name, parents, attributes): print "- my_metaclass.__init__ - Initializing the class instance" , self super (my_metaclass, self ).__init__( self ) def __call__( self , * args, * * kwargs): print "- my_metaclass.__call__ - Creating object of type " , self return super (my_metaclass, self ).__call__( * args, * * kwargs) def my_class_decorator( cls ): print "- my_class_decorator - Chance to modify the class" , cls return cls @my_class_decorator class C( object ): __metaclass__ = my_metaclass def __new__( cls ): print "- C.__new__ - Creating object." return super (C, cls ).__new__( cls ) def __init__( self ): print "- C.__init__ - Initializing object." c = C() print "Object c =" , c |
现在,你可以花几分钟时间测试一下你的理解,并且猜一猜打印输出的顺序。
首先,让我们来看看Python的解释器是如何阅读这部分代码的,然后我们会对应输出来加深我们的理解。
1. Python首先看类声明,准备三个传递给元类的参数。这三个参数分别为类名(class_name),父类(parent)以及属性列表(attributs)。
2. Python会检查__metaclass__属性,如果设置了此属性,它将调用metaclass,传递三个参数,并且返回一个类。
3. 在这个例子中,metaclass自身就是一个类,所以调用它的过程类似创建一个新类。这就意味着my_metaclass.__new__将首先被调 用,输入四个参数,这将新建一个metaclass类的实例。然后这个实例的my_metaclass.__init__将被调用调用结果是作为一个新的 类对象返回。所以此时C将被设置成这个类对象。
4. 接下来Python将查看所有装饰了此类的装饰器。在这个例子中,只有一个装饰器。Python将调用这个装饰器,将从元类哪里得到的类传递给它作为参数。然后这个类将被装饰器返回的对象所替代。
5. 装饰器返回的类类型与元类设置的相同。
6. 当类被调用创建一个新的对象实例时,因为类的类型是metaclass,因此Python将会调用元类的__call__方法。在这个例子 中,my_metaclass.__call__只是简单的调用了type.__call__,目的是创建一个传递给它的类的对象实例。
7. 下一步type.__call__通过C.__new__创建一个对象。
8. 最后type.__call__通过C.__new__返回的结果运行C.__init__。
9. 返回的对象已经准备完毕。
所以基于以上的分析,我们可以看到调用的顺序如下:my_metaclass.__new__首先被调用,然后是 my_metaclass.__init__,然后是my_class_decorator。至此C类已经准备完毕(返回结果就是C)。当我们调用C来创 建一个对象的时候,首先会调用my_metaclass.__call__(任何对象被创建的时候,Python都首先会去调用其类的__call__方 法),然后C.__new__将会被type.__call__调用(my_metaclass.__call__简单调用了 type.__call__),最后是C.__init__被调用。现在让我们来看看输出:
1
2
3
4
5
6
7
|
- my_metaclass.__new__ - Creating class instance of type < class '__main__.my_metaclass' > - my_metaclass.__init__ - Initializing the class instance < class '__main__.C' > - my_class_decorator - Chance to modify the class < class '__main__.C' > - my_metaclass.__call__ - Creating object of type < class '__main__.C' > - C.__new__ - Creating object . - C.__init__ - Initializing object . Object c = <__main__.C object at 0x1043feb90 > < class '__main__.C' > |
关于元类多说几句
元类,一门强大而晦涩的技法。在GitHub上搜索__metaclass__得到的结果多半是指向”cookbook”或其他Python教学材 料的链接。一些测试用例(诸如Jython中的一些测试用例),或是其他一些写有__metaclass__ = type的地方只是为了确保新类被正常使用了。坦白地说,这些用例都没有真正地使用元类。过滤了下结果,我只能找到两个地方真正使用了元 类:ABCMeta和djangoplugins。
ABCMeta是一个允许注册抽象基类的元类。如果想了解多些请查看其官方文档,本文将不会讨论它。
对于djangoplugins而言,基本的思想是基于这篇文章article on a simple plugin framework for Python,使用元类是为了创建一个插件挂载系统。我并没有对其有深入的研究,不过我感觉这个功能可以使用装饰器来实现。如果你有相关的想法请在 本文后留言。
总结笔记
通过理解元类能够帮助我们更深入的理解Python中类和对象的行为,现实中使用它们的情况可能比文中的例子要复杂得多。大部分元类完成的功能都可 以使用装饰器来实现。所以当你的第一直觉是使用元类来解决你的问题,那么请你停下来先想想这是否必要。如果不是非要使用元类,那么请三思而行。这会使你的 代码更易懂,更易调试和维护。
Good luck :)
Python高级特性(3): Classes和Metaclasses(转)的更多相关文章
- 三、python高级特性(切片、迭代、列表生成器、生成器)
1.python高级特性 1.1切片 list列表 L=['Mli','add','sal','saoo','Lkkl'] L[0:3] #即为['Mli','add','sal'] 从索引0开始 ...
- python高级特性:切片/迭代/列表生成式/生成器
廖雪峰老师的教程上学来的,地址:python高级特性 下面以几个具体示例演示用法: 一.切片 1.1 利用切片实现trim def trim(s): while s[:1] == " &qu ...
- python高级特性和高阶函数
python高级特性 1.集合的推导式 列表推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:[exp for item in collection if codition] if ...
- Python高级特性(2):Closures、Decorators和functools(转)
原文:Python高级特性(2):Closures.Decorators和functools 装饰器(Decorators) 装饰器是这样一种设计模式:如果一个类希望添加其他类的一些功能,而不希望通过 ...
- Python高级特性(1):Iterators、Generators和itertools(转)
译文:Python高级特性(1):Iterators.Generators和itertools [译注]:作为一门动态脚本语言,Python 对编程初学者而言很友好,丰富的第三方库能够给使用者带来很大 ...
- Python高级特性之:List Comprehensions、Generator、Dictionary and set ...
今天帅气的易哥和大家分享的是Pyton的高级特性,希望大家能和我一起学习这门语言的魅力. Python高级特性之:List Comprehensions.Generator.Dictionary an ...
- Python 高级特性介绍 - 迭代的99种姿势 与协程
Python 高级特性介绍 - 迭代的99种姿势 与协程 引言 写这个笔记记录一下一点点收获 测试环境版本: Python 3.7.4 (default, Sep 28 2019, 16:39:19) ...
- Python高级特性(切片,迭代,列表生成式,生成器,迭代器)
掌握了Python的数据类型.语句和函数,基本上就可以编写出很多有用的程序了. 比如构造一个1, 3, 5, 7, ..., 99的列表,可以通过循环实现: L = [] n = 1 while n ...
- Python 高级特性(1)- 切片
前言 面 tx 被问到 python 的高级特性相关,这里做个补充学习吧 正向范围取值 关键点 首位下标是 0 第一个数字是起始下标,第二个数字是结束下标(但最终结果不包含它) 代码块一 # 正向范围 ...
随机推荐
- array2json
原文:jQuery方法扩展:type, toJSON, evalJSON. http://zhkac.iteye.com/blog/499330 .2013-05-19 (function($) { ...
- cocos2dx场景切换的坑
有一个类可以使用不同的数据源,每个数据源对应一个对象. 我在类里保存了对象的实例,由于要在其它地方使用所以做成了静态,并在每次初始化时 重新设置,析构时删除. 现在我打开了A,切换到B,结果这个静态的 ...
- RDD缓存学习
首先实现rdd缓存 准备了500M的数据 10份,每份 100万条,存在hdfs 中通过sc.textFile方法读取 val rdd1 = sc.textFile("hdfs://mini ...
- linux -- camera shot 拍照功能
#include <stdio.h> #include <string.h> #include <errno.h> #include <stdlib.h> ...
- 页面置换算 - FIFO、LFU、LRU
缓存算法(页面置换算法)-FIFO.LFU.LRU 在前一篇文章中通过leetcode的一道题目了解了LRU算法的具体设计思路,下面继续来探讨一下另外两种常见的Cache算法:FIFO.LFU 1 ...
- [HTML5] 手机摇一摇实现
目录结构 引入jQuery:jquery-1.11.1.min.js html代码 <!DOCTYPE html> <html lang="en"> < ...
- Statement和PreparedStatement都是用来发送和执行SQL语句的
Statement和PreparedStatement都是用来发送和执行SQL语句的 DriverManager管理一组驱动程序
- Unity3d优化总结1
优化内容 包括: 1.代码方面: 2.函数使用方面: 3.ngui注意方面: 4.数学运算方面: 5.内存方面: 6.垃圾回收方面 等等... 1. 尽量避免每帧处理,可以每隔几帧处理一次 比如: ...
- CSS顶级技巧大放送,div+css布局必知
字体大小使用px 在一行内声明CSS 对比下面两个: h2 {font-size:18px; border:1px solid blue; color:#000; } h2 { font-siz ...
- Ubuntu 16.04 LTS 安装libvips出现”Package vips was not found in the pkg-config search path”
使用libvips来操作图像,libvips的部署参考一个Node.js工程:https://github.com/lovell/sharp 在MAC下安装很顺利,到Linux环境下(Ubuntu 1 ...