您在这里:首页 > 深入 Python > 对象和面向对象 > 探索 UserDict:一个包装类 | << >> | ||||
深入 Python从 Python 新手到专家 |
正如您所见,FileInfo 是一个表现得像字典的类。为了进一步探讨这一点,让我们来看看 UserDict 模块中的 UserDict 类,它是 FileInfo 类的父类。这没什么特别的;这个类是用 Python 编写的,并像任何其他 Python 代码一样存储在一个 .py 文件中。具体来说,它存储在您的 Python 安装目录下的 lib 目录中。
![]() |
|
在 Windows 上的 ActivePython IDE 中,您可以通过选择 -> (Ctrl-L) 快速打开库路径中的任何模块。 |
class UserDict:def __init__(self, dict=None):
self.data = {}
if dict is not None: self.update(dict)
![]()
![]()
![]() |
请注意,UserDict 是一个基类,它没有继承自任何其他类。 |
![]() |
这是您在 FileInfo 类中重写 的 __init__ 方法。请注意,此父类中的参数列表与子类中的参数列表不同。没关系;每个子类都可以有自己的一组参数,只要它使用正确的参数调用父类即可。在这里,父类有一种定义初始值的方法(通过在 dict 参数中传递字典),而 FileInfo 没有使用这种方法。 |
![]() |
Python 支持数据属性(在 Java 和 Powerbuilder 中称为“实例变量”,在 C++ 中称为“成员变量”)。数据属性是由类的特定实例保存的数据片段。在这种情况下,UserDict 的每个实例都将有一个数据属性 data。要从类外部的代码中引用此属性,可以使用实例名称对其进行限定,例如 instance.data,这与使用模块名称限定函数的方式相同。要从类内部引用数据属性,请使用 self 作为限定符。按照惯例,所有数据属性都在 __init__ 方法中初始化为合理的值。但是,这不是必需的,因为数据属性(如局部变量)在 首次赋值时 就会存在。 |
![]() |
update 方法是一个字典复制器:它将一个字典中的所有键和值复制到另一个字典中。这不会 首先清除目标字典;如果目标字典中已经有一些键,则来自源字典中的键将被覆盖,但其他键将保持不变。将 update 视为合并函数,而不是复制函数。 |
![]() |
这是一种您以前可能没有见过的语法(我在本书的示例中没有使用过)。它是一个 if 语句,但它没有在下一行开始缩进的代码块,而是在冒号后面的同一行只有一个语句。这是完全合法的语法,当代码块中只有一个语句时,可以使用这种快捷方式。(这就像在 C++ 中指定一个没有花括号的语句一样。)您可以使用这种语法,也可以在后续行中使用缩进代码,但不能对同一个代码块同时使用这两种语法。 |
![]() |
|
Java 和 Powerbuilder 支持按参数列表进行函数重载,即 一个类可以有多个同名但参数数量不同或参数类型不同的方法。其他语言(最著名的是 PL/SQL)甚至支持按参数名称进行函数重载;即 一个类可以有多个同名、参数数量相同、参数类型相同但参数名称不同的方法。Python 不支持这两种方法中的任何一种;它没有任何形式的函数重载。方法仅由其名称定义,并且每个类中只能有一个具有给定名称的方法。因此,如果子类有一个 __init__ 方法,那么它总是 会覆盖父类的 __init__ 方法,即使子类使用不同的参数列表定义它也是如此。同样的规则也适用于任何其他方法。 |
![]() |
|
Python 的原作者 Guido 对方法重写的解释如下:“派生类可以覆盖其基类的方法。因为方法在调用同一个对象的其它方法时没有特殊权限,所以一个基类的方法调用同一个基类中定义的另一个方法时,实际上可能会调用派生类中重写该方法的方法。(对于 C++ 程序员来说:Python 中的所有方法实际上都是虚方法。)” 如果您不明白这句话(它把我搞糊涂了),请随意忽略它。我只是想把它传递下去。 |
![]() |
|
始终在 __init__ 方法中为实例的所有数据属性分配初始值。这将为您节省以后数小时的调试时间,避免因为引用未初始化(因此不存在)的属性而跟踪 AttributeError 异常。 |
def clear(self): self.data.clear()def copy(self):
if self.__class__ is UserDict:
return UserDict(self.data) import copy
return copy.copy(self) def keys(self): return self.data.keys()
def items(self): return self.data.items() def values(self): return self.data.values()
![]() |
clear 是一个普通的类方法;它可以被任何人随时公开调用。请注意,clear 像所有类方法一样,将 self 作为其第一个参数。(请记住,在调用方法时不需要包含 self;这是 Python 为您添加的。)还要注意这个包装类的基本技术:将一个真实的字典(data)存储为数据属性,定义真实字典拥有的所有方法,并让每个类方法重定向到真实字典上的相应方法。(如果您忘记了,字典的 clear 方法 会删除其所有键 及其关联的值。) |
![]() |
真实字典的 copy 方法返回一个新的字典,该字典是原始字典的精确副本(所有键值对都相同)。但是 UserDict 不能简单地重定向到 self.data.copy,因为该方法返回一个真实的字典,而您想要返回的是一个与 self 类相同的新实例。 |
![]() |
您可以使用 __class__ 属性来查看 self 是否是 UserDict;如果是,那就太好了,因为您知道如何复制 UserDict:只需创建一个新的 UserDict 并将您存储在 self.data 中的真实字典传递给它即可。然后,您立即返回新的 UserDict,您甚至不需要进入下一行的 import copy。 |
![]() |
如果 self.__class__ 不是 UserDict,那么 self 必须是 UserDict 的某个子类(例如 FileInfo),在这种情况下,事情就变得棘手了。UserDict 不知道如何精确复制其后代;例如,子类中可能定义了其他数据属性,因此您需要遍历它们并确保复制所有属性。幸运的是,Python 附带了一个模块来完成这项工作,它叫做 copy。我在这里不会详细介绍(不过如果你有兴趣,可以自己深入研究一下,它是一个非常酷的模块)。只需说 copy 可以复制任意的 Python 对象,这就是你在这里使用它的方式。 |
![]() |
其余的方法都很简单,将调用重定向到 self.data 上的内置方法。 |
![]() |
|
在 2.2 之前的 Python 版本中,不能直接子类化字符串、列表和字典等内置数据类型。为了弥补这一点,Python 附带了模拟这些内置数据类型行为的包装类:UserString、UserList 和 UserDict。通过组合使用普通方法和特殊方法,UserDict 类可以很好地模拟字典。在 Python 2.2 及更高版本中,可以直接从 dict 等内置数据类型继承类。本书附带的示例中给出了一个例子,位于 fileinfo_fromdict.py 中。 |
在 Python 中,可以直接从 dict 内置数据类型继承,如本例所示。与 UserDict 版本相比,这里有三个区别。
class FileInfo(dict):"store file metadata" def __init__(self, filename=None):
self["name"] = filename
<< 实例化类 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
特殊类方法 >> |