我应该如何在类型注释中表示包含文件路径的字符串类型?
How should I represent the type of a string containing a path to a file in type annotations?
我正在编写一个库,它提供了一个 class 供最终用户使用的 class。 class 的构造函数关心添加到 subclass 的方法的参数类型。我希望最终用户能够使用类型注释指示参数的类型。
我的 class 关心的事情之一是参数是否是包含文件路径的字符串。从逻辑上讲,这应该是 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')
没有静态类型检查器抛出错误。这更多是因为我担心我正在定义的类型的语义,而不是因为任何实际使用我的代码 and 的人都会担心 运行上面有静态类型检查器。
一种解决方案是将 FilePath
定义为
FilePath = Union[AnyStr, NewType('FilePath', AnyStr)]
但这看起来很混乱,它的 __repr__
是一个彻头彻尾的谎言:
>>> FilePath
Union[Anystr, FilePath]
有没有更好的方法?
你的两个目标是不兼容的:你不能同时指定你的方法只接受特定的 path-like 对象(甚至是 str 的特定子类型),同时允许调用者直接传入一些任意 str .
你需要从两者中选择一个。
如果您决定使用前者(指定该方法只接受特定的 path-like 对象),使用 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]
或 AnyStr
) 直接。这将是一个更诚实的类型签名:
class MyClass:
def my_method(self, x: str) -> None: ...
MyClass().my_method("foo/bar.baz")
您或许可以通过使用类型别名来稍微提高可读性,如下所示:
MaybeAPath = str
class MyClass:
def my_method(self, x: MaybeAPath) -> None: ...
MyClass().my_method("foo/bar.baz")
...但这只是提高了可读性。为了完整 type-safe,如果您的代码和您的子类化器的代码收到一些无法解析为路径的随机字符串,它们仍然需要包含一些错误处理。
我个人倾向于在任何地方使用 pathlib.Path 对象,因为它的价值。
我正在编写一个库,它提供了一个 class 供最终用户使用的 class。 class 的构造函数关心添加到 subclass 的方法的参数类型。我希望最终用户能够使用类型注释指示参数的类型。
我的 class 关心的事情之一是参数是否是包含文件路径的字符串。从逻辑上讲,这应该是 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')
没有静态类型检查器抛出错误。这更多是因为我担心我正在定义的类型的语义,而不是因为任何实际使用我的代码 and 的人都会担心 运行上面有静态类型检查器。
一种解决方案是将 FilePath
定义为
FilePath = Union[AnyStr, NewType('FilePath', AnyStr)]
但这看起来很混乱,它的 __repr__
是一个彻头彻尾的谎言:
>>> FilePath
Union[Anystr, FilePath]
有没有更好的方法?
你的两个目标是不兼容的:你不能同时指定你的方法只接受特定的 path-like 对象(甚至是 str 的特定子类型),同时允许调用者直接传入一些任意 str .
你需要从两者中选择一个。
如果您决定使用前者(指定该方法只接受特定的 path-like 对象),使用 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]
或 AnyStr
) 直接。这将是一个更诚实的类型签名:
class MyClass:
def my_method(self, x: str) -> None: ...
MyClass().my_method("foo/bar.baz")
您或许可以通过使用类型别名来稍微提高可读性,如下所示:
MaybeAPath = str
class MyClass:
def my_method(self, x: MaybeAPath) -> None: ...
MyClass().my_method("foo/bar.baz")
...但这只是提高了可读性。为了完整 type-safe,如果您的代码和您的子类化器的代码收到一些无法解析为路径的随机字符串,它们仍然需要包含一些错误处理。
我个人倾向于在任何地方使用 pathlib.Path 对象,因为它的价值。