Python 高手编程系列一百二十九:槽

有一个有趣的特性几乎从未被开发人员使用过,就是槽(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)。
装饰器 — 一种元编程方法
第 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 对多重继承的处理相对较好。但如果仅因为不需要额外工作且使代码变得简单
的话,最好避免将多个类子类化。这也是类装饰器能很好地代替混入类的原因。

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值