我希望能够轻松地创建新类型,并(可选地)添加有关它们的一些信息(例如一些文档以及它们经常使用的一组变量名)。
简单的方法是:
from typing import Any, NewType, Union, List, Iterable, Optional
Key = NewType('Key', Any)
Key._aka = set(['key', 'k'])
Val = NewType('Val', Union[int, float, List[Union[int, float]]])
Val.__doc__ = "A number or list of numbers."
但是有两个原因我不喜欢这个:
我必须复制粘贴我正在制作的新类型的名称的三遍(不是D.R.Y.并容易出错)
我不喜欢“外部化”可选附加信息(_aka
和__doc__
)的分配
所以我想出了这个:
from typing import Any, NewType, Union, List, Iterable, Optional
def new_type(name, tp, doc: Optional[str]=None, aka: Optional[Iterable]=None):
"""Make a new type with (optional) doc and (optional) aka, set of var names it often appears as"""
new_tp = NewType(name, tp)
if doc is not None:
setattr(new_tp, '__doc__', doc)
if aka is not None:
setattr(new_tp, '_aka', set(aka))
globals()[name] = new_tp # is this dangerous? Does scope need to be considered more carefully?
这会给我我想要的界面:
new_type('Key', Any, aka=['key', 'k'])
new_type('Val', Union[int, float, List[Union[int, float]]], doc="A number or list of numbers.")
但是我不确定globals()[name] = new_tp
这件事。如果我要在模块的顶层定义类型,这似乎是无害的,但不确定在某些边缘情况下嵌套范围的情况下如何公平。
创建新类型的通常方法是只编写一个新类:
class Key:
def __init__(self, key: object) -> None:
self._key = key
self._aka = set(['key', 'k'])
class SqlString(str):
"""A custom string which has been verified to be valid SQL."""
请注意,这种方法避免了您的DRY和范围界定问题。
[仅当您不要添加任何额外的属性或文档字符串时才使用NewType -执行Foo = NewType('Foo', X)
基本上与执行class Foo(X): pass
相似,只是运行时开销稍有减少。
(更确切地说,类型检查器将Foo = NewType('Foo', X)
像写为class Foo(X): pass
一样对待,但是在运行时实际上会发生什么Foo = lambda x: x
-Foo是标识函数。]
但是,由于联合不是可子类化的(根据PEP 484,这使得该NewType是非法的,因此我们确实遇到了第二个联合示例的麻烦。
相反,我个人将执行以下操作:
# A number or list of numbers
Val = Union[int, float, List[Union[int, float]]]
IMO,因为类型应该在运行时不可见,所以我认为不必麻烦附加运行时可用的文档就很有意义。