Last active
July 26, 2022 16:47
-
-
Save yashshah1/4cf30c19597e744b6d05a8eb572320cf to your computer and use it in GitHub Desktop.
Safe operator in python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Attempt to create something like a safe operator (?. in js) for Python | |
| # | |
| # Solution 1: Overload objects in Python (pollute global name space) | |
| # Turns out this is not actually possible: https://stackoverflow.com/a/4698550 | |
| # and you have to subclass the main class, and create instances. | |
| # This is not a great idea since we would have to stop instantiating inbuilt classes | |
| # and would have weird looking code | |
| # | |
| # Solution 2: Possibly create a wrapper class that wraps instances | |
| # Something like: | |
| # >>> class A(): | |
| # ... def __init__(self): | |
| # ... self.x = 1 | |
| # >>> A().y | |
| # Traceback (most recent call last): | |
| # File "<stdin>", line 1, in <module> | |
| # AttributeError: 'A' object has no attribute 'y' | |
| # >>> | |
| # >>> safe(A()).y == None | |
| # True | |
| class CustomNone: | |
| def __bool__(self): | |
| return False | |
| def __str__(self): | |
| return "None" | |
| def __eq__(self, obj): | |
| return (obj is None or isinstance(obj, CustomNone)) | |
| def __getattr__(self, _): | |
| return self | |
| def __getitem__(self, _): | |
| return self | |
| def __call__(self): | |
| return self | |
| none = CustomNone() | |
| class SafeWrapper(): | |
| def __init__(self, obj): | |
| if obj is None: | |
| self.__obj = none | |
| else: | |
| self.__obj = obj | |
| def __getitem__(self, key: str): | |
| # https://docs.python.org/3/reference/datamodel.html#object.__getitem__ | |
| try: | |
| value = self.__obj.__getitem__(key) | |
| # There is a chance that value is None, which doesn't | |
| # implement __getitem__, so returning None isn't okay here | |
| # which is why we should return a custom value which mimics | |
| # None, but implements some basic stuff | |
| if value == None: | |
| return none | |
| except (KeyError, AttributeError): | |
| return none | |
| return SafeWrapper(value) | |
| def __getattr__(self, key): | |
| # https://docs.python.org/3/library/functions.html#getattr | |
| return SafeWrapper(getattr(self.__obj, key, none)) | |
| def __setattr__(self, name, value): | |
| if name == "_SafeWrapper__obj": | |
| # This is called when setting self.__obj = obj | |
| # so we let python do this! | |
| return super().__setattr__(name, value) | |
| else: | |
| return self.__obj.__setattr__(name, value) | |
| def __repr__(self): | |
| module_name = self.__class__.__module__ | |
| class_name = self.__class__.__name__ | |
| built_in_repr = repr(self.__obj) | |
| return f"{module_name}:{class_name}: {built_in_repr}" | |
| def __str__(self): | |
| return str(self.__obj) | |
| def safe(obj): | |
| # in the case None is passed in, we return a CustomNone object | |
| if obj is None: | |
| return none | |
| return SafeWrapper(obj) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment