An implementation of the Optional object in Python
Project description
Optional.py
An Implementation of the Optional Object for Python
Why
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. Doing this introduces ambiguity into you code.
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 adds up quickly. These defensive checks obfuscate our actual business logic, decreasing readability. Furthermore, defensive checking is an error prone process, because it is easy to forget to check a required condition.
So we present you with an Optional object as an alternative.
Install
Compatible with both python 2 and 3!
$ pip install optional.py
Usage
-
You can import it using:
from optional import Optional
-
You can set it to empty:
instead of: :scream_cat:
return None
you can do: :smile_cat:
return Optional.empty()
or
return Optional.of()
-
You can set it to have content:
instead of: :scream_cat:
return "thing"
you can do: :smile_cat:
return Optional.of("thing")
-
You can check if its present:
instead of: :scream_cat:
if thing is not None:
you can do: :smile_cat:
thing = some_func_returning_an_optional() if thing.is_present():
-
You can check if its empty:
instead of: :scream_cat:
if thing is None:
you can do: :smile_cat:
thing = some_func_returning_an_optional() if thing.is_empty():
-
You can get the value:
instead of: :scream_cat:
print(thing)
you can do: :smirk_cat:
thing = some_func_returning_an_optional() ... print(thing.get())
but this is not the recommended way to use this library.
-
You can't get the value if its empty:
instead of: :crying_cat_face:
if thing is None: print(None) # very odd
you can do: :smirk_cat:
thing = some_func_returning_an_optional() if thing.is_empty(): print(thing.get()) # **will raise an exception**
but this will raise an exception!
-
Best Usage: You can chain on presence:
instead of: :scream_cat:
if thing is not None: print(thing)
you can do: :heart_eyes_cat:
thing = some_func_returning_an_optional() thing.if_present(lambda thing: print(thing))
-
Best Usage: You can chain on non presence:
instead of: :scream_cat:
if thing is not None: print(thing) else: print("PANTS!")
you can do: :heart_eyes_cat:
thing = some_func_returning_an_optional() thing.if_present(lambda thing: print(thing)).or_else(lambda _: print("PANTS!"))
Note that the lambdas here can be swapped out for actual function names.
-
Best Usage: You can raise on non presence:
instead of: :scream_cat:
if thing is None: raise SomeException("Boom!")
you can do: :heart_eyes_cat:
thing = some_func_returning_an_optional() thing.if_present(lambda thing: print(thing)).or_else_raise(SomeException("Boom!"))
-
Best Usage: You can map a function: :heart_eyes_cat:
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 already returns an Optional: :heart_eyes_cat:
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: :smile_cat:
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
Tests
There is complete test coverage and they pass in both python 2 and 3.
Running Unit Tests
You can run the tests using:
$ python2 -B -m unittest discover
or
$ python3 -B -m unittest discover
Test Coverage
if you install the requirements in test_requirements.txt
using:
$ pip install -r test_requirements.txt
You can check the code coverage using:
$ coverage run -m --branch unittest discover
$ coverage report
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file optional.py-0.2.2.tar.gz
.
File metadata
- Download URL: optional.py-0.2.2.tar.gz
- Upload date:
- Size: 5.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.9.1 setuptools/40.2.0 requests-toolbelt/0.8.0 tqdm/4.25.0 CPython/3.5.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4741a98c88e8983e9eb15dfbaacb1ef314b5a72db3f0df1dfa06368aa15eef90 |
|
MD5 | 39b2f9b3a465d65a710336783a516167 |
|
BLAKE2b-256 | 293fb2ee600e77a8475d2a45eb009f5d439f9aa032f4b6d51c41d84ccaabab11 |