是否有Scala的Option或Either的Python等价物?

Mel*_*nez 32 python functional-programming scala

我非常喜欢在Scala中使用Option和Either monad.Python中有这些东西的等价物吗?如果没有,那么什么是pythonic方式处理错误或"缺乏价值"而不抛出异常?

Sin*_*ion 20

嗯,实际上,函数说"我此时没有定义"的pythonic方法是引发异常.

>>> 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
Run Code Online (Sandbox Code Playgroud)

这是因为python没有(通常有用的)静态类型检查器.在编译时,python函数不能在语法上声明它具有特定的codomain; 没有办法强制调用者匹配函数返回类型中的所有情况.

如果您愿意,可以(非自然地)编写一个Maybe包装器:

class Maybe(object):
    def get_or_else(self, default):
        return self.vaue if isinstance(self, Just) else default

class Just(Maybe):
    def __init__(self, value):
        self.value = value

class Nothing(Maybe):
    pass
Run Code Online (Sandbox Code Playgroud)

但我不会这样做,除非你试图将scala中的东西移植到python而不会改变太多

  • 也可以使用“无”与“<您想要的实际值>”并严格执行。 (2认同)
  • 嗨@SingleNegationElimination,我理解你关于没有静态类型的观点,但是Option(和其他Monads)仍然为你提供了一个强大的抽象来推理你的代码!即使您不会进行类型检查,您仍然会得到一个提示,即在选项的情况下,值可能会丢失,并采取措施从失败中恢复! (2认同)

小智 9

mypy在常规Python上添加类型定义和类型检查(不是在运行时).他们有Optional:https://docs.python.org/3/library/typing.html#typing.Optional.更多信息,请访问https://www.python.org/dev/peps/pep-0484/#rationale-and-goals.Intellij拥有插件支持,使其非常专业和流畅.

  • `mypy` 的 `Optional` 只是一个*类型注释*,这意味着它可以像这样使用 "def foo(arg:Optional[int]) ->Optional[str]: ...` 但它不能被实例化,即 `Optional("foo")` 会抛出错误。 (2认同)
  • 在Python中,“Optional[T]”只是“Union[None, T]”的糖。与 Scala 相比,这一点很弱。例如,这意味着您无法区分空的“Optional”和包含“None”的非空“Optional”。另外,不能有像 map 或 iter 这样的工具用于“Optional”。而 `Optional[Optional[T]]` 也是 `Union[None, T]`。 (2认同)

jor*_*sti 8

在python中,由于缺少值,变量为None,因此您可以这样做.

vars = None

vars = myfunction()

if vars is None:
     print 'No value!'
else:
     print 'Value!'
Run Code Online (Sandbox Code Playgroud)

或者甚至只是检查是否存在这样的值

if vars is not None:
     print vars
Run Code Online (Sandbox Code Playgroud)

  • 为什么这被否决?重要的是,Python中的任何内容都可以为空,因为Python不是静态类型检查的.我在Python中使用`None`与在Scala中使用`None`的方式相同,但Scala可以验证我在算术中不使用'None`,而Python让我总是犯这个错误. (4认同)
  • 我投了反对票,因为就问题而言,答案很差。scala 中的 Option 模式是指称为“空对象模式”的模式,目的是避免 if 语句 (3认同)

sla*_*vik 8

您可以使用打字包(Python 3.6.9)。使用以下使类型检查器快乐

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'))
Run Code Online (Sandbox Code Playgroud)

结果

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

如果你想添加一元行为,Either你可以试试这个

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})'
Run Code Online (Sandbox Code Playgroud)

和正确的类型

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))
Run Code Online (Sandbox Code Playgroud)

结果

Right(2)
Left(Error during parsing a: invalid literal for int() with base 10: 'a')
Right(2.0)
Left(zero !!!)
Run Code Online (Sandbox Code Playgroud)


Zor*_*orf 5

我意识到派对的时间已经很晚了,但是在决定实施之前我来谷歌这个页面,所以也许我可以帮助其他人用Google搜索.我实现了它,你可以从pypi中获取它pyther-maybe,它实现了Either和Maybe with Maybe作为Either的特殊子类.这个例子应该解释它是如何工作的:

import sys
from pyther_maybe import *

def save_div ( x, y ):
    if y == 0:
        return nothing() # alias of Maybe()
    else:
        return value(x / y) # alias of Maybe(x / y)

float_test = save_div(1.0, 3.0)

assert isinstance(float_test, Maybe)

if float_test: #nothing tests as false:
    float = float_test() # calling the container with no arguments returns its value
else:
    sys.exit("something went wrong")

print float

# or if you want to encode a reason:

def save_div ( x, y ):
    if y == 0:
        return left("You can't divide by zero, silly") # alias of Either(left=...)
    else:
        return right(x / y) # alis of Either(...)

float_test = save_div(4.2, 0.0)

assert isinstance(float_test, Either)

def fake_exit ( string ):
    print "We would have exited with:"
    print string
    return "Whatever value"

if float_test:
    # these two are te same
    float = float_test()
    float = float_test.right()
else:
    fake_exit(float_test.left())

# or in a shorter and more pleasant format
# does the same as above
float = float_test.extract(fake_exit)

print float # prints "Whatever value"

# Also, these containers are mutable:

box = nothing()

try:
    print box() # raises exception
except RightEitherException:
    print "We caught an exception"

def change_box():
    box(4)

change_box()
print box() # 4
Run Code Online (Sandbox Code Playgroud)

它有更多的功能,其中一些在实践中相当无用(例如它也是一个迭代器,并且有下标表示法pyther_maybe.either(x)[pyther_maybe.Right] == x.