Package: safebag
safebagSafebagis a little python package implementing optional chaining.InstallationpipinstallsafebagUsageCode we want to avoidif(objisnotNoneandobj.attrisnotNoneandobj.attr.attrisnotNoneandobj.attr.attr.attrisnotNoneandobj.attr.attr.attr.attrisnotNone):# Do something useful with obj.attr.attr.attr.attr...Pythonic solutiontry:print(obj.attr.attr.attr.attr)# Do something useful with obj.attr.attr.attr.attrexcept(NameError,AttributeError)ase:# Do something useful with an errorStill it's not very clean way in case of multiple attribute handling in one placetry:print(obj.attr.attr.attr.attr)# Do something useful with obj.attr.attr.attr.attrexcept(NameError,AttributeError)ase:...try:print(obj.attr.attr)# Do something useful with obj.attr.attrexcept(NameError,AttributeError)ase:...try:print(obj.attr)# Do something useful with obj.attrexcept(NameError,AttributeError)ase:...Usage example:fromsafebagimportchain,get_valueifattr:=chain(obj).attr.attr.attr.attr:# Do something useful with obj.attr.attr.attr.attrprint(get_value(attr))ifattr:=chain(obj).attr.attr:# Do something useful with obj.attr.attrprint(get_value(attr))ifattr:=chain(obj).attr:# Do something useful with obj.attrprint(get_value(attr))Exampleschain [source]Optional chain constructor, may be constructed from any objectChain is used for building sequence of null-safe attribute callsfrom__future__importannotationsimportdataclassesasdtimporttyping@dt.dataclassclassNode:data:intnode:typing.Optional[Node]nodes=Node(data=1,node=Node(data=2,node=None))fromsafebagimportchainthird_node_proxy=chain(nodes).node.node.nodeprint(third_node_proxy)# ChainProxy(data_object=None, bool_hook=False)get_value [source]Final value getter for optional chain.Optional chain constructed from any object. Chain is used for building sequence of null-safe attribute calls.from__future__importannotationsimportdataclassesasdtimporttyping@dt.dataclassclassNode:data:intnode:typing.Optional[Node]nodes=Node(data=1,node=Node(data=2,node=None))fromsafebagimportchain,get_valuethird_node_proxy=chain(nodes).node.node.nodevalue=get_value(third_node_proxy)assertvalueisNonenext_node=chain(nodes).nodevalue=get_value(next_node)# Node(data=2, node=None)Possible way of getting valueifnext_node:=chain(nodes).node:print(next_node.get_value())# Node(data=2, node=None)Default can be passed as argumentifnext_node:=chain(nodes).node.node:print(next_node.get_value(default='Default'))# 'Default'Useful in combination with walrus operator:ifnext_node:=chain(nodes).node.node:print(get_value(next_node))ifnext_node:=chain(nodes).node:print(get_value(next_node))# Node(data=2, node=None)ChainProxy [source]ChainProxycontainer:storesdata_objectproxyingdata_objectattribute value into newChainProxyinstance
when attribute is invoked. If attribute does not exist or attribute value isNone.ChainProxyinstancedata_objectwill beNoneandbool_hookwill beFalse.ChainProxyinstance always returning when attribute is invoked.Release: 0.2.0Performance updateIncrease performance by adding empty_proxy instead of real ChainProxyFor case below
Numbers: # 0.100 -> 0.046importtimeitfromdataclassesimportdataclass@dataclassclassNode:data:intnode:typing.Optional[Node]node=Node(1,None)executable=lambda:get_value(chain(node).node.node.node.node.node.node.node.node.node.node)perf=timeit.timeit(executable,number=10000)