EAFP 和 mypy 可以共存吗?

问题描述 投票:0回答:1

EAFP

请求原谅比请求许可更容易。这种常见的 Python 编码风格假设存在有效的键或属性,并在假设证明错误时捕获异常。这种干净、快速的风格的特点是存在许多 try 和 except 语句。该技术与许多其他语言(例如 C)常见的 LBYL 风格形成对比。

来自Python术语表

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
感到悲伤?

  • Python
    3.8.6
  • mypy
    0.782
python mypy python-typing duck-typing
1个回答
0
投票

当我们开始在代码库上利用

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
© www.soinside.com 2019 - 2024. All rights reserved.