EAFP
请求原谅比请求许可更容易。这种常见的 Python 编码风格假设存在有效的键或属性,并在假设证明错误时捕获异常。这种干净、快速的风格的特点是存在许多 try 和 except 语句。该技术与许多其他语言(例如 C)常见的 LBYL 风格形成对比。
mypy
mypy 是什么?
Mypy 是 Python 的可选静态类型检查器。您可以将类型提示(PEP 484)添加到您的Python程序中,并使用mypy对它们进行静态类型检查。无需运行程序即可找到程序中的错误!
您可以在程序中混合动态和静态类型。当静态类型不方便时(例如遗留代码),您始终可以回退到动态类型。
这里有一个关于 EAFP 的精彩 YouTube 视频:https://youtu.be/x3v9zMX1s4s
我正在尝试使用
mypy
,但基本上每次我写一些 EAFP 代码时它都会生气。
例如:
from contextlib import suppress
from typing import Optional
class MyClass:
def __init__(self):
self.__name: Optional[str] = None
@property
def name(self) -> Optional[str]:
return self.__name
@name.setter
def name(self, name: str):
self.__name = name
@property
def upper_name(self) -> Optional[str]:
with suppress(AttributeError):
return self.name.upper() # Item "None" of "Optional[str]" has no attribute "upper" - mypy(error)
return None
在此示例中,我希望
upper_name
属性尝试将 name
转换为大写,但如果 name
是 None
,它将引发 AttributeError
,然后被 suppress(AttributeError)
抑制,因此函数返回 None
。
我在底部有明确的
return None
,因为PEP 8说:
返回语句保持一致。函数中的所有 return 语句要么都应该返回表达式,要么都不应该返回。如果任何 return 语句返回一个表达式,则任何不返回值的 return 语句都应显式地将其声明为 return None,并且显式 return 语句应出现在函数末尾(如果可到达)。
我已将其作为 问题 (#9467) 提交给
mypy
存储库,但它几乎立即被关闭。
我确实认为有可能让
mypy
了解 EAFP。
例如,如果它看到类型
Optional[something]
,并且它看到一个表达式,如果收到 SomethingError
,则会引发 None
,但如果收到 something
,则成功,并且该表达式包含在 try
中
或 suppress
块可以处理 SomethingError
,那么它可以假设它是一些 EAFP 代码,而不是惊慌失措。
但现在的情况是,为了让它不被上面的例子吓坏,我必须将其更改为:
from typing import Optional
class MyClass:
def __init__(self):
self.__name: Optional[str] = None
@property
def name(self) -> Optional[str]:
return self.__name
@name.setter
def name(self, name: str):
self.__name = name
@property
def upper_name(self) -> Optional[str]:
if self.name is not None:
return self.name.upper() # No error here this time
else:
return None
另一个例子:
假设我可以有一个
house
,可以有一个garage
,可以有一个car
,有一个brand
,这样的东西可以让我得到它的brand
:
from contextlib import suppress
from my_module import house
car_brand = None
try:
car = house.garage.car
except AttributeError:
pass
else:
car_brand = car.brand
如果
house
是 None
,或其 garage
是 None
,或者其 car
是 None
,则 car_brand
将保留 None
。但如果存在一个 house
和一个 garage
以及一个 car
,它会尝试获取它的 brand
。如果 car
没有 brand
,则不会抑制该错误,因为 car
必须有 brand
。
为了避免出现
mypy
错误,另一种方法是:
from my_module import house
car_brand = None
if house is not None:
garage = house.garage
if garage is not None:
car = garage.car
if car is not None:
car_brand = car.brand
而且我认为这不是非常可读或干净。
我认为有效的另一点是避免竞争条件。如果我首先读取一个值来检查它是否有效,然后再次读取它以使用它(如果它有效),我相信没有什么可以保证我第二次读取它时会收到与第一次相同的值。
以“EAFP”方式执行操作时,我只读取一次值,因此读取后值是否发生大幅变化并不重要。
我的例子并不完美,但我认为它们在传达想法方面起到了作用。
所以我的问题是,如何在使用 EAFP 风格的同时仍然使用
mypy
?是否有一个标志或参数我可以给出mypy
,这样它就不会因为 EAFP 的存在而被吓坏了?或者是否有一种“更好”的 EAFP 做事方式,不会让 mypy
感到悲伤?
3.8.6
0.782
当我们开始在代码库上利用
mypy
时,我遇到了这个问题,我认为使用 cast
中的 typing
是一个合理的解决方案。从文档中,我们可以看到 cast
不应该影响运行时行为:
https://docs.python.org/3/library/typing.html#typing.cast
我在下面展示了您提供的两个示例的外观。
from contextlib import suppress
from typing import Optional, cast
class MyClass:
def __init__(self):
self.__name: Optional[str] = None
@property
def name(self) -> Optional[str]:
return self.__name
@name.setter
def name(self, name: str):
self.__name = name
@property
def upper_name(self) -> Optional[str]:
with suppress(AttributeError):
required_name: str = cast(str, self.name)
return required_name.upper()
return None
from contextlib import suppress
from typing import cast
from my_module import house
car_brand = None
try:
required_house = cast(House, house)
required_garage = cast(Garage, required_house.garage)
required_car = cast(Car, required_garage.car)
car = required_car
except AttributeError:
pass
else:
car_brand = car.brand