Fluent Python2 【Chapter14_QA】

1. 编程语言的"性状"的概念该如何理解?

"性状"(Traits)是指编程语言中用来描述类或对象特征和行为的抽象概念。它可以包括属性(属性)、方法(行为)以及与特定类或对象相关的其他特征。

"性状"通常被用来实现代码的复用和模块化,使得类或对象可以拥有和共享相似的特征和行为。

概念:

  • "性状"是指类或对象的特征和行为,它可以描述类或对象的属性、方法、状态等。
  • "性状"可以被其他类或对象复用,从而提高代码的可重用性和可维护性。

作用:

  • 实现代码的复用:通过将通用的特征和行为抽象成性状,不同的类或对象可以共享这些性状,避免了重复编写相似的代码。
  • 提高代码的模块化:将代码模块化成性状,可以更好地组织和管理代码,使得代码更易于理解、扩展和维护。

通俗解释:

  • "性状"就像是一组定义了类或对象的特征和行为的规范或模板。它描述了类或对象应该具有的属性和方法,以及它们的行为方式。
  • 类或对象可以选择性地拥有和应用不同的性状,从而实现代码的复用和定制化。

举例说明:

  1. 动物的性状: 在动物世界中,不同的动物可能共享一些相似的特征和行为,例如会飞的动物和不会飞的动物。这些特征和行为可以抽象成性状,如飞行性状和游泳性状。

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 实际上做了以下几步:

  1. 获取 self 所指向的实例对象,在这里是 leaf1 对象。
  2. 调用该实例对象的 __repr__ 方法来获取它的字符串表示形式。由于 Leaf 类没有定义自己的 __repr__ 方法,因此会使用继承自 Root 类的 __repr__ 方法。
  3. Root 类的 __repr__ 方法返回了 <instance of Leaf> 这个字符串,表示它是一个 Leaf 类的实例对象。
  4. Python 将 self 替换为这个字符串表示形式,即 <instance of Leaf>
  5. 最终输出为 <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 中用于调用父类方法的内置函数。它有两个参数:

  1. type
    • 概念: 表示当前类的类型。
    • 作用: 指定从哪个类开始查找父类方法。
    • 通俗解释: 想象一下,你站在一个楼梯上,type 就是告诉 Python 你当前所在的楼层。
    • 举例: super(Leaf, self) 表示从 Leaf 类开始查找父类方法。
  2. 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() 时:

  1. 首先执行 Leaf 类的 ping 方法,输出 <instance of Leaf>.ping() in Leaf
  2. 然后,super(Leaf, self).ping() 告诉 Python 从 Leaf 类开始查找父类方法,并在 self 这个 Leaf 类实例对象的层次结构中查找。
  3. 根据方法解析顺序 (MRO),Python 找到了 A 类的 ping 方法,执行它并输出 <instance of Leaf>.ping() in A
  4. A 类的 ping 方法又调用了 super().ping()
  5. 继续根据 MRO,Python 找到了 B 类的 ping 方法,执行它并输出 <instance of Leaf>.ping() in B
  6. B 类的 ping 方法又调用了 super().ping()
  7. 最后,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.defaultdictcollections.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 被更新为包含所有这些键值对。

 

posted @ 2024-02-20 16:47  AlphaGeek  阅读(12)  评论(0)    收藏  举报