试图找到一种方法来清理我的一些代码。
在我的Python代码中有这样的内容:
company = None
country = None
person = Person.find(id=12345)
if person is not None: # found
company = Company.find(person.companyId)
if company is not None:
country = Country.find(company.countryId)
return (person, company, country)
在阅读了Haskell的单子(特别是Maybe)的教程后,我想知道是否可以用另一种方式来写它。
company = country = None
try:
person = Person.find(id=12345)
company = Company.find(person.companyId)
country = Country.find(company.countryId)
except AttributeError:
pass # `person` or `company` might be None
EAFP
利用短路行为,并且自定义对象默认为真,None
为假:
person = Person.find(id=12345)
company = person and person.company
country = company and company.country
Python没有特别好的monad语法。也就是说,如果你想限制自己使用像Maybe
单子(意思是你只能使用Maybe
;您将无法创建处理任何单子的泛型函数,您可以使用以下方法:
class Maybe():
def andThen(self, action): # equivalent to Haskell's >>=
if self.__class__ == _Maybe__Nothing:
return Nothing
elif self.__class__ == Just:
return action(self.value)
def followedBy(self, action): # equivalent to Haskell's >>
return self.andThen(lambda _: action)
class _Maybe__Nothing(Maybe):
def __repr__(self):
return "Nothing"
Nothing = _Maybe__Nothing()
class Just(Maybe):
def __init__(self, v):
self.value = v
def __repr__(self):
return "Just(%r)" % self.value
然后,使当前返回None
的所有方法返回Just(value)
或Nothing
。这允许您编写以下代码:
Person.find(id=12345)
.andThen(lambda person: Company.find(person.companyId))
.andThen(lambda company: Country.find(company.countryId))
你当然可以调整lambda来将中间结果存储在变量中;
您检查过PyMonad了吗?
https://pypi.python.org/pypi/PyMonad/它不仅包括Maybe单子,还包括列表单子、函子类和应用函子类。Monoids和更多。
在你的例子中,应该是这样的:
country = Person.find(id=12345) >> (lambda person:
Company.find(person.companyId) >> (lambda company:
Country.find(company.countryId))
比EAFP更容易理解和清晰。
我认为这对getattr(object, name[, default])
来说是一个完美的情况:
person = Person.find(id=12345)
company = getattr(person, 'company', None)
country = getattr(company, 'country', None)
person = Person.find(id=12345)
company = None if person is None else Company.find(person.companyId)
country = None if company is None else Country.find(company.countryId)
return (person, company, country)
比起尝试实现不同的范式(不是说它不有趣或不酷),更"python化"的做法是为对象添加智能,这样它们就可以自己找到它们的属性(以及它们是否存在)。
Bellow是一个基类的例子,它使用您的"find"方法以及Id属性名称和类名称的相关性来处理您的示例-我放入了最小的Person和Company类来搜索要工作的公司:
class Base(object):
def __getattr__(self, attr):
if hasattr(self, attr + "Id"):
return globals()[attr.title()].find(getattr(self, attr + "Id"))
return None
@classmethod
def find(cls, id):
return "id %d " % id
class Person(Base):
companyId=5
class Company(Base):
pass
在控制台上,粘贴上面的代码后:
>>> p = Person()
>>> p.company
'id 5 '
有了这个Base
,你上面的代码可以是:
person = Person.find(id=12345)
company = person.company
country = company and company.country
haskell
的可能在python
中的模拟是类型。可选的,但遗憾的是,这不是一回事。
有关更多信息,请参阅
https://docs.python.org/3/library/typing.html typing.Optional
我应该如何使用可选类型提示?
使用typing.Optional
构建链可以像这样:
from typing import Callable, Optional
# some imported func, can't change definition
def Div3(x: int) -> Optional[int]:
print(f"-- {x} --", end= " -> ")
return None if x%3!=0 else x//3
# `bind` function
# No Type Vars in python, so can't specify Callable[ T, Optional[S]] -> Callable[ Optional[T], Optional[S]]
def Optionalize( function: Callable[ any, Optional[any]] ) -> Callable[ Optional[any], Optional[any]]:
def _binded( x ):
return None if x is None else function(x)
return _binded
@Optionalize
def Div2(x: int) -> Optional[int]:
print(f"-- {x} --", end= " -> ")
return None if x%2!=0 else x//2
OpD3 = Optionalize( Div3 )
# we not wrap this one
def SPQRT(x: int) -> Optional[int]:
print(f"-=^\ {x} /^=-", end= " -> ")
return None if x<0 else x**2
print( SPQRT (20) )
print( SPQRT (-1) )
print( Div2( SPQRT (20)) ) # Haskell would swear here
print( OpD3 ( SPQRT (-1)) )
print( Div2( Div2( Div2( Div2 ((-1)))))) # Only one set of wings printed (but Haskell would swear here too)
print( Div3( SPQRT (-1)) ) # TypeError at last
--------------------------
-=^ 20 /^=- -> 400
-=^ -1 /^=- -> None
-=^ 20 /^=- -> -- 400 -- -> 200
-=^ -1 /^=- -> None
-- -1 -- -> None
-=^ -1 /^=- -> -- None -- ->
---------------------------------------------------------------------------
TypeError: unsupported operand type(s) for %: 'NoneType' and 'int'