Is there a Python equivalent for Scala's Option or Either?

前端 未结 8 1603
忘掉有多难
忘掉有多难 2021-02-05 01:26

I really enjoy using the Option and Either monads in Scala. Are there any equivalent for these things in Python? If there aren\'t, then what is the pythonic way of handling erro

相关标签:
8条回答
  • 2021-02-05 01:34

    mypy adds type definitions and type checking (not at runtime) over regular Python. They have an Optional: https://docs.python.org/3/library/typing.html#typing.Optional. More here https://www.python.org/dev/peps/pep-0484/#rationale-and-goals. Intellij has plugin support which makes it all very professional and smooth.

    0 讨论(0)
  • 2021-02-05 01:34

    You can play with typing package (Python 3.6.9). Using following makes type checker happy

    from typing import Optional, Union
    
    
    def parse_int(s: str) -> Optional[int]:
        try:
            return int(s)
        except:
            return None
    
    
    print('-- optional --')
    print(parse_int('123'))
    print(parse_int('a'))
    
    
    def parse_int2(s: str) -> Union[str, int]:
        try:
            return int(s)
        except Exception as e:
            return f'Error during parsing "{s}": {e}'
    
    
    print('-- either --')
    print(parse_int2('123'))
    print(parse_int2('a'))
    

    Result

    -- optional --
    123
    None
    -- either --
    123
    Error during parsing "a": invalid literal for int() with base 10: 'a'
    

    If you want to add monadic behaviour to Either you can try this

    from typing import TypeVar, Generic, Callable
    
    A = TypeVar('A')
    B = TypeVar('B')
    C = TypeVar('C')
    
    Either = NewType('Either', Union['Left[A]', 'Right[C]'])
    
    
    class Left(Generic[A]):
        def __init__(self, value: A):
            self.__value = value
    
        def get(self) -> A:
            raise Exception('it is left')
    
        def get_left(self) -> A:
            return self.__value
    
        def flat_map(self, f: Callable[[B], Either]) -> Either:
            return self
    
        def map(self, f: Callable[[B], C]) -> Either:
            return self
    
        def __str__(self):
            return f'Left({self.__value})'
    

    and right type

    class Right(Generic[B]):
        def __init__(self, value: B):
            self.__value = value
    
        def flat_map(self, f: Callable[[B], Either]) -> Either:
            return f(self.__value)
    
        def map(self, f: Callable[[B], C]) -> Either:
            return Right(f(self.__value))
    
        def __str__(self):
            return f'Right({self.__value})'
    
    
    def parse_int(s: str) -> Union[Left[str], Right[int]]:
        try:
            return Right(int(s))
        except Exception as e:
            return Left(f'Error during parsing {s}: {e}')
    
    def divide(x: int) -> Union[Left[str], Right[int]]:
        return Right(4 / x) if x != 0 else Left('zero !!!')
    
    print(parse_int('1').map(lambda x: x * 2))
    print(parse_int('a').map(lambda x: x * 2))
    print(parse_int('2').flat_map(divide))
    print(parse_int('0').flat_map(divide))
    

    Result

    Right(2)
    Left(Error during parsing a: invalid literal for int() with base 10: 'a')
    Right(2.0)
    Left(zero !!!)
    
    0 讨论(0)
  • 2021-02-05 01:34

    Natively, Python has a Literal Type Optional but it's not the same. Alternatively this is a representation of the Either data type for python 3.

    https://gist.github.com/MatteoGuadrini/98e79a9ab2bd6ae5badc41df89cfe338

    0 讨论(0)
  • 2021-02-05 01:36

    The pythonic way for a function to say "I am not defined at this point" is to raise an exception.

    >>> int("blarg")
    Traceback (most recent call last):
      ...
    ValueError: invalid literal for int() with base 10: 'blarg'
    
    >>> dict(foo=5)['bar']
    Traceback (most recent call last):
      ...
    KeyError: 'bar'
    
    >>> 1 / 0
    Traceback (most recent call last):
      ...
    ZeroDivisionError: integer division or modulo by zero
    

    This is, in part, because there's no (generally useful) static type checker for python. A Python function cannot syntactically state, at compile time, that it has a particular codomain; there's no way to force callers to match all of the cases in the function's return type.

    If you prefer, you can write (unpythonically) a Maybe wrapper:

    class Maybe(object):
        def get_or_else(self, default):
            return self.value if isinstance(self, Just) else default
    
    class Just(Maybe):
        def __init__(self, value):
            self.value = value
    
    class Nothing(Maybe):
        pass
    

    But I would not do this, unless you're trying to port something from Scala to Python without changing much.

    0 讨论(0)
  • 2021-02-05 01:41

    In python, for an absence of value, the variable is None, so you can do it this way.

    vars = None
    
    vars = myfunction()
    
    if vars is None:
         print 'No value!'
    else:
         print 'Value!'
    

    or even just check if a value is present like this

    if vars is not None:
         print vars
    
    0 讨论(0)
  • 2021-02-05 01:49

    A list that happens to always be of length zero or one fulfills some of the same goals as optional/maybe types. You won't get the benefits of static typing in Python, but you'll probably get a run-time error even on the happy path if you write code that tries to use the "maybe" without explicitly "unwrapping" it.

    0 讨论(0)
提交回复
热议问题