Skip to main content

An implementation of the Optional object in Python

Project description

Optional.py

Build Status Build Status Coverage Status License Python Versions Contributors Open Source Love Downloads

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

  1. You can import it using:

    from optional import Optional
    
  2. You can set it to empty:

    instead of: :scream_cat:

    return None
    

    you can do: :smile_cat:

    return Optional.empty()
    

    or

    return Optional.of()
    
  3. You can set it to have content:

    instead of: :scream_cat:

    return "thing"
    

    you can do: :smile_cat:

    return Optional.of("thing")
    
  4. 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():
    

    or, alternatively: :smirk_cat:

    thing = some_func_returning_an_optional()
    if thing:
    
  5. 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():
    

    or, alternatively: :smirk_cat:

    thing = some_func_returning_an_optional()
    if not thing:
    
  6. 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.

  7. 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!

  8. You can get_or_default which takes a default value:

    instead of: :crying_cat_face:

    thing = some_func_may_return_none()
    if thing is None:
         thing = '23'
    

    or: :scream_cat:

    thing = Optional.of(some_func_may_return_none())
    if thing.is_empty():
        thing = '23'
    else:
        thing = thing.get()
    

    you can do: :smirk_cat:

    thing = Optional.of(some_func_may_return_none()).get_or_default('23')
    
  9. You can get_or_raise to raise any exception on abscence:

    instead of: :scream_cat:

    try:
        thing = some_func_returning_an_optional()
        return thing.get()
    except OptionalAccessOfEmptyException:
        raise MyCustomException()
    

    you can do: :heart_eyes_cat:

    return some_func_returning_an_optional().get_or_raise(
        MyCustomException()
    )
    
  10. 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))
    
  11. 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.

  12. Best Usage: You can run a supplier on non presence:

    instead of: :scream_cat:

        thing = some_func_returning_an_empty_optional()
        if thing.is_empty():
            thing = Optional.of("pants")
        print(thing.get()) # Prints "pants"
    

    you can do: :heart_eyes_cat:

        def some_supplier():
            return "pants"
        thing = some_func_returning_an_empty_optional().or_else(some_supplier)
        print(thing.get()) # Prints "pants"
    
  13. 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!"))
    
  14. 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 return Optional.empty(). Also if you call map on an empty optional it will return Optional.empty().

  15. 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 a FlatMapFunctionDoesNotReturnOptionalException.

  16. 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

First, install poetry using the instructions located here.

Then, install the requirements using:

$ poetry install

You can run the tests using:

$ poetry run pytest

Test Coverage

You can check the code coverage using:

$ poetry run pytest --cov=optional

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

optional.py-1.3.2.tar.gz (6.6 kB view details)

Uploaded Source

Built Distribution

optional.py-1.3.2-py2.py3-none-any.whl (6.6 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file optional.py-1.3.2.tar.gz.

File metadata

  • Download URL: optional.py-1.3.2.tar.gz
  • Upload date:
  • Size: 6.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/32.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.62.3 importlib-metadata/4.10.1 keyring/21.8.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.1

File hashes

Hashes for optional.py-1.3.2.tar.gz
Algorithm Hash digest
SHA256 e0040b29d8e671245b92fffe3abc5aa3bd8870e9a804d7ac7ad377a370cccadc
MD5 0fd66c9b34e28ed8b275c3bbccfae262
BLAKE2b-256 82ba318a40ead0b400c7aa9a126172d63a3c99a3b36cbd2357c679d05e5c7e7a

See more details on using hashes here.

File details

Details for the file optional.py-1.3.2-py2.py3-none-any.whl.

File metadata

  • Download URL: optional.py-1.3.2-py2.py3-none-any.whl
  • Upload date:
  • Size: 6.6 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/32.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.62.3 importlib-metadata/4.10.1 keyring/21.8.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.1

File hashes

Hashes for optional.py-1.3.2-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 41b575c96377b6f8fedc4aef0d0bcc172bbf7f143089f149dc2b4c0812fe2984
MD5 b861a6cc7316df8f1d398b56792d41e0
BLAKE2b-256 d8c23200f7eea7b09d826a5772c3440bd42e17f04f5547ca6d1d6e4e6fdf62f9

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page