Fluent Python2 【Chapter10_QA】

1. @abstractmethod的概念,作用,必要性,和举例说明。

@abstractmethod 是 Python 中 abc 模块(Abstract Base Classes 抽象基类)中的装饰器,用于声明抽象方法。

抽象方法是指在父类中定义了方法的签名(名称和参数),但没有具体的实现。子类必须实现这些抽象方法才能被实例化,否则会抛出 TypeError 异常。

以下是 @abstractmethod 的概念、作用、必要性以及举例说明:

  1. 概念: @abstractmethod 装饰器用于声明抽象方法,即在父类中定义方法的名称和参数,但不提供具体的实现。抽象方法必须由子类实现,否则子类无法被实例化。

  2. 作用:

    • 声明抽象方法,强制要求子类提供具体的实现。
    • 使得父类更加抽象,能够定义出子类应该具备的行为,而不需要实现具体的逻辑。
  3. 必要性:

    • 通过声明抽象方法,可以定义一套规范,指导子类的实现。
    • 强制子类实现特定的方法,确保代码的一致性和规范性。
  4. 举例说明:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

    def perimeter(self):
        return 2 * 3.14 * self.radius

# 创建抽象类的实例会报错
# shape = Shape()  # TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter

# 创建子类的实例
rectangle = Rectangle(5, 4)
circle = Circle(3)

# 调用子类的方法
print("Rectangle Area:", rectangle.area())
print("Rectangle Perimeter:", rectangle.perimeter())
print("Circle Area:", circle.area())
print("Circle Perimeter:", circle.perimeter())
# 创建子类的实例
  rectangle = Rectangle(5, 4)
  circle = Circle(3)
# 调用子类的方法
  print("Rectangle Area:", rectangle.area())
  print("Rectangle Perimeter:", rectangle.perimeter())
  print("Circle Area:", circle.area())
  print("Circle Perimeter:", circle.perimeter())

在上面的例子中,Shape 类是一个抽象类,其中声明了两个抽象方法 areaperimeterRectangleCircle 类都是 Shape 的子类,必须实现这两个抽象方法才能被实例化。如果子类没有实现抽象方法,尝试实例化子类时会抛出 TypeError 异常。

 

2. python的内省模块,内省函数的概念的理解。

Python 的内省(introspection)指的是通过代码在运行时检查对象的属性和方法,以及获取对象的类型信息等。内省模块和函数则是用于实现内省操作的工具和函数。

  1. 概念: 内省模块和函数是用于在运行时获取对象信息、属性和方法的工具和函数。它们使得程序能够在运行时了解对象的内部结构和特征。

  2. 作用:

    • 动态获取对象的类型信息,如类名、模块名等。
    • 动态获取对象的属性和方法。
    • 用于调试、测试和动态编程等场景。
  3. 通俗解释: 内省就像是一面镜子,能够让程序在运行时看到对象的内部结构和特征,就像我们可以在镜子中看到自己的形象一样。

  4. 举例说明:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self) -> str:
        return f"Hello, my name is {self.name} and I am {self.age} years old."

# 使用内省函数获取对象信息
obj = Person("Alice", 30)
print(type(obj))  # 获取对象类型信息
print(dir(obj))   # 获取对象的属性和方法列表
print(hasattr(obj, "name"))  # 检查对象是否有指定属性
print(getattr(obj, "age"))   # 获取对象的指定属性的值
print(callable(obj.greet))   # 检查对象的指定方法是否可调用

# 使用内省模块获取对象信息
import inspect
print(inspect.getmembers(obj))              # 获取对象的属性和方法列表
print(inspect.getmembers(obj, inspect.ismethod))  # 获取对象的方法列表
print(inspect.signature(obj.greet))        # 获取对象的方法的签名信息

# 返回结果
type(obj):  <class '__main__.Person'>
dir(obj):  ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'greet', 'name']
hasattr(obj, "name") True
getattr(obj, "age") 30
callable(obj.greet):  True
inspect.getmembers(obj):  [('__class__', <class '__main__.Person'>), ('__delattr__', <method-wrapper '__delattr__' of Person object at 0x0000020985CF3FD0>), ('__dict__', {'name': 'Alice', 'age': 30}), ('__dir__', <built-in method __dir__ of Person object at 0x0000020985CF3FD0>), ('__doc__', None), ('__eq__', <method-wrapper '__eq__' of Person object at 0x0000020985CF3FD0>), ('__format__', <built-in method __format__ of Person object at 0x0000020985CF3FD0>), ('__ge__', <method-wrapper '__ge__' of Person object at 0x0000020985CF3FD0>), ('__getattribute__', <method-wrapper '__getattribute__' of Person object at 0x0000020985CF3FD0>), ('__gt__', <method-wrapper '__gt__' of Person object at 0x0000020985CF3FD0>), ('__hash__', <method-wrapper '__hash__' of Person object at 0x0000020985CF3FD0>), ('__init__', <bound method Person.__init__ of <__main__.Person object at 0x0000020985CF3FD0>>), ('__init_subclass__', <built-in method __init_subclass__ of type object at 0x0000020985BDFF30>), ('__le__', <method-wrapper '__le__' of Person object at 0x0000020985CF3FD0>), ('__lt__', <method-wrapper '__lt__' of Person object at 0x0000020985CF3FD0>), ('__module__', '__main__'), ('__ne__', <method-wrapper '__ne__' of Person object at 0x0000020985CF3FD0>), ('__new__', <built-in method __new__ of type object at 0x00007FFA2072ABB0>), ('__reduce__', <built-in method __reduce__ of Person object at 0x0000020985CF3FD0>), ('__reduce_ex__', <built-in method __reduce_ex__ of Person object at 0x0000020985CF3FD0>), ('__repr__', <method-wrapper '__repr__' of Person object at 0x0000020985CF3FD0>), ('__setattr__', <method-wrapper '__setattr__' of Person object at 0x0000020985CF3FD0>), ('__sizeof__', <built-in method __sizeof__ of Person object at 0x0000020985CF3FD0>), ('__str__', <method-wrapper '__str__' of Person object at 0x0000020985CF3FD0>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x0000020985BDFF30>), ('__weakref__', None), ('age', 30), ('greet', <bound method Person.greet of <__main__.Person object at 0x0000020985CF3FD0>>), ('name', 'Alice')]
inspect.getmembers(obj, inspect.ismethod):  [('__init__', <bound method Person.__init__ of <__main__.Person object at 0x0000020985CF3FD0>>), ('greet', <bound method Person.greet of <__main__.Person object at 0x0000020985CF3FD0>>)]
inspect.signature(obj.greet):  () -> str

 

3. pyi后缀结尾的是什么文件,比如这个classic_strategy.pyi

from typing import (
    List,
    Optional,
    Union,
)


class BulkItemPromo:
    def discount(self, order: Order) -> Union[float, int]: ...


class FidelityPromo:
    def discount(self, order: Order) -> Union[float, int]: ...


class LargeOrderPromo:
    def discount(self, order: Order) -> Union[float, int]: ...


class LineItem:
    def __init__(self, product: str, quantity: int, price: float) -> None: ...
    def total(self) -> float: ...


class Order:
    def __init__(
        self,
        customer: Customer,
        cart: List[LineItem],
        promotion: Optional[Union[BulkItemPromo, LargeOrderPromo, FidelityPromo]] = ...
    ) -> None: ...
    def due(self) -> float: ...
    def total(self) -> float: ...

.pyi 文件是Python中的类型存根文件(Stub file)

它们提供了Python代码的类型注解信息,通常用于类型检查代码自动完成等功能。

类型存根文件包含了模块、函数、类以及它们的参数和返回值的类型注解信息,但不包含具体的实现代码。

它们的作用是为那些没有类型注解或者无法获取源代码(如C扩展模块)的代码提供类型信息,从而支持诸如mypy等静态类型检查工具对这些代码进行类型检查类型推断

您提供的示例classic_strategy.pyi就是一个类型存根文件,它定义了多个类及其方法的类型签名,以便在使用这些类时获得类型检查和代码自动补全等支持。

总的来说,.pyi文件是Python类型检查和类型提示的重要支持文件。

 

4. @pytest.fixture装饰器的作用理解  [from classic_strategy_test.py]

from typing import List

import pytest  # type: ignore

from classic_strategy import Customer, LineItem, Order
from classic_strategy import FidelityPromo, BulkItemPromo, LargeOrderPromo


@pytest.fixture
def customer_fidelity_0() -> Customer:
    return Customer('John Doe', 0)


@pytest.fixture
def customer_fidelity_1100() -> Customer:
    return Customer('Ann Smith', 1100)


@pytest.fixture
def cart_plain() -> List[LineItem]:
    return [LineItem('banana', 4, .5),
            LineItem('apple', 10, 1.5),
            LineItem('watermelon', 5, 5.0)]


def test_fidelity_promo_no_discount(customer_fidelity_0, cart_plain) -> None:
    order = Order(customer_fidelity_0, cart_plain, FidelityPromo())
    assert order.total() == 42.0
    assert order.due() == 42.0


def test_fidelity_promo_with_discount(customer_fidelity_1100, cart_plain) -> None:
    order = Order(customer_fidelity_1100, cart_plain, FidelityPromo())
    assert order.total() == 42.0
    assert order.due() == 39.9


def test_bulk_item_promo_no_discount(customer_fidelity_0, cart_plain) -> None:
    order = Order(customer_fidelity_0, cart_plain, BulkItemPromo())
    assert order.total() == 42.0
    assert order.due() == 42.0


def test_bulk_item_promo_with_discount(customer_fidelity_0) -> None:
    cart = [LineItem('banana', 30, .5),
            LineItem('apple', 10, 1.5)]
    order = Order(customer_fidelity_0, cart, BulkItemPromo())
    assert order.total() == 30.0
    assert order.due() == 28.5


def test_large_order_promo_no_discount(customer_fidelity_0, cart_plain) -> None:
    order = Order(customer_fidelity_0, cart_plain, LargeOrderPromo())
    assert order.total() == 42.0
    assert order.due() == 42.0


def test_large_order_promo_with_discount(customer_fidelity_0) -> None:
    cart = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
    order = Order(customer_fidelity_0, cart, LargeOrderPromo())
    assert order.total() == 10.0
    assert order.due() == 9.3

在这段代码中,有三个被@pytest.fixture装饰的函数:

  • customer_fidelity_0()
  • customer_fidelity_1100()
  • cart_plain()

这些函数的作用是提供测试所需的数据,分别返回一个Customer对象和一个LineItem对象列表。

接下来,有6个测试函数:

  • test_fidelity_promo_no_discount()
  • test_fidelity_promo_with_discount()
  • test_bulk_item_promo_no_discount()
  • test_bulk_item_promo_with_discount()
  • test_large_order_promo_no_discount()
  • test_large_order_promo_with_discount()

这些测试函数的参数正是之前定义的那些fixture函数的名字。

pytest在运行这些测试函数时,会自动调用相应的fixture函数来获取测试所需的数据,并将数据传递给测试函数。

例如,在test_fidelity_promo_no_discount()中,pytest会先调用customer_fidelity_0()cart_plain()获取一个Customer对象和LineItem列表,

然后将它们作为参数传递给test_fidelity_promo_no_discount()函数。这样一来,测试函数就可以直接使用这些已经准备好的数据,而不需要在函数内部重复编写创建数据的代码。

使用fixture的好处是可以将准备测试数据的代码与实际的测试逻辑分离,提高了代码的可读性和可维护性。同时,由于fixture函数是可复用的,如果多个测试函数需要使用相同的测试数据,只需定义一次fixture函数即可,避免了重复代码

总的来说,@pytest.fixture的作用是:提供可复用的测试数据准备函数,pytest在运行测试用例时会自动调用这些函数来获取所需数据,并将数据传递给测试函数,从而简化了测试用例的编写,提高了代码的可读性和可维护性。

 

5. 如下代码的含义。

这段代码的作用是在当前目录下运行所有的pytest测试用例。

具体解释如下:

  1. import pytest是导入pytest模块。
  2. pytest.main(['.'])是调用pytest的主函数main()
    • main()函数是pytest的入口点,用于执行测试用例。
    • 传递给main()函数的参数是一个列表,列表中的元素被当作命令行参数来对待。
    • 在这里,传递的参数是['.'],其中.表示当前目录。

所以,pytest.main(['.'])的作用是在当前目录下查找所有符合pytest命名约定的测试文件(如以test_开头的Python文件或者以_test.py结尾的文件), 并执行这些测试文件中定义的所有测试用例。

通常,我们会在项目的根目录下运行pytest.main(['.'])来执行整个项目中的所有测试用例。也可以传递其他参数来控制pytest的行为,例如指定特定的测试文件、输出格式等。

在实际项目中,通常不会直接在代码中调用pytest.main()。相反,我们会在命令行运行pytest命令来执行测试用例,pytest会自动查找当前目录和子目录下的测试文件并执行测试用例

 

 6. 如下是python3.10的内置inspect.py脚本, isfunction() ismethod()

函数和方法到底有何异同,请举例说明

一言以蔽之: 在 Python 中,函数和方法是有区别的。函数是一个独立的可调用对象, 而方法是绑定在类实例上的函数。

def isfunction(object):
    """Return true if the object is a user-defined function.

    Function objects provide these attributes:
        __doc__         documentation string
        __name__        name with which this function was defined
        __code__        code object containing compiled function bytecode
        __defaults__    tuple of any default values for arguments
        __globals__     global namespace in which this function was defined
        __annotations__ dict of parameter annotations
        __kwdefaults__  dict of keyword only parameters with defaults"""
    return isinstance(object, types.FunctionType)

def ismethod(object):
    """Return true if the object is an instance method.

    Instance method objects provide these attributes:
        __doc__         documentation string
        __name__        name with which this method was defined
        __func__        function object containing implementation of method
        __self__        instance to which this method is bound"""
    return isinstance(object, types.MethodType)

函数和方法在 Python 中都属于可调用对象,但它们存在一些关键区别:

函数(Function)是一个独立的可执行代码单元,它可以被直接调用,例如:

def greet(name):
    print(f"Hello, {name}!")

greet("Alice")  # 输出: Hello, Alice!

方法(Method)是一种特殊类型的函数,它必须绑定到一个类的实例上才能被调用。

方法的第一个参数通常是 self,它指向调用该方法的实例对象。例如:

class Person:
    def __init__(self, name):
        self.name = name

    def greet(self):
        print(f"Hello, my name is {self.name}!")

person = Person("Alice")
person.greet()  # 输出: Hello, my name is Alice!

在上面的例子中,greet是一个方法,它被绑定到 Person 类的实例 person 上。当我们调用 person.greet() 时,Python 会自动将 person 实例作为第一个参数传递给 greet 方法。

总的来说:

  • 函数是独立的可执行代码单元,可以直接调用。
  • 方法必须绑定到一个类的实例上,并通过该实例调用。方法的第一个参数通常是 self,指向调用该方法的实例。
  • 方法可以访问和修改它所绑定实例的状态(属性)。
  • 函数通常用于封装可重用的功能,而方法则用于在类中实现对象的行为。

inspect.isfunction()inspect.ismethod() 是 Python 内置的辅助函数,它们用于检查一个对象是函数还是方法,以便进行特殊处理或调试。

 

 7. 对于10-dp-1class-func/monkeytype/classic_strategy.pyi的两段代码classic_strategy.py和classic_strategy.pyi

为什么有.py后,还要有.pyi文件,.pyi文件在这里也没有被引用啊,而且为什么不把.pyi文件中的类型提示都写在.py文件中,要分成两个文件来写,这样是有什么好处吗?

 

.pyi 文件是 Python 的一个特殊文件格式,称为“接口文件”(Intermediate file)。它们用于生成类型提示(type hints),通常与 .py 文件一起使用。.pyi 文件不是用来执行的代码,而是用于工具(如 mypy)来分析代码的类型信息。

当你在 .pyi 文件中定义类型提示时,这些提示可以被工具用来进行静态类型检查,以发现潜在的类型错误。mypy 是一个常用的静态类型检查工具,它读取 .pyi 文件中的类型提示,然后与 .py 文件中的实际代码进行对比,以查找类型不匹配的情况。

将类型提示分离到 .pyi 文件中的一些好处包括:

  1. 更清晰的代码:main.py 文件可以专注于业务逻辑,而不必包含大量的类型提示,使得代码更加简洁和易于阅读。

  2. 可维护性:如果 .py 文件中的类型提示发生了变化,可以只更新 .pyi 文件,而不是修改 .py 文件,这样可以减少错误并提高代码的可维护性。

  3. 更好的工具集成:静态类型检查工具通常需要 .pyi 文件来提供完整的类型信息。

  4. 灵活性:.pyi 文件可以独立于 .py 文件进行编辑和版本控制,这为不同的开发团队提供了更大的灵活性。

在你的例子中,.pyi 文件和 .py 文件都在同一目录下,并且 .py 文件中使用了类型提示。这表明你正在使用类型提示来帮助你编写更安全的代码,并可能在使用 mypy 这样的静态类型检查工具。

.pyi 文件提供了这些类型提示的清晰定义,而 .py 文件包含了实际的实现细节。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2024-02-15 16:01  AlphaGeek  阅读(17)  评论(0)    收藏  举报