Fluent Python2 【Chapter14_QA】
1. 编程语言的"性状"的概念该如何理解?
"性状"(Traits)是指编程语言中用来描述类或对象特征和行为的抽象概念。它可以包括属性(属性)、方法(行为)以及与特定类或对象相关的其他特征。
"性状"通常被用来实现代码的复用和模块化,使得类或对象可以拥有和共享相似的特征和行为。
概念:
- "性状"是指类或对象的特征和行为,它可以描述类或对象的属性、方法、状态等。
- "性状"可以被其他类或对象复用,从而提高代码的可重用性和可维护性。
作用:
- 实现代码的复用:通过将通用的特征和行为抽象成性状,不同的类或对象可以共享这些性状,避免了重复编写相似的代码。
- 提高代码的模块化:将代码模块化成性状,可以更好地组织和管理代码,使得代码更易于理解、扩展和维护。
通俗解释:
- "性状"就像是一组定义了类或对象的特征和行为的规范或模板。它描述了类或对象应该具有的属性和方法,以及它们的行为方式。
- 类或对象可以选择性地拥有和应用不同的性状,从而实现代码的复用和定制化。
举例说明:
-
动物的性状: 在动物世界中,不同的动物可能共享一些相似的特征和行为,例如会飞的动物和不会飞的动物。这些特征和行为可以抽象成性状,如飞行性状和游泳性状。
class Flyable: def fly(self): print("I can fly!") class Swimmable: def swim(self): print("I can swim!") class Bird(Flyable): def __init__(self, name): self.name = name class Fish(Swimmable): def __init__(self, name): self.name = name bird = Bird("Eagle") fish = Fish("Salmon") bird.fly() # 输出:I can fly! fish.swim() # 输出:I can swim!
2. GUI组件的性状: 在图形用户界面编程中,不同的GUI组件可能具有相似的特征和行为,例如按钮、文本框和复选框等。这些特征和行为可以抽象成性状,如可点击性状和可编辑性状。
class Clickable: def click(self): print("Clicked!") class Editable: def edit(self): print("Edited!") class Button(Clickable): def __init__(self, label): self.label = label class TextField(Editable): def __init__(self, text): self.text = text button = Button("Submit") text_field = TextField("Enter text") button.click() # 输出:Clicked! text_field.edit() # 输出:Edited!
总之,"性状"是描述类或对象特征和行为的抽象概念,它可以帮助实现代码的复用和模块化,提高代码的可维护性和扩展性。
3. 为什么cls_name打印的都是Leaf而不是Root? [from diamond.py]
class Root: def ping(self): print(f'{self}.ping() in Root') def pong(self): print(f'{self}.pong() in Root') def __repr__(self): cls_name = type(self).__name__ return f'<instance of {cls_name}>' # 这里 class A(Root): def ping(self): print(f'{self}.ping() in A') super().ping() def pong(self): print(f'{self}.pong() in A') super().pong() class B(Root): def ping(self): print(f'{self}.ping() in B') super().ping() def pong(self): print(f'{self}.pong() in B') class Leaf(A, B): def ping(self): print(f'{self}.ping() in Leaf') super().ping() print(Leaf.__mro__) leaf1 = Leaf() leaf1.ping() #output (<class '__main__.Leaf'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Root'>, <class 'object'>) <instance of Leaf>.ping() in Leaf <instance of Leaf>.ping() in A <instance of Leaf>.ping() in B <instance of Leaf>.ping() in Root
一个很细很好的问题,详细解释一下原因:
在Python中, self 实际上是一个指向当前实例对象的引用, 而不是指向类对象本身的引用。当我们调用实例方法时,Python会自动将该实例对象作为第一个参数传递给方法。
在你的示例代码中,当执行 leaf1.__repr__() 时,Python实际上是在调用 Root.__repr__(leaf1) 这个方法。也就是说,self 参数指向的是 leaf1 这个 Leaf 类的实例对象,而不是 Root 类对象。
因此,在 Root 类的 __repr__ 方法中,type(self) 返回的是 self 所指向对象的类型,也就是 Leaf 类。这就是为什么 cls_name 的值为 'Leaf',从而输出 <instance of Leaf>。
简单来说,self 始终指向调用该方法的实例对象,而不是该方法所定义的类对象。即便是在父类中定义的方法,当被子类实例调用时,self 也会绑定到该子类实例。
所以,在你的例子中,尽管 __repr__ 方法是在 Root 类中定义的,但当 leaf1 (一个 Leaf 类的实例)调用该方法时,self 指向的就是 leaf1 这个 Leaf 类的实例对象,因此 type(self) 返回的是 Leaf 类。
追问:执行 Leaf 类中的 ping 方法,输出 <instance of Leaf>.ping() in Leaf??为什么会输出<instance of Leaf>.ping(), 难道不是执行print(f'{self}.ping() in Leaf')吗
一个非常好的问题。确实,在 Leaf 类的 ping 方法中,代码 print(f'{self}.ping() in Leaf') 看起来应该只输出 self.ping() in Leaf。
但实际上,它输出的是 <instance of Leaf>.ping() in Leaf。这是因为在这里,self 被 Python 自动替换成了 self 所指向的实例对象的字符串表示形式。
更具体地说,当执行 print(f'{self}.ping() in Leaf') 时,Python 实际上做了以下几步:
- 获取
self所指向的实例对象,在这里是leaf1对象。 - 调用该实例对象的
__repr__方法来获取它的字符串表示形式。由于Leaf类没有定义自己的__repr__方法,因此会使用继承自Root类的__repr__方法。 Root类的__repr__方法返回了<instance of Leaf>这个字符串,表示它是一个Leaf类的实例对象。- Python 将
self替换为这个字符串表示形式,即<instance of Leaf>。 - 最终输出为
<instance of Leaf>.ping() in Leaf。
所以,虽然在代码中看到的是 print(f'{self}.ping() in Leaf'), 但由于 Python 自动将 self 替换为了实例对象的字符串表示形式, 因此最终输出中包含了 <instance of Leaf> 这部分内容。
同样的原理也适用于其他类中的 ping 方法输出,如 <instance of Leaf>.ping() in A、<instance of Leaf>.ping() in B 和 <instance of Leaf>.ping() in Root。
它们都是将 self 替换为了 Leaf 类实例对象的字符串表示形式。
5. 如何理解.super(type, object_or_type)这个两个参数呢
super(type, object_or_type) 是 Python 中用于调用父类方法的内置函数。它有两个参数:
type- 概念: 表示当前类的类型。
- 作用: 指定从哪个类开始查找父类方法。
- 通俗解释: 想象一下,你站在一个楼梯上,
type就是告诉 Python 你当前所在的楼层。 - 举例:
super(Leaf, self)表示从Leaf类开始查找父类方法。
object_or_type- 概念: 表示当前对象或类型。
- 作用: 指定在哪个对象或类型的层次结构中查找父类方法。
- 通俗解释: 如果你站在一个公寓楼的某层楼梯上,
object_or_type就是告诉 Python 你要在哪个公寓楼的楼梯上查找。 - 举例:
super(Leaf, self)表示在self这个Leaf类的实例对象的层次结构中查找父类方法。
通常情况下,我们只需要传递 object_or_type 参数,因为 Python 可以自动确定 type 参数。但在某些特殊情况下,需要显式地指定 type 参数,例如在元类编程中。
举个例子,假设我们有以下类层次结构:
class Root: def ping(self): print(f'{self}.ping() in Root') class A(Root): def ping(self): print(f'{self}.ping() in A') super().ping() class B(Root): def ping(self): print(f'{self}.ping() in B') super().ping() class Leaf(A, B): def ping(self): print(f'{self}.ping() in Leaf') super(Leaf, self).ping() leaf = Leaf() leaf.ping()
在这个例子中,当执行 leaf.ping() 时:
- 首先执行
Leaf类的ping方法,输出<instance of Leaf>.ping() in Leaf。 - 然后,
super(Leaf, self).ping()告诉 Python 从Leaf类开始查找父类方法,并在self这个Leaf类实例对象的层次结构中查找。 - 根据方法解析顺序 (MRO),Python 找到了
A类的ping方法,执行它并输出<instance of Leaf>.ping() in A。 A类的ping方法又调用了super().ping()。- 继续根据 MRO,Python 找到了
B类的ping方法,执行它并输出<instance of Leaf>.ping() in B。 B类的ping方法又调用了super().ping()。- 最后,Python 找到了
Root类的ping方法,执行它并输出<instance of Leaf>.ping() in Root。
通过这个例子,你可以看到 super() 是如何帮助我们在继承层次结构中查找并调用父类方法的。它使代码更加简洁、易于维护,而且避免了重复调用父类方法的麻烦。
6. super()函数的理解
在Python中,super()是一个特殊函数,用于调用当前对象的父类(超类或基类)的方法。下面详细介绍super()的概念、作用、通俗解释和使用举例。
概念
super()是Python内置的一个函数,它主要用于在子类中调用父类的方法。在Python 3中,super()返回的是父类对象的一个代理,而在Python 2中,super()则是一个内置类。
作用
super()的主要作用是提供一个简单的方式来调用父类的方法,特别是当类层次结构比较复杂时。它避免了直接硬编码父类的名字,这样当父类的名字改变时,代码不需要做太多的修改。
通俗解释
你可以将super()想象成一个自动找到“上司”的工具。在现实生活中,如果你在自己的岗位上遇到了解决不了的问题,你会去找你的上司寻求帮助。
在Python的类层次中,super()就是用来找到“上司”(即父类)的工具,让你能够调用父类的功能。
使用举例
下面是一个使用super()的简单例子:
class Animal: def __init__(self, name): self.name = name def speak(self): return 'Animal is speaking now...' class Dog(Animal): def __init__(self, name) -> None: super().__init__(name) def speak(self) -> str: return super().speak() dog = Dog('Buddy') print(dog.name) # Buddyprint(dog.speak()) # Animal is speaking now...
7. 通过在pycharm中逐步断点调试,深入反复理解super(X, self), X(也就是type参数)取不同值是各个类的调用顺序。
也就是注释部分为什么会分别得到那样的对应结果。一定要反复多调试多体会,深入理解!

8.python中混入类的概念理解
在Python中,混入类(Mixin)是一种设计模式,用于复用代码,它允许开发者将多个类的功能组合到一个类中,从而实现代码的复用和扩展。混入类通常不作为任何类的父类,它们只是提供一些功能,可以被其他类使用。
概念和作用
混入类通常有一些特点:
- 它们通常不会包含太多的状态(即属性),它们更多的是提供行为(即方法)。
- 它们不要求继承关系,可以被多个类使用。
- 它们通常不会单独使用,而是作为其他类功能的一个补充。
通俗解释
想象你有一系列的工具(类),每个工具都能完成特定的任务。现在,你需要一个多功能的工具来完成任务,但你不想从头开始创建一个全新的工具。使用混入类就像是将几个单一功能的工具组合起来,形成一个多功能的工具,这样你就可以复用已有的功能,而不需要重新发明轮子。
举例说明
下面是一个简单的例子,展示了如何使用混入类:
# 混入类提供额外功能 class ToDictMixin: def to_dict(self): return self.__dict__ # 基础类 class User: def __init__(self, name, email): self.name = name self.email = email # 使用混入类 class Admin(ToDictMixin, User): def __init__(self, name, email, role): super().__init__(name, email) self.role = role # 创建实例并使用混入类提供的方法 admin = Admin('Alice', 'alice@example.com', 'admin') print(admin.to_dict()) # 输出: {'name': 'Alice', 'email': 'alice@example.com', 'role': 'admin'}
在这个例子中,ToDictMixin 提供了一个 to_dict 方法,Admin 类通过继承这个混入类获得了这个方法。
使用混入类带来的差异性
如果没有使用混入类,我们可能需要在每个需要 to_dict 方法的类中重复实现这个方法,这会导致代码重复,并且难以维护。使用混入类可以让多个类共享相同的行为,而不需要多重继承或复杂的层次结构。
混入类的另一个优势是它们可以增加代码的可插拔性。通过简单地改变类的继承列表,我们可以轻松地为一个类添加或移除功能。
总之,混入类提供了一种灵活的方式来复用和组合类的功能,这在复杂的项目中尤其有用,可以减少代码重复,提高代码的可维护性和扩展性。
9. __missing__方法的理解
在Python中,__missing__方法是一个特殊的方法,它属于字典的子类collections.defaultdict和collections.Mapping。这个方法在类中用于自定义当字典中的键不存在时应该返回的值。
概念
__missing__方法是一个“魔法方法”或“双下方法”(dunder methods),即它们的名字由双下划线包围。这个方法在类中用于定义当尝试获取一个不存在的键时的行为。
作用
当__missing__被定义时,它允许一个类在键不存在于字典中时提供自定义的行为。这意味着你可以定义在尝试访问字典中不存在的键时的返回值,而不是抛出KeyError异常。
通俗解释
想象一下,你有一个字典,用于存储水果的数量。当你尝试获取一个不存在的水果的数量时,你希望返回一个默认值,比如0,而不是抛出异常。在这种情况下,你可以在字典的类中定义__missing__方法,以提供自定义的返回值。
举例说明
下面是一个使用collections.defaultdict和__missing__方法的例子:
from collections import defaultdict class FruitCounter(defaultdict): def __missing__(self, key): return 0 # 创建一个FruitCounter实例 fruit_counter = FruitCounter() # 添加一些水果数量 fruit_counter['apple'] = 5 fruit_counter['banana'] = 3 # 获取水果数量 print(fruit_counter['apple']) # 输出 5 print(fruit_counter['banana']) # 输出 3 # 尝试获取一个不存在的水果数量 print(fruit_counter['orange']) # 输出 0,因为orange键不存在,所以调用__missing__方法
在这个例子中,我们创建了一个名为FruitCounter的类,它继承了defaultdict类并定义了__missing__方法。
当我们尝试获取一个不存在的水果数量时,__missing__方法被调用并返回0,而不是抛出KeyError异常。
这样,我们就可以安全地获取任何水果的数量,而不必担心是否存在于字典中。
10. 如何理解pairs = iterable.items()失败后,会返回 AttributeError? [from strkeydict_dictsub.py]
class StrKeyDict(dict): def __init__(self, iterable=None, **kwds): super().__init__() self.update(iterable, **kwds) def __missing__(self, key): if isinstance(key, str): raise KeyError(key) return self[str(key)] def __contains__(self, key): return key in self.keys() or str(key) in self.keys() def __setitem__(self, key, item): super().__setitem__(str(key), item) def get(self, key, default=None): try: return self[key] except KeyError: return default def update(self, iterable=None, **kwds): if iterable is not None: try: # duck typing FTW! pairs = iterable.items() # ? except AttributeError: # ? pairs = iterable for key, value in pairs: self[key] = value if kwds: self.update(kwds)
StrKeyDict 类继承自内置的 dict 类,并添加了一些特殊方法来处理字符串键。
update 方法用于将键值对添加到字典中,它接受一个可迭代对象 iterable 和关键字参数 **kwds。
在 update 方法中,可以看到以下代码段:
if iterable is not None: try: # duck typing FTW! pairs = iterable.items() except AttributeError: pairs = iterable for key, value in pairs: self[key] = value
这段代码尝试将 iterable 中的键值对添加到字典中。
这里使用了“鸭子类型”(duck typing)的概念,即“如果它看起来像鸭子,叫起来像鸭子,那么它就是鸭子”。
在这个上下文中,如果 iterable 有一个 items 方法,那么它被假设为一个映射(如字典),并且代码尝试调用 iterable.items() 来获取键值对。
如果 iterable 没有这个方法,那么会抛出一个 AttributeError 异常。这是因为尝试访问一个对象没有的属性时会抛出 AttributeError。
例如,如果你传递一个列表给 update 方法,列表没有 items 方法,所以会抛出 AttributeError。
当 AttributeError 被抛出时,except 块捕获到这个异常,并将 pairs 设置为 iterable 本身。这意味着 iterable 被假设为一个键值对列表(或其他可迭代对象),其中的元素是键值对元组。
这里有一个例子来说明这个过程:
# 创建一个 StrKeyDict 实例 my_dict = StrKeyDict() # 使用字典更新 StrKeyDict my_dict.update({'a': 1, 'b': 2}) # 这里调用的是 dict.items() # 使用列表(列表没有 items 方法)更新 StrKeyDict # 这将导致 AttributeError,因为列表没有 items 方法 my_dict.update([('c', 3), ('d', 4)]) # 这里 pairs 被设置为 [('c', 3), ('d', 4)] print(my_dict) # 输出 {'a': 1, 'b': 2, 'c': 3, 'd': 4}
在这个例子中,第一次调用 update 方法时,传入了一个字典,因此 iterable.items() 成功地返回了字典中的键值对。
第二次调用 update 方法时,传入了一个列表,列表没有 items 方法,所以抛出了 AttributeError 异常,然后 pairs 被设置为列表本身,列表中的元素是键值对元组。
最终,StrKeyDict 被更新为包含所有这些键值对。

浙公网安备 33010602011771号