也许"kind-of" python中的monad



试图找到一种方法来清理我的一些代码。

在我的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'

最新更新