Fluent Python2 【Chapter10_QA】
1. @abstractmethod的概念,作用,必要性,和举例说明。
@abstractmethod 是 Python 中 abc 模块(Abstract Base Classes 抽象基类)中的装饰器,用于声明抽象方法。
抽象方法是指在父类中定义了方法的签名(名称和参数),但没有具体的实现。子类必须实现这些抽象方法才能被实例化,否则会抛出 TypeError 异常。
以下是 @abstractmethod 的概念、作用、必要性以及举例说明:
-
概念:
@abstractmethod装饰器用于声明抽象方法,即在父类中定义方法的名称和参数,但不提供具体的实现。抽象方法必须由子类实现,否则子类无法被实例化。 -
作用:
- 声明抽象方法,强制要求子类提供具体的实现。
- 使得父类更加抽象,能够定义出子类应该具备的行为,而不需要实现具体的逻辑。
-
必要性:
- 通过声明抽象方法,可以定义一套规范,指导子类的实现。
- 强制子类实现特定的方法,确保代码的一致性和规范性。
-
举例说明:
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 类是一个抽象类,其中声明了两个抽象方法 area 和 perimeter。Rectangle 和 Circle 类都是 Shape 的子类,必须实现这两个抽象方法才能被实例化。如果子类没有实现抽象方法,尝试实例化子类时会抛出 TypeError 异常。
2. python的内省模块,内省函数的概念的理解。
Python 的内省(introspection)指的是通过代码在运行时检查对象的属性和方法,以及获取对象的类型信息等。内省模块和函数则是用于实现内省操作的工具和函数。
-
概念: 内省模块和函数是用于在运行时获取对象信息、属性和方法的工具和函数。它们使得程序能够在运行时了解对象的内部结构和特征。
-
作用:
- 动态获取对象的类型信息,如类名、模块名等。
- 动态获取对象的属性和方法。
- 用于调试、测试和动态编程等场景。
-
通俗解释: 内省就像是一面镜子,能够让程序在运行时看到对象的内部结构和特征,就像我们可以在镜子中看到自己的形象一样。
-
举例说明:
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测试用例。
具体解释如下:
import pytest是导入pytest模块。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 文件中的一些好处包括:
-
更清晰的代码:
main.py文件可以专注于业务逻辑,而不必包含大量的类型提示,使得代码更加简洁和易于阅读。 -
可维护性:如果
.py文件中的类型提示发生了变化,可以只更新.pyi文件,而不是修改.py文件,这样可以减少错误并提高代码的可维护性。 -
更好的工具集成:静态类型检查工具通常需要
.pyi文件来提供完整的类型信息。 -
灵活性:
.pyi文件可以独立于.py文件进行编辑和版本控制,这为不同的开发团队提供了更大的灵活性。
在你的例子中,.pyi 文件和 .py 文件都在同一目录下,并且 .py 文件中使用了类型提示。这表明你正在使用类型提示来帮助你编写更安全的代码,并可能在使用 mypy 这样的静态类型检查工具。
.pyi 文件提供了这些类型提示的清晰定义,而 .py 文件包含了实际的实现细节。

浙公网安备 33010602011771号