0
点赞
收藏
分享

微信扫一扫

有一个有趣的特性几乎从未被开发人员使用过,就是槽(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 "<input>", line 1, in <module>

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)。

装饰器—一种元编程方法

第 2 章介绍过装饰器语法,其简单形式如下:

def decorated_function():

pass

decorated_function = some_decorator(decorated_function)

这清楚地展示了装饰器的作用。它接受一个函数对象,并在运行时修改它。其结果就

是,基于前一个函数对象创建了一个同名的新函数(或其他任何内容)。这甚至可以是一个

复杂的操作,根据原始函数的实现方式来执行内省并给出不同的结果。这都说明装饰器可

以被看作一种元编程工具。

这是个好消息。装饰器相对容易理解,在多数情况下可以使代码更短、更容易阅读,

维护成本也更低。Python 中其他可用的元编程工具要更加难以掌握。而且,它们可能也不

会使代码变得简单。

类装饰器

Python 有一个不太为人所知的语法特性,就是类装饰器。其语法和工作方式都与第 2

章介绍的函数装饰器完全相同。唯一的区别在于它的返回值是一个类,而不是函数对象。

下面是一个类装饰器的例子,修改__repr__()方法并返回缩短的可打印对象表示,缩短后

的长度可任意取值,如下所示:

def short_repr(cls):

cls.__repr__ = lambda self: super(cls, self).__repr__()[:8]

return cls

@short_repr

class ClassWithRelativelyLongName:

pass

你将会看到以下输出:

>>> ClassWithRelativelyLongName()

<ClassWi

当然,上面的代码片段并不是很好的代码示例,因为其含义过于模糊。不过,它展示

了本章介绍的多种语言特性可以综合使用。

• 在运行时不仅可以修改实例,还可以修改类对象。

• 函数也是描述符,之所以也可以在运行时添加到类中,是因为根据描述符协议,在

属性查找时将执行实际绑定的实例。

• 只要提供了正确的参数,super()调用可以在类定义作用域之外使用。

• 最后,类装饰器可以用于类的定义。

编写函数装饰器的其他内容也适用于类装饰器。最重要的是,它可以使用闭包,也可

以被参数化。利用这一点,可以将上一个例子重写成更加易于阅读和维护的形式:

def parametrized_short_repr(max_width=8):

"""缩短表示的参数化装饰器"""

def parametrized(cls):

"""内部包装函数,是实际的装饰器"""

class ShortlyRepresented(cls):

"""提供装饰器行为的子类"""

def __repr__(self):

return super().__repr__()[:max_width]


return ShortlyRepresented

return parametrized

在类装饰器中这样使用闭包的主要缺点是,生成的对象不再是被装饰的类的实例,而是在

装饰器函数中动态创建的子类的实例。这会影响类的__name__和__doc__等属性,如下所示:

@parametrized_short_repr(10)

class ClassWithLittleBitLongerLongName:

pass

类装饰器的这种用法会使类的元数据发生以下变化:

>>> ClassWithLittleBitLongerLongName().__class__

<class 'ShortlyRepresented'>

>>> ClassWithLittleBitLongerLongName().__doc__

'Subclass that provides decorated behavior'

不幸的是,这个问题不能用第 2 章“(4)保存内省的装饰器”一节介绍的方法(使用额外

的 wraps 装饰器)简单解决。这样的话,在某些情况下以这种形式使用类装饰器会受到限制。

如果没有做其他工作来保存旧类的元数据,那么这可能会破坏许多自动生成文档工具的结果。

虽然有这样的警告,但类装饰器仍然是对流行的混入(mixin)类模式的一种简单又轻

量级的替代方案。

Python 中的混入类是一种不应被初始化的类,而是用来向其他现有类提供某种可复用

的 API 或功能。混入类几乎总是使用多重继承来添加,其形式如下:

class SomeConcreteClass(MixinClass, SomeBaseClass):

pass

混入类是很有用的设计模式,在许多库中都有应用。举个例子,Django 就是大量使用

这种模式的框架之一。虽然混入类很有用也很流行,但如果设计不好的话可能会导致一些

麻烦,因为大部分情况下都需要开发人员依赖多重继承。我们前面说过,由于 MRO 的存

在,Python 对多重继承的处理相对较好。但如果仅因为不需要额外工作且使代码变得简单

的话,最好避免将多个类子类化。这也是类装饰器能很好地代替混入类的原因。

举报

相关推荐

0 条评论