Is there a Python equivalent for Scala's Option or Either?
Question:
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 errors or “absence of value” without throwing exceptions?
Answers:
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
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.
I realize this is pretty late to the party but I came to this page on top of google before deciding to implement it so maybe I can help others googling with this. I implemented it, you can get it from pypi as pyther-maybe
, it implements both Either and Maybe with Maybe as a special subclass of Either. This example should explain how it works:
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
It has more features than that, some of which are pretty useless in practise (it’s also an iterator for instance and has subscript notation like pyther_maybe.either(x)[pyther_maybe.Right] == x
.
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.
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.
Try This:
from monad import Monad
class Either(Monad):
# pure :: a -> Either a
@staticmethod
def pure(value):
return Right(value)
# flat_map :: # Either a -> (a -> Either b) -> Either b
def flat_map(self, f):
if self.is_left:
return self
else:
return f(self.value)
class Left(Either):
def __init__(self, value):
self.value = value
self.is_left = True
class Right(Either):
def __init__(self, value):
self.value = value
self.is_left = False
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
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 !!!)
pip install either
The library was created in 2012, its Development status is: Stable, according to https://pypi.org/project/either/.
There is a package called Pymonad that implements option types as maybe and an either type as well as other functional ideas such as a wrapper for currying, monads, monoids, and others.
General Details
https://pypi.org/project/PyMonad/
Reference
https://jasondelaat.github.io/pymonad_docs/reference/pymonad.html
You can write your own Optional
implement
from __future__ import annotations
from typing import *
T = TypeVar("T")
U = TypeVar("U")
class Option(Generic[T]):
_value: Optional[T]
@classmethod
def ofNullable(cls, value: Optional[T]) -> Option[T]:
option = cls()
option._value = value
return option
@classmethod
def of(cls, value: Optional[T]) -> Option[T]:
assert value is not None
return cls.ofNullable(value)
@classmethod
def empty(cls) -> Option[T]:
return cls.ofNullable(None)
def isPresent(self) -> bool:
return self._value is not None
def ifPresent(self, consumer: Callable[[T], Any]) -> None:
if self._value is not None:
consumer(self._value)
def get(self) -> T:
return self._value
def orElse(self, other: T) -> T:
return self._value if self._value is not None else other
def orElseGet(self, other: Callable[[], T]) -> T:
return self._value if self._value is not None else other()
def orElseThrow(self, exceptionSupplier: Callable[[], BaseException]):
if self._value is not None:
return self._value
else:
raise exceptionSupplier()
def filter(self, predicate: Callable[[T], bool]) -> Option[T]:
if predicate(self):
return self
return self.__class__.empty()
def map(self, mapper: Callable[[T], U]) -> Option[U]:
if self._value is not None:
return self.__class__.of(mapper(self._value))
return self.__class__.empty()
def flatMap(self, mapper: Callable[[T], Option[U]]) -> Option[U]:
if self._value is not None:
return mapper(self._value)
return self.__class__.empty()
def __str__(self) -> str:
return f"<Option:{self._value}>"
def __repr__(self) -> str:
return f"<Option:{repr(self._value)}>"
def __eq__(self, other: Union[T, Any]):
if isinstance(other, Option):
return self._value == other._value
return False
Or use a 3rd Optional
library
IceSpringRealOptional
Real Optional
type in python
, not @Nullable
annotation.
Official sites
- Home: https://baijifeilong.github.io/2022/01/09/ice-spring-real-optional/index.html
- Github: https://github.com/baijifeilong/IceSpringRealOptional
- PyPI: https://pypi.org/project/IceSpringRealOptional
Features
- All
Java 8
style Optional
API support
- All
Generic Type
annotation support
Install
- PyPI:
pip install IceSpringRealOptional
Usage
from IceSpringRealOptional import Option
option = Option.ofNullable("CommonSense")
print("{}: isPresent={}".format(option, option.isPresent()))
print("{}: value={}".format(option, option.get()))
option.ifPresent(lambda x: print(f"{x} exist"))
print("{}'s length: {}".format(option, option.map(len)))
empty = Option.empty()
print(empty.orElse("{} is empty".format(empty)))
print(empty.orElseGet(lambda: "{} is empty again".format(empty)))
try:
Option.empty().orElseThrow(lambda: RuntimeError("Unlucky"))
except RuntimeError as e:
print("Runtime error caught: {}".format(e))
Example Output
<Option:CommonSense>: isPresent=True
<Option:CommonSense>: value=CommonSense
CommonSense exist
<Option:CommonSense>'s length: <Option:11>
<Option:None> is empty
<Option:None> is empty again
Runtime error caught: Unlucky
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 errors or “absence of value” without throwing exceptions?
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
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.
I realize this is pretty late to the party but I came to this page on top of google before deciding to implement it so maybe I can help others googling with this. I implemented it, you can get it from pypi as pyther-maybe
, it implements both Either and Maybe with Maybe as a special subclass of Either. This example should explain how it works:
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
It has more features than that, some of which are pretty useless in practise (it’s also an iterator for instance and has subscript notation like pyther_maybe.either(x)[pyther_maybe.Right] == x
.
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.
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.
Try This:
from monad import Monad
class Either(Monad):
# pure :: a -> Either a
@staticmethod
def pure(value):
return Right(value)
# flat_map :: # Either a -> (a -> Either b) -> Either b
def flat_map(self, f):
if self.is_left:
return self
else:
return f(self.value)
class Left(Either):
def __init__(self, value):
self.value = value
self.is_left = True
class Right(Either):
def __init__(self, value):
self.value = value
self.is_left = False
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
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 !!!)
pip install either
The library was created in 2012, its Development status is: Stable, according to https://pypi.org/project/either/.
There is a package called Pymonad that implements option types as maybe and an either type as well as other functional ideas such as a wrapper for currying, monads, monoids, and others.
General Details
https://pypi.org/project/PyMonad/
Reference
https://jasondelaat.github.io/pymonad_docs/reference/pymonad.html
You can write your own Optional
implement
from __future__ import annotations
from typing import *
T = TypeVar("T")
U = TypeVar("U")
class Option(Generic[T]):
_value: Optional[T]
@classmethod
def ofNullable(cls, value: Optional[T]) -> Option[T]:
option = cls()
option._value = value
return option
@classmethod
def of(cls, value: Optional[T]) -> Option[T]:
assert value is not None
return cls.ofNullable(value)
@classmethod
def empty(cls) -> Option[T]:
return cls.ofNullable(None)
def isPresent(self) -> bool:
return self._value is not None
def ifPresent(self, consumer: Callable[[T], Any]) -> None:
if self._value is not None:
consumer(self._value)
def get(self) -> T:
return self._value
def orElse(self, other: T) -> T:
return self._value if self._value is not None else other
def orElseGet(self, other: Callable[[], T]) -> T:
return self._value if self._value is not None else other()
def orElseThrow(self, exceptionSupplier: Callable[[], BaseException]):
if self._value is not None:
return self._value
else:
raise exceptionSupplier()
def filter(self, predicate: Callable[[T], bool]) -> Option[T]:
if predicate(self):
return self
return self.__class__.empty()
def map(self, mapper: Callable[[T], U]) -> Option[U]:
if self._value is not None:
return self.__class__.of(mapper(self._value))
return self.__class__.empty()
def flatMap(self, mapper: Callable[[T], Option[U]]) -> Option[U]:
if self._value is not None:
return mapper(self._value)
return self.__class__.empty()
def __str__(self) -> str:
return f"<Option:{self._value}>"
def __repr__(self) -> str:
return f"<Option:{repr(self._value)}>"
def __eq__(self, other: Union[T, Any]):
if isinstance(other, Option):
return self._value == other._value
return False
Or use a 3rd Optional
library
IceSpringRealOptional
Real Optional
type in python
, not @Nullable
annotation.
Official sites
- Home: https://baijifeilong.github.io/2022/01/09/ice-spring-real-optional/index.html
- Github: https://github.com/baijifeilong/IceSpringRealOptional
- PyPI: https://pypi.org/project/IceSpringRealOptional
Features
- All
Java 8
styleOptional
API support - All
Generic Type
annotation support
Install
- PyPI:
pip install IceSpringRealOptional
Usage
from IceSpringRealOptional import Option
option = Option.ofNullable("CommonSense")
print("{}: isPresent={}".format(option, option.isPresent()))
print("{}: value={}".format(option, option.get()))
option.ifPresent(lambda x: print(f"{x} exist"))
print("{}'s length: {}".format(option, option.map(len)))
empty = Option.empty()
print(empty.orElse("{} is empty".format(empty)))
print(empty.orElseGet(lambda: "{} is empty again".format(empty)))
try:
Option.empty().orElseThrow(lambda: RuntimeError("Unlucky"))
except RuntimeError as e:
print("Runtime error caught: {}".format(e))
Example Output
<Option:CommonSense>: isPresent=True
<Option:CommonSense>: value=CommonSense
CommonSense exist
<Option:CommonSense>'s length: <Option:11>
<Option:None> is empty
<Option:None> is empty again
Runtime error caught: Unlucky