property 提供了一个内置的描述符类型,它知道如何将一个属性链接到一组方法上。 property 接受 4 个可选参数:fget、fset、fdel 和 doc。最后一个参数可以用来定义 一个链接到属性的 docstring,就像是一个方法一样。下面是一个 Rectangle 类的例子, 其控制方法有两种,一种是直接访问保存两个顶点的属性,另一种是利用 width 和 height。这两个 property,如下所示: class Rectangle: def init(self, x1, y1, x2, y2): self.x1, self.y1 = x1, y1 self.x2, self.y2 = x2, y2 def _width_get(self): return self.x2 - self.x1 def _width_set(self, value): self.x2 = self.x1 + value def _height_get(self): return self.y2 - self.y1 def _height_set(self, value): self.y2 = self.y1 + value width = property( _width_get, _width_set, doc="rectangle width measured from left" ) height = property( _height_get, _height_set, doc="rectangle height measured from top" ) def repr(self): return "{}({}, {}, {}, {})".format( self.class.name, self.x1, self.y1, self.x2, self.y2 ) 在交互式会话中,使用上述定义的 property 的示例如下:

rectangle = Rectangle(10, 10, 25, 34) rectangle.width, rectangle.height (15, 24) rectangle.width = 100 rectangle Rectangle(10, 10, 110, 34) rectangle.height = 100 rectangle Rectangle(10, 10, 110, 110) help(Rectangle) Help on class Rectangle in module chapter3: class Rectangle(builtins.object) | Methods defined here: | | __init __(self, x1, y1, x2, y2) | Initialize self. See help(type(self)) for accurate signature. | | __repr __(self) | Return repr(self). | | -------------------------------------------------------- | Data descriptors defined here: | (...) | | height | rectangle height measured from top | | width | rectangle width measured from left property 简化了描述符的编写,但在使用类的继承时必须小心处理。所创建的属性 是利用当前类的方法实时创建,不会使用派生类中覆写的方法。 例如,下面的例子将无法覆写父类(Rectangle)widthproperty 的fget 方法的实现: class MetricRectangle(Rectangle): ... def _ width _get(self): ... return "{} meters".format(self.x2 - self.x1) ... Rectangle(0, 0, 100, 100).width 100 为了解决这一问题,只需要在派生类中覆写整个 property,如下所示:

class MetricRectangle(Rectangle): ... def _ width get(self): ... return "{} meters".format(self.x2 - self.x1) ... width = property( width _get, Rectangle.width.fset) ... MetricRectangle(0, 0, 100, 100).width '100 meters' 不幸的是,上面的代码有一些可维护性的问题。如果开发人员决定要更改父类,但却 忘记修改 property 调用的话,就会出现问题。这就是为什么不建议仅覆写 property 的部分行为。如果需要修改property的工作方式,推荐在派生类中覆写所有的property 方法,而不是依赖父类的实现。在大多数情况下,这是唯一的选择,因为如果修改了 property 的 setter 行为的话,通常意味着也需要修改 getter 的行为。 基于上述原因,创建 property 的最佳语法是使用 property 作为装饰器。这会减少 类内部方法签名的数量,并提高代码的可读性和可维护性,如下所示: class Rectangle: def init(self, x1, y1, x2, y2): self.x1, self.y1 = x1, y1 self.x2, self.y2 = x2, y2 @property def width(self): """rectangle height measured from top""" return self.x2 - self.x1 @width.setter def width(self, value): self.x2 = self.x1 + value @property def height(self): """rectangle height measured from top""" return self.y2 - self.y1 @height.setter def height(self, value): self.y2 = self.y1 + value

有一个有趣的特性几乎从未被开发人员使用过,就是槽(slots)。它允许你使用__slots__

属性来为指定的类设置一个静态属性列表,并在类的每个实例中跳过__dict__字典的创建过程。它可以为属性很少的类节约内存空间,因为每个实例都没有创建__dict__。 除此之外,它还有助于设计签名需要被冻结的类。例如,如果你需要限制一个类的语 言动态特性,那么定义槽可以有所帮助:

class Frozen: ... __slots __ = ['ice', 'cream'] ... ' __dict __' in dir(Frozen) False 'ice' in dir(Frozen) True frozen = Frozen() frozen.ice = True frozen.cream = None frozen.icy = True Traceback (most recent call last): File "", line 1, in AttributeError: 'Frozen' object has no attribute 'icy' 这一特性应该谨慎使用。如果使用__slots__限制一组可用的属性,那么向对象动态 添加内容会变得更加困难。对于定义了槽的类实例而言,某些技术(例如猴子补丁)将无 法使用。幸运的是,可以向派生类中添加新属性,如果它没有定义自己的槽的话: class Unfrozen(Frozen): ... pass ... unfrozen = Unfrozen() unfrozen.icy = False unfrozen.icy False

元编程

在一些学术论文里可能有对元编程(metaprogramming)很好的定义,我们本可以在这

里引用,但本书更为关注优秀的软件工艺,而不是计算机科学理论。所以我们将使用以下

简单的定义:

“元编程是一种编写计算机程序的技术,这些程序可以将自己看作数据,因此

你可以在运行时对它进行内省、生成和/或修改。”

利用这一定义,是我们可以区分 Python 元编程的两种主要方法。

第一种方法专注于语言对基本元素(例如函数、类或类型)内省的能力与对其实时创建或修改的能力。Python 为这一领域的开发人员提供了大量工具。最简单的工具就是装饰

器,允许向现有函数、方法或类中添加附加功能。然后是类的特殊方法,允许你修改类实例

的创建过程。最强大的工具是元类,甚至允许程序员完全重新设计 Python 面向对象编程范式

的实现。这里我们也精心选择了不同的工具,允许程序员直接处理代码,或者是原始的纯文

本格式,或者是以编程方式更容易访问的抽象语法树(Abstract Syntax Tree,AST)形式。第

二种方法当然更加复杂,也更难以处理,但可以用来完成不同凡响的任务,例如扩展 Python

语言的语法,甚至创建你自己的领域特定语言(Domain Specific Language,DSL)。