Skip to content

Instantly share code, notes, and snippets.

@taikedz
Last active October 17, 2023 10:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save taikedz/44e3af7d30d124f10974ca45f9780021 to your computer and use it in GitHub Desktop.
Save taikedz/44e3af7d30d124f10974ca45f9780021 to your computer and use it in GitHub Desktop.
Rust-style Option objects in Python

Mimic Rust's Option type in Python

Commentary published in an article: https://dev.to/taikedz/rusts-option-type-in-python-547p

How to use this

Using this type allows the developer to express nullness and force the recipient of the Option to code for it. The Option does not stand in for the value, the value must be unpacked from it.

def some_func() -> Option:
    some_value = None
    
    # ... implementation ...

    if valid:
        return Option("some value")
    elif not_valid:
        # Explicit null-return
        return Null
    # or even
    return Option(some_value) # will behave like Null if some_value is still None

res = some_func()
if res.is_null():
    ... # handle it
value = res.unwrap()

it can be ignored, or a default result can be used instead:

# Without a check, this may "panic" (borrowing from rust's terminology)
#  the program exits systematically with an error message
value = some_func().unwrap("failed to get data from some_func")

# On occurrence of nullity, a default value is substituted instead.
value = some_func().or_default("default data")

on nullity, you may want to use an exception:

value = some_func().or_raise(ValueError, "could not get value")

Why use this ?

This makes more sense if you need to force consumers of the code to think about nullity.

In that sense, making use of this mechanism is opinionated.

The likelihood of other python programmers enjoying this is fairly low, even as a semantically succinct technique for way-marking nullity and making ensuring results are checked properly.

Thus, you might want to use it

  • as a teaching example to build in good habits
  • as your own way of making yourself more conscientious of nullity
  • preventing yourself from writing un-checked code later on when you consume your own work ...
""" Module to mimick rust's null-safety type.
"""
import traceback
import sys
_NULL_MESSAGE="attempted to access a value that turned out Null"
class Panic(Exception):
def __init__(self, message):
print(f"PANIC: {message}", file=sys.stderr)
# Print the stack, omitting last two:
# - the call to this panic object
# - the call to this object's init function
traceback.print_stack(limit = -2)
sys.exit(100)
class Option:
def __init__(self, value):
self.value = value
def or_default(self, value):
if self.value is None:
return value
return self.value
def or_raise(self, exception_type, *a, **k):
if self.value is None:
raise exception_type(*a, **k)
return self.value
def unwrap(self, message=_NULL_MESSAGE):
if self.value is None:
raise Panic(message)
return self.value
def is_null(self):
return self.value is None
class Null(Option):
""" A representation of null-ness
Do `return Null` to express nullity.
"""
def __init__(self):
raise Panic("Cannot instantiate Null")
@staticmethod
def or_default(value):
return value
@staticmethod
def or_raise(exception_type, *a, **k):
return exception_type(*a, **k)
@staticmethod
def unwrap(message=_NULL_MESSAGE):
raise Panic(message)
@staticmethod
def is_null():
return True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment