我该如何在类型注释中表示包含文件路径的字符串的类型?

时间:2019-06-10 03:11:22

标签: python typing typehints

我正在编写一个库,该库提供了供最终用户子类化的类。类的构造函数关心添加到子类中的方法的参数类型。我希望最终用户能够使用类型注释来指示参数的类型。

我的课程关心的一件事是参数是否为包含文件路径的字符串。从逻辑上讲,这应该是str的子类型,可以这样表示:

import mylib
from typing import *

FilePath = NewType('FilePath', AnyStr)

class MySubclass(mylib.MyClass):
    def my_method(self, path: FilePath):
        return open(path)

但是typing.NewType的文档字符串给出了以下示例:

from typing import *

UserId = NewType('UserId', int)

def name_by_id(user_id: UserId) -> str:
    ...

name_by_id(42)          # Fails type check
name_by_id(UserId(42))  # OK

因此,为了使静态类型检查器不会失败使用我的库的代码,用户必须执行以下操作:

from mylib import *

... # MySubclass defined as above

o = MySubclass()
o.my_method(FilePath('foo/bar.baz'))

但是我希望他们能够做到

o.my_method('foo/bar.baz')

没有静态类型检查器抛出错误。这更多是因为我担心要定义的类型的语义,而不是因为任何人都可能实际上使用我的代码 并在其上运行静态类型检查器的危险。 / p>

一种解决方案是将FilePath定义为

FilePath = Union[AnyStr, NewType('FilePath', AnyStr)]

但这令人困惑,它的__repr__是一个直截了当的谎言:

>>> FilePath
Union[Anystr, FilePath]

有更好的方法吗?

1 个答案:

答案 0 :(得分:1)

您的两个目标是不兼容的:您无法同时指定您的方法仅接受特定的类路径对象(甚至是str的特定子类型),同时允许调用者直接传递任意的str。

您需要选择两个。

如果您决定使用前者(请指定该方法仅接受特定的类路径对象),则使用NewTypes的更可口的替代方法可能是改为改为让您的方法仅接受pathlib.Path objects

from pathlib import Path

class MyClass:
    def my_method(self, x: Path) -> None: ...

MyClass().my_method(Path("foo/bar.baz"))

您的调用方仍然需要将其字符串转换为这些Path对象,但是至少现在他们将从中获得一些实际的运行时好处。

如果您决定采用后一个目标(允许用户直接传递字符串),则最好摆脱所有NewType并改用str(或Union[Text, bytes]或{{1} })。这将是一个更诚实的类型签名:

AnyStr

通过使用类型别名,您也许可以使其更易读,例如:

class MyClass:
    def my_method(self, x: str) -> None: ...

MyClass().my_method("foo/bar.baz")

...但这只是可读性的改进。为了完全确保类型安全,如果您的代码和子类程序的代码收到无法解析为路径的随机字符串,则它们仍然需要包含一些错误处理。

我个人偏向于在任何地方使用pathlib.Path对象,以获取其价值。