元编程
2019年6月5日大约 4 分钟约 1202 字
概念
简而言之,元编程就是创建对源代码进行操作(比如修改、生成或包装原来的代码)的函数和类的编程技术。
元编程的主要技术方式:
- 装饰器
- 类装饰器
- 元类
元编程的一些其他的技术方式:
- 签名对象
- 使用exec()执行代码
- 对内部函数和类的反射技术
元类
概念
元类(metaclass),一种用于创建类的类:
- 类定义包含类名、类字典和基类列表。元类负责接受上述三个参数并创建相应的类。
- 大部分面向对象的编程语言都会提供一个默认实现。Python 的特别之处在于可以创建自定义元类
- 大部分用户永远不需要这个工具,但当需要出现时,元类可提供强大而优雅的解决方案。它们已被用于记录属性访问日志、添加线程安全性、跟踪对象创建、实现单例,以及其他许多任务。
自定义元类
如果你想自定义用类创建实例的步骤,可以定义一个元类并自己实现__call__()
方法。利用元类实现多种实例创建模式通常要比不用元类的方式优雅得多。
# 假设你不想任何人创建这个类的实例
class NoInstance(type):
def __call__(self, *args, **kwargs):
raise TypeError("Can't instantiate directly")
# Example
class Spam(metaclass=NoInstance):
@staticmethod
def grok(x):
print('Spam.grok')
# 用户只能调用这个类的静态方法而不能用通常的方法来创建它的实例了
>>> Spam.grok(42)
Spam.grok
>>> s = Spam()
Traceback (most recent call last):
...
TypeError: Can't instantiate directly
利用元类可以很容易地捕获类的定义信息。下面的例子使用了一个OrderedDict
来记录描述器的定义顺序,对于很多程序而言,能够捕获类定义的顺序是一个看似不起眼却又非常重要的特性:
from collections import OrderedDict
# A set of descriptors for various types
class Typed:
_expected_type = type(None)
def __init__(self, name=None):
self._name = name
def __set__(self, instance, value):
if not isinstance(value, self._expected_type):
raise TypeError('Expected ' + str(self._expected_type))
instance.__dict__[self._name] = value
class Integer(Typed):
_expected_type = int
class Float(Typed):
_expected_type = float
class String(Typed):
_expected_type = str
# Metaclass that uses an OrderedDict for class body
class OrderedMeta(type):
def __new__(cls, clsname, bases, clsdict):
d = dict(clsdict)
order = []
for name, value in clsdict.items():
if isinstance(value, Typed):
value._name = name
order.append(name)
f['_order'] = order
return type.__new__(cls, clsname, bases, d)
@classmethod
def __prepare__(cls, clsname, bases):
return OrderedDict()
定义有可选参数的元类
在定义类的时候,python 允许我们使用metaclass
关键字参数来指定特定的元类,在自定义元类中也可以提供其他的关键字参数。
from abc import ABCMeta, abstractmethod
class IStream(metaclass=ABCMeta):
@abstractmethod
def read(self, maxsize=None):
pass
@abstractmethod
def write(self, data):
pass
class Spam(metaclass=MyMeta, debug=True, synchronize=True):
pass
为了使元类支持这些关键字参数,必须确保在
__prepare__()
,__new__()
和__init__()
方法中都使用强制关键字参数:class MyMeta(type): # Optional @classmethod def __prepare__(cls, name, bases, *, debug=False, synchronize=False): pass return super().__prepare__(name, bases) # Required def __new__(cls, name, bases, ns, *, debug=False, synchronize=False): pass return super().__new__(cls, name, bases, ns) # Required def __init__(self, name, bases, ns, *, debug=False, synchronize=False): pass super().__init__(name, bases, ns)
给一个元类添加可选关键字参数需要你完全弄懂类创建的所有步骤,因为这些参数会被传递给每一个相关的方法。
__prepare__()
方法在所有类定义开始执行前首先被调用,用来创建类命名空间。通常来讲,这个方法只是简单地返回一个字典或者其他映射对象;__new__()
方法被用来实例化最终的类对象,它在类的主体被执行完后开始执行;__init__()
方法最后被调用,用来执行其他的一些初始化工作。- 当我们构造元类的时候,通常只需要定义一个
__new__()
或__init__()
方法,而不是两个都定义;但是,如果需要接受其他的关键字参数的话,这两个方法就要同时提供,并且都要提供对应的参数签名。 - 默认的
__prepare__()
方法接收任意的关键字参数,但是会忽略它们,所以只有当这些额外的参数可难会影响到类命名空间的创建时你才需要去定义__prepare__()
方法。
使用关键字参数配置一个元类还可以视作对类变量的一种替代方式。将这些属性定义为参数的好处在于它们不会污染类的名称空间,这些属性只从属于类的创建阶段,而不是类中语句的执行阶段。
class Spam(metaclass=MyMeta): debug = True synchronize = True pass
后面元类的介绍实在太复杂了,暂且不看了吧。——《Python Cookbook》9.16 节之后