我正在编写一个库,它提供了一个由最终用户进行子类化的类。类的构造函数关心添加到子类中的方法的参数类型。我希望最终用户能够使用类型注释来指示参数的类型。
我的类关心的一件事是参数是否是包含文件路径的字符串。从逻辑上讲,这应该是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')
没有静态类型检查器抛出错误。这更多的是因为我担心我正在定义的类型的语义,而不是因为任何人实际使用我的代码并费心在它上运行静态类型检查器的危险。
一种解决方案是将FilePath
定义为
FilePath = Union[AnyStr, NewType('FilePath', AnyStr)]
但这看起来很混乱,它的__repr__
是一个直截了当的谎言:
>>> FilePath
Union[Anystr, FilePath]
有没有更好的方法?
发布于 2019-06-11 02:58:13
您的两个目标是不兼容的:您不能同时指定您的方法只接受一个特定的类似路径的对象(甚至是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对象,但至少现在他们将从这样做中获得一些实际的运行时好处。
如果您决定使用后一种方法(允许用户直接传入字符串),那么您不妨去掉所有的str (或者Union[Text, bytes]
或AnyStr
),直接使用str。这将是一个更诚实的类型签名:
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")
...but这只是一个可读性的改进。为了实现完全的类型安全,如果你的代码和子类的代码接收到一些不能被解析为路径的随机字符串,那么它们仍然需要包含一些错误处理。
我个人倾向于在任何地方都使用pathlib.Path对象,这是有价值的。
https://stackoverflow.com/questions/56520059
复制相似问题