An Implementation of the Optional Object for Python
There is a difference between None
as Empty and None
as the result for an Error. A common bad practice is to
return None
to indicate the absence of something. This can cause issues.
For example:
thing = stuff.getSomeThing().getAnotherThing()
What will happen if the result from getSomeThing returns None
? We will get an AttributeError: 'NoneType' object has no attribute 'getAnotherThing'
.
What can you do to prevent these kinds of exceptions? You can write defensively:
something = stuff.getSomeThing()
if something is not None:
thing = something.getAnotherThing()
However, if we add to our chain, you can imagine how the nesting of defensive checks gets ugly quickly. There is plenty of information out there on why defensive coding is not a great idea, which is beyond the scope of this README.
Ultimately, these defensive checks are annoying and obfuscate our actual business logic (decreasing the readability). Furthermore it is an error prone process, because it is easy to forget to do the checks everywhere.
So we present you with an Optional object as an alternative.
-
You can set it to empty instead of None:
from optional import Optional ... return Optional.empty()
instead of:
return None
-
You can set it to have content otherwise:
from optional import Optional ... return Optional.of("thing")
instead of:
return "thing"
-
You can check if its present:
thing = some_func_returning_an_optional() if thing.is_present():
instead of:
if thing is not None:
-
You can check if its empty:
thing = some_func_returning_an_optional() if thing.is_empty():
instead of:
if thing is None:
-
You can get the value:
thing = some_func_returning_an_optional() ... print(thing.get())
instead of:
print(thing)
-
You can't get the value if its empty:
thing = some_func_returning_an_optional() if thing.is_empty(): print(thing.get()) # **will raise an exception**
instead of:
if thing is None: print(None) # very odd
-
Best Usage: You can chain on presence:
thing = some_func_returning_an_optional() thing.if_present(lambda thing: print(thing))
instead of:
if thing is not None: print(thing)
-
Best Usage: You can chain on non presence:
thing = some_func_returning_an_optional() thing.if_present(lambda thing: print(thing)).or_else(lambda _: print("PANTS!"))
instead of:
if thing is not None: print(thing) else: print("PANTS!")
-
Best Usage: You can map a function:
def mapping_func(thing): return thing + "PANTS" thing_to_map = Optional.of("thing") mapped_thing = thing_to_map.map(mapping_func) # returns Optional.of("thingPANTS")
Note that if the mapping function returns
None
then the map call will returnOptional.empty()
. Also if you callmap
on an empty optional it will returnOptional.empty()
. -
Best Usage: You can flat map a function which returns an Optional.
def flat_mapping_func(thing): return Optional.of(thing + "PANTS") thing_to_map = Optional.of("thing") mapped_thing = thing_to_map.map(mapping_func) # returns Optional.of("thingPANTS")
Note that this does not return an Optional of an Optional. Use this for mapping functions which return optionals. If the mapping function you use with this does not return an Optional, calling
flat_map
will raise aFlatMapFunctionDoesNotReturnOptionalException
. -
You can compare two optionals:
Optional.empty() == Optional.empty() # True Optional.of("thing") == Optional.of("thing") # True Optional.of("thing") == Optional.empty() # False Optional.of("thing") == Optional.of("PANTS") # False