- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议62:掌握 metaclass
什么是元类(metaclass)?也许我们对下面这些说法都不陌生:
元类是关于类的类,是类的模板。
元类是用来控制如何创建类的,正如类是创建对象的模板一样。
元类的实例为类,正如类的实例为对象。
这些说法都没有错,在概念之外我们来进行一些更深入的探讨:元类是如何来控制类的创建的?用户该如何定义自己的元类?在哪些情况下需要用到元类?使用元类可以解决什么问题?
我们知Python中一切皆对象,类也是对象,可以在运行的时候动态创建。当使用关键字class的时候,Python解释器在执行的时候就会创建一个对象(这里的对象是指类而非类的实例)。
>>> def dynamic_class(name): ... if name == 'A': ... class A(object): ... pass ... return A ... else: ... class B(object): ... pass ... return B ... >>> >>> UserClass = dynamic_class('A') >>> print UserClass <class '__main__.A'> >>> UserClass() <__main__.A object at 0x00D67CF0> >>>
既然类是对象,那么它就有其所属的类型,也一定还有什么东西能够控制它的生成。通过type查看会发现UserClass的类型为type,而其对象UserClass()的类型为类A。
>>> type(UserClass) <type 'type'> >>> type(UserClass()) <class '__main__.A'>
同时我们知道type还可以这样使用:
type( 类名, 父类的元组(针对继承的情况,可以为空), 包含属性的字典(名称和值))
例如:
>>> A=type('A',(object,),{'value':2}) >>> A.value >>> print A <class '__main__.A'> >>> class C(A): ... pass ... >>> print C <class '__main__.C'> >>> >>> print C.__class__ <type 'type'>
上例中type通过接受类的描述作为参数返回一个对象,这个对象可以被继承,属性能够被访问,它实际是一个类,其创建由type控制,由type所创建的对象的__class__属性为type。type实际上是Python的一个内建元类,用来直接指导类的生成。当然,除了使用内建元类type,用户也可以通过继承type来自定义元类。我们来看一个利用元类实现强制类型检查的例子。
class TypeSetter(object): def __init__(self,fieldtype): print "set attribute type",fieldtype self.fieldtype = fieldtype def is_valid(self,value): return isinstance(value,self.fieldtype) class TypeCheckMeta(type): def __new__(cls,name,bases,dict): print '-----------------------------------' print "Allocating memory for class", name print name print bases print dict return super(TypeCheckMeta, cls).__new__(cls,name,bases,dict) def __init__(cls,name,bases,dict): cls._fields = {} for key,value in dict.items(): if isinstance(value,TypeSetter): cls._fields[key] = value def sayHi(cls): print "Hi"
TypeSetter用来设置属性的类型,TypeCheckMeta为用户自定义的元类,覆盖了type元类中的__new__()方法和__init__()方法。虽然也可以直接使用TypeCheckMeta(name,bases,dict)这种方式来创建类,但更为常见的是在需要被生成的类中设置__metaclass__属性,两种用法是等价的。
class TypeCheck(object): __metaclass__ = TypeCheckMeta def __setattr__(self,key,value): print "set attribute value" if key in self._fields: if not self._fields[key].is_valid(value): raise TypeError('Invalid type for field') super(TypeCheck,self).__setattr__(key,value) class MetaTest(TypeCheck): name = TypeSetter(str) num = TypeSetter(int) mt = MetaTest() mt.name = "apple" mt.num = "test"
当类中设置了__metaclass__属性的时候,所有继承自该类的子类都将使用所设置的元类来指导类的生成,因此上述程序的输出如下:
----------------------------------- Allocating memory for class TypeCheck TypeCheck (<type 'object'>,) {'__module__':'__main__','__metaclass__':<class '__main__.TypeCheckMeta'>, '_ _setattr__': <function __setattr__ at 0x00D61830>} set attribute type <type 'str'> set attribute type <type 'int'> ----------------------------------- Allocating memory for class MetaTest MetaTest (<class '__main__.TypeCheck'>,) {'__module__':'__main__','num': <__main__.TypeSetter object at 0x00D67E70>, 'n ame': <__main__.TypeSetter object at 0x00D67E50>} set attribute value set attribute value Traceback (most recent call last): File "metatest.py", line 38, in <module> mt.num = "test" File "metatest.py", line 28, in __setattr__ raise TypeError('Invalid type for field') TypeError: Invalid type for field
实际上,在新式类中当一个类未设置__metaclass__属性的时候,它将使用默认的type元类来生成类。而当该属性被设置时查找规则如下:
1)如果存在dict['__metaclass__'],则使用对应的值来构建类;否则使用其父类dict['__metaclass__']中所指定的元类来构建类,当父类中也不存在指定的metaclass的情形下使用默认元类type。
2)对于古典类,条件1不满足的情况下,如果存在全局变量__metaclass__,则使用该变量所对应的元类来构建类;否则使用types.ClassType。
读者可以通过将上述例子中__metaclass__=TypeCheckMeta设置为模块级别或者将TypeCheck改为古典类来验证上述查找规则。
需要额外提醒的是,元类中所定义的方法为其所创建的类的类方法,并不属于该类的对象。因此上例中mt.sayHi()会抛出AttributeError: 'MetaTest' object has no attribute 'sayHi'错误,而调用该方法的正确途径为MetaTest.sayHi()。
那么在什么情况下会用到元类呢?有句话是这么说的:当你面临一个问题还在纠结要不要使用元类的时候,往往会有其他的更为简单的解决方案。
Python界的领袖Tim Peters曾这样说过:“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。”
我们来看几个使用元类的场景。
1)利用元类来实现单例模式。
class Singleton(type): def __init__(cls,name,bases,dic): super(Singleton,cls).__init__(name,bases,dic) cls.instance = None def __call__(cls,*args,**kwargs): if cls.instance is None: print "creating a new instance" cls.instance = super(Singleton,cls).__call__ (*args,**kwargs) else: print "warning:only allowed to create one instance,minstance already exists!" return cls.instance class MySingleton(object): __metaclass__ = Singleton
2)第二个例子来源于Python的标准库string.Template.string,它提供简单的字符串替换功能。常见的使用例子如下:
Template('$name $age').substitute({'name':'admin'}, age=26)
该标准库的源代码中就用到了元类,Template的元类为_TemplateMetaclass。_TemplateMetaclass的__init__()方法通过查找属性(pattern、delimiter和idpattern)并将其构建为一个编译好的正则表达式存放在pattern属性中。用户如果需要自定义分隔符(delimiter)可以通过继承Template并覆盖它的类属性delimiter来实现。string.Template的部分源代码如下:
class Template: """A string class for supporting $-substitutions.""" __metaclass__ = _TemplateMetaclass delimiter = '$' idpattern = r'[_a-z][_a-z0-9]*' def __init__(self, template): self.template = template class _TemplateMetaclass(type): pattern = r""" %(delim)s(?: (?P<escaped>%(delim)s) | # Escape sequence of two delimiters (?P<named>%(id)s) | # delimiter and a Python identifier {(?P<braced>%(id)s)} | # delimiter and a braced identifier (?P<invalid>) # Other ill-formed delimiter exprs ) """ def __init__(cls, name, bases, dct): super(_TemplateMetaclass, cls).__init__(name, bases, dct) if 'pattern' in dct: pattern = cls.pattern else: pattern = _TemplateMetaclass.pattern % { 'delim' : _re.escape(cls.delimiter), 'id' : cls.idpattern, } cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)
另外在Django ORM、AOP编程中也有大量使用元类的情形。最后来谈谈关于元类需要注意的几点:
1)区别类方法与元方法(定义在元类中的方法)。我们先来看一个例子:Meta和SubMeta都为元类,其中SubMeta继承自Meta。因此f1、f2都为元方法,而Test为普通类,其元类设置为SubMeta,f3为类方法。
>>> class Meta(type): ... def f1(cls): ... print "This is f1()" ... >>> class SubMeta(Meta): ... def f2(cls): ... print "This is f2()" ... >>> >>> class Test(object): ... __metaclass__ = SubMeta ... @classmethod ... def f3(cls): ... print " I am f3()" ... >>> >>> t= Test() >>> SubMeta.f1(Test) This is f1() >>> Meta.f1(Test) This is f1() >>> Test.f1() This is f1() >>> SubMeta.f2(Test) This is f2() >>> Test.f2() This is f2() >>> t.f2() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Test' object has no attribute 'f2' >>> Test.f3() I am f3() >>> t.f3() I am f3() >>>
上面的例子说明,元方法可以从元类或者类中调用,而不能从类的实例中调用;但类方法可以从类中调用,也可以从类的实例中调用。
2)多继承需要严格限制,否则会产生冲突。
>>> class M1(type): ... def __new__(meta, name, bases, atts): ... print "M1 called for " + name ... return super(M1, meta).__new__(meta, name, bases, atts) ... >>> class C1(object): ... __metaclass__ = M1 ... M1 called for C1 >>> >>> class Sub1(C1):pass ... M1 called for Sub1 >>> class M2(type): ... def __new__(meta, name, bases, atts): ... print "M2 called for " + name ... return super(M2, meta).__new__(meta, name, bases, atts) ... >>> class C2(object): ... __metaclass__ = M2 ... M2 called for C2 >>> class Sub2(C1, C2): ... pass ... M1 called for Sub2 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in __new__ 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
上面的例子中当Sub2同时继承自元类C1和C2的时候会抛出异常,这是因为Python解释器并不知道C1和C2是否兼容,因此会发出冲突警告。解决冲突的办法是重新定义一个派生自M1和M2的元类,并在C3中将其__metaclass__属性设置为该派生类。
>>> class M3(M1, M2): ... def __new__(meta, name, bases, atts): ... print "M3 called for " + name ... return super(M3, meta).__new__(meta, name, bases, atts) ... >>> class C3(C1, C2): ... __metaclass__ = M3 ... M3 called for C3 M1 called for C3 M2 called for C3
注意
元类用来指导类的生成,元方法可以从元类或者类中调用,不能从类的实例中调用,而类方法既可以从类中调用也可以从类的实例中调用。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论