可能是Python中的“kind-of”单子

q7solyqu  于 2023-03-11  发布在  Python
关注(0)|答案(8)|浏览(146)

想办法清理我的代码。
在我的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)之后,我想知道是否有可能用另一种方式来写它。

vq8itlhq

vq8itlhq1#

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

tyg4sfes

tyg4sfes2#

利用短路行为以及自定义对象默认为true而None为false:

person  = Person.find(id=12345)
company = person and person.company
country = company and company.country
dced5bon

dced5bon3#

Python没有特别好的单子语法,也就是说,如果你想限制自己使用像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以将中间结果存储在变量中;如何正确地做这件事取决于你。

toiithl6

toiithl64#

你查过PyMonad了吗?
https://pypi.python.org/pypi/PyMonad/
它不仅包括一个可能单子,而且还包括一个列表单子,一个函子和应用函子类。
在您的情况下,它可能是这样的:

country = Person.find(id=12345)          >> (lambda person: 
          Company.find(person.companyId) >> (lambda company: 
          Country.find(company.countryId))

比EAFP更容易理解和清洁。

goucqfw6

goucqfw65#

我认为这是getattr(object, name[, default])的完美情况:

person  = Person.find(id=12345)
company = getattr(person, 'company', None)
country = getattr(company, 'country', None)
kqlmhetl

kqlmhetl6#

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)
whitzsjs

whitzsjs7#

比试图实现一个不同的范式(不是说它不有趣和不酷)更“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
r6l8ljro

r6l8ljro8#

haskell也许python中的模拟是打字,可选的,但可悲的是,这不是一回事。
有关详细信息,请参见
https://docs.python.org/3/library/typing.html#typing.Optional
How should I use the Optional type hint?
使用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'

相关问题