用于在python中定义不可变和类型的namedtuple样式库。
sumtype的Python项目详细描述
sumtype
一个namedtuple
样式库,用于在python中定义不可变的sum类型。(Get it on PyPI)
You may know sum types under a different name – they're also referred to as
tagged unions
,enums
in Rust/Swift, andvariants
in C++. If you haven't heard about them yet, here's a nice introduction.
目前的版本是0.10.0
,很快接近1.0
。
库支持Python3.x。
核心代码已经存在于各种文件夹中大约一年了,
在我厌倦复制之前,我决定把它作为一个独立的包发布。
(另请参见:Should I use it?)
欢迎提出建议、反馈和意见!
快速游览
>>>fromsumtypeimportsumtype>>>fromtypingimportTuple>>>>>>classThing(sumtype):...defFoo(x:int,y:int):......defBar(y:str,hmm:Tuple[str,str]):......defZap():......>>>
这意味着Thing
值可以是三个变量之一:
- 带两个
int
字段的x
和y
- 带
str
字段y
和Tuple[str, str]
字段hmm
- 没有字段的
Zap
如果提供了类型注释,构造函数将对参数进行类型检查(请参见Typechecking)
您还可以在类定义中添加自己的docstring和方法。
如果您喜欢namedtuple
样式的定义,sumtype
也支持这些定义-请参见sumtype.sumtype.demo()
中的Thing2
示例。
创建值和属性访问
>>>foo=Thing.Foo(x=3,y=5)# named arguments>>>bar=Thing.Bar('hello',('wo','rld'))# positional arguments>>>zap=Thing.Zap()
注意,它们仍然只是同一类型的不同值,而不是子类:
>>>type(foo)isThingandtype(bar)isThingandtype(zap)isThingTrue
每个指定字段都有一个访问器:
>>>foo.x,foo.y;(3,5)>>>bar.y,bar.hmm('hello',('wo','rld'))
…检查访问是否有效,并显示描述性错误消息:
>>>Thing.Zap().hmm# only `Bar`s have a `hmm` fieldTraceback(mostrecentcalllast):...AttributeError:Incorrect'Thing'variant:Field'hmm'notdeclaredinvariant'Zap'...>>>>>>Thing.Foo(x=1,y=2).blah_blah_blah# no variant has a `blah_blah_blah` field Traceback(mostrecentcalllast):...AttributeError:Unknownattribute:Field'blah_blah_blah'notdeclaredinanyvariantof'Thing'...
这些值还有一个很好的__repr__()
:
>>>foo;bar;zapThing.Foo(x=3,y=5)Thing.Bar(y='hello',hmm=('wo','rld'))Thing.Zap()
这个库的设计考虑到了效率——它使用__slots__
来存储属性
并为每个类生成所有方法的专用版本。
要查看生成的代码,请执行class Thing(sumtype, verbose=True):
。
呃,至少我喜欢这样想;)不过,我尽量用轮廓来描绘事物!
功能
打字检查
sumtype
使用^{
>>># Foo(x: int, y: int) -> Thing>>>Thing.Foo(x=1,y=2)Thing.Foo(x=1,y=2)>>>Thing.Foo(x='should be an int',y=2)Traceback(mostrecentcalllast):...TypeError:typeofargument"x"mustbeint;gotstrinstead
typing
也支持注释:
>>># Bar(y: str, hmm: Tuple[str, str]) -> Thing>>>Thing.Bar(y='a',hmm=('b','c'))Thing.Bar(y='a',hmm=('b','c'))>>>Thing.Bar(y='a',hmm=(1,2))Traceback(mostrecentcalllast):...TypeError:typeofargument"hmm"[0]mustbestr;gotintinstead
^{typing
结构(Tuple
、List
、Dict
、Union
等)。
(完整列表请参见他们的README)
但是,从2.2.2
开始,它不支持用户定义的泛型类,因此对于像z: UserDefinedList[float]
这样的字段,typeguard
将不会检查
如果内容实际上是floats
。
这也阻止我们定义泛型sumtype(例如ConsList[A]
,Maybe[A]
,Either[A, B]
),但我正在解决这个问题。
类型检查可以用typecheck
参数控制:class Thing(sumtype, typecheck='always'|'debug'|'never'):
。
默认模式是'always'
不带注释的字段将不会被类型检查,您可以在定义中混合使用带注释和不带注释的字段。
相等和散列
>>>Thing.Foo(1,2)==Thing.Foo(1,2)True>>>Thing.Foo(1,2)==Thing.Bar('a',('b','c'));False>>>{foo,foo,bar,zap}=={foo,bar,zap}True
__eq__
和__hash__
注意变异-即使我们有变异Moo(x: int, y: int)
,
Foo(1,2) != Moo(1,2)
和hash(Foo(1,2)) != hash(Moo(1,2))
。
Note: Just like tuples,
sumtypes
__eq__
/__hash__
work by__eq__
ing/__hash__
ing the values inside, so the values must all implement the relevant method for it to work.
修改值
>>>foo;foo.replace(x=99)Thing.Foo(x=3,y=5)Thing.Foo(x=99,y=5)>>>>>>bar;bar.replace(y='abc',hmm=('d','e'))Thing.Bar(y='hello',hmm=('wo','rld'))Thing.Bar(y='abc',hmm=('d','e'))
foo.replace(x=99)
返回一个新值,就像在namedtuple
中一样。
.replace
的行为就像构造函数w.r.t.类型检查一样。
Note:
replace
and all the other methods have underscored aliases (_replace
). So even if you have a field calledreplace
, you can still usemy_value._replace(x=15)
.
模式匹配
声明格式:
>>>defdo_something(val:Thing):...ifval.is_Foo():...print(val.x*val.y)...elifval.is_Bar():...print('The result is',val.y,''.join(val.hmm))...elifval.is_Zap():...print('Whoosh!')...else:val.impossible()# throws an error - nice if you like having all cases covered...>>>forvalin(foo,bar,zap):...do_something(val)...15TheresultishelloworldWhoosh!
表达式形式:
>>>[val.match(...Foo=lambdax,y:x*y,...Zap=lambda:999,..._=lambda:-1# default case...)...forvalin(foo,bar,zap)][15,-1,999]
sumtypes
与标准类型之间的转换
去…
>>>foo.values();foo.values_dict();(3,5)OrderedDict([('x',3),('y',5)])
>>>foo.as_tuple();foo.as_dict()('Foo',3,5)OrderedDict([('variant','Foo'),('x',3),('y',5)])
…从
>>>Thing.from_tuple(('Foo',10,15));Thing.from_dict({'variant':'Foo','x':10,'y':15})Thing.Foo(x=10,y=15)Thing.Foo(x=10,y=15)
还有x == Thing.from_tuple(x.as_tuple())
和x == Thing.from_dict(x.as_dict())
。
酸洗支架
>>>importpickle>>>vals=[Thing.Foo(1,2),Thing.Bar('one',('two','three')),Thing.Zap()]>>>vals2=pickle.loads(pickle.dumps(vals))>>>vals;vals==vals2[Thing.Foo(x=1,y=2),Thing.Bar(y='one',hmm=('two','three')),Thing.Zap()]True
在sumtype.tests
中也有一些测试,以确保一切正常工作。
这就是一切…现在!
在1.0
中计划的功能
- 以类型安全的方式定义像
Maybe[A]
/Either[A, B]
这样的泛型sumtype
我应该用它吗?
是啊!我建这个图书馆并不是因为我觉得它会很好- 我正在开发一个应用程序和一些小项目中大量使用它。 说它是经过战斗测试的有点多,但它已经达到了目的。
未来可能的功能
默认值
mypy
支持。 不幸的是,上一次我检查时,mypy
并没有很好地处理元类创建的类,但这可能已经改变了。 或者,我们可以提供一种生成mypy
存根文件。而且,现在没有办法告诉mypy
访问器的返回类型取决于变量–Union[a, b]
很接近,但是mypy
会抱怨不是所有的情况 即使他们被处理。静态生成文件的类定义
自定义生成方法的动态替代方案- 如果启动时间比效率更重要,则可能有用
如果需要真正的不变性,另一个由元组支持的实现- 目前还没有办法让基于
__slots__
的实现在这方面做到无懈可击,我正在尽最大努力。可能opt-in-mutability–目前,如果需要,可以使用
Thing._unsafe_set_Foo_x(foo, 10)
,但这不是一个好的接口