3.6 总结、设计要素和折中方案
在本章中,我们看了一些对象属性的工作方式。我们可以使用object类中已经定义好的功能来获取和设置属性值,可通过定义特性来改变属性的行为。
对于更复杂的情况,可以重写getattr()、setattr()和delattr()或getattribute()函数的实现。这样一来,可以从根本上更细粒度地控制(也可能带来疑惑)Python的行为。
Python在内部使用了修饰符实现一些函数、静态方法和特性。对于一些场景,使用修饰符显得更自然,也体现了一种编程语言的优势。
其他语言(尤其Java和C++)的程序员通常一开始要把所有的属性定义为私有的,并编写可扩展的getter和setter函数。对于在编译期处理类型定义的语言来说,这种编码方式是可取的。
在Python中,建议把所有的属性公有,这意味着如下几点。
- 此处应当有很好的文档说明。
- 此处应能够正确地反映出对象的状态,它们不应当是暂时性的或者临时变量。
- 在少数情形下,属性包含了容易产生歧义的值,可使用单下划线(_)来标记为“不在接口定义范围内”,以此表明它并不是真正意义上的私有。
私有属性是令人厌烦的。封装,并不会因为某种编程语言缺乏一种复杂的私有机制而被破坏,它只会被糟糕的设计所破坏。
3.6.1 特性与属性对比
在大多数情况,属性附加在类的外部。之前的Hand类的例子中演示了这一点。使用这个类的其他版本时,只需简单地把对象添加到hand.cards中,使用延迟计算方式实现的total特性,就可以有效地工作了。
有时对一个属性值的改变会造成其他属性值的改变,需要更复杂的类定义。
- 选择在函数内部维护状态的改变,当函数定义包含多个参数值时可以考虑这种做法。
- 一个setter特性或许比一个函数要表达的意图更清晰。当只需访问一个值时,这个做法是明智的。
- 我们也可以使用原地运算符,在第7章“创建数值类型”中会介绍。
在使用方面没有严格的规定。这样一来,当需要为单一参数赋值时,在方法函数与属性之间的区别在API语法上的差异以及在传达意图上是否有更好的方式。
对于已经计算的值,特性允许延迟计算,然而属性却需要主动计算。这需要在性能上进行考虑,至于使用哪一种还要看具体的应用场景。
3.6.2 修饰符的设计
Python中定义了一些修饰符,我们并不需要重新创建特性、类方法或静态方法。
创建修饰符的典型例子是完成Python和非Python之间的映射。例如,对于对象关系数据库映射,需要在Python类中定义大量的属性,并确保它们的顺序和SQL数据表中列的顺序是一致的。再如,当需要完成Python之外的映射时,修饰符类可以用来完成编码和解码的工作,或从外部资源中获取数据。
当创建一个网络服务客户端时,可以考虑使用修饰符来完成网络请求。可考虑使用get()方法构造HTTP GET请求对象,也可使用set()方法来构造HTTP PUT请求对象。
在一些情形下,一个请求的构造可能需要由多个修饰符来完成。这样的话,在构造一个新的HTTP请求对象之前,get()函数可以先从缓存中获取可用的实例。
许多数据修饰符的操作使用特性会更容易一些,可以这样的思考顺序来设计:先考虑特性。如果特性的逻辑非常复杂,可以考虑使用修饰符或对类进行重构。
3.6.3 展望
在下一章中,会着重介绍抽象基类(Abstract Base Classes,ABC),在第5章、第6章和第7章也会深入探讨。这些抽象基类会帮助我们定义一些可以和Python机制无缝集成的类,这也使得类层次结构的设计能够在一致性设计和扩展性上发挥更大的作用。
