diff --git a/snippets/undefined.py b/snippets/undefined.py index baeb491..bd1e577 100644 --- a/snippets/undefined.py +++ b/snippets/undefined.py @@ -1,117 +1,64 @@ -# Example of an undefined singleton, usable with eval() functions to limit -# possibility of NameError exceptions. -# -# Use with a namespace decorator like : -# -# class NoNameErrorNamespace: -# def __init__(self, ns): -# self.ns = ns -# -# def __getitem__(self, name): -# try: -# return self.ns[name] -# except KeyError: -# return Undefined() -# -# def __setitem__(self, name, value): -# self.ns[name] = value -# -# eval('x - 1') +from functools import total_ordering -class Undefined: - def __str__(self): - return 'Undefined' +def init_undefined(): + singleton = None - def __getattr__(self, name): - return self + @total_ordering + class Undefined: + def __new__(cls, *args, **kwargs): + return singleton - def __neq__(self, other): - return True + def __call__(self, *args, **kwargs): + return singleton - self = lambda self, *args, **kwargs: self - true = lambda *args: True - false = lambda *args: False + def __getattribute__(self, name): + return singleton - __eq__ = self - __lt__ = false - __gt__ = false - __le__ = false - __ge__ = false + def __add__(self, other): + return singleton - # Arithmetic operators - for op in ['__add__', '__sub__', '__mul__', '__matmul__', '__truediv__', - '__floordiv__', '__mod__', '__divmod__', '__pow__', - '__lshift__', '__rshift__', '__and__', '__xor__', '__or__']: - locals()[op] = self - locals()[op.replace('__', '__r', 1)] = self - locals()[op.replace('__', '__i', 1)] = self + def __sub__(self, other): + return singleton - __call__ = self - __getitem__ = self - __bool__ = false - __abs__ = self - __neg__ = self - __pos__ = self - __invert__ = self - __contains__ = self - __reversed__ = self - __length_hint__ = lambda: 0 - __hash__ = lambda self: id(self) + def __eq__(self, other): + return singleton - __instance = None - def __new__(cls, *args, **kwargs): - if cls.__instance is None: - cls.__instance = super().__new__(cls) - return cls.__instance + def __req__(self, other): + return singleton + + def __lt__(self, other): + return singleton + + def __bool__(self): + return False + + def __getitem__(self, name): + return singleton + + def __setitem__(self, name, value): + pass + + def __setattr__(self, name, value): + pass + + singleton = object.__new__(Undefined) + return Undefined -import operator -import inspect -import pytest +Undefined = init_undefined() +undefined = Undefined() -def test(): - assert Undefined().__lt__(1) is False +assert undefined is Undefined() - assert Undefined() is Undefined() - assert Undefined().x is Undefined() - assert Undefined()['a'] is Undefined() - assert Undefined()() is Undefined() - assert Undefined().x('a', 1, x=3) is Undefined() - assert not Undefined() - assert not bool(Undefined()) - assert (Undefined() - 1) is Undefined() - assert (1 - Undefined()) is Undefined() - - -ops = [op for key, op in operator.__dict__.items() if callable(op) and key.startswith('__')] -@pytest.mark.parametrize('obj', [1, '', [], (), {}, dict()], ids=['integer', 'string', 'list', 'tuple', 'set', 'dict']) -@pytest.mark.parametrize('op', ops, ids=[op.__name__ for op in ops]) -def test_op(op, obj): - if isinstance(op, type): - pytest.skip() - name = op.__name__.strip('_').strip('0') - if name in ['index', 'concat', 'countOf', 'delitem', 'indexOf', 'setitem', 'attrgetter', 'iconcat']: - pytest.skip() - args_len = len([p for p in inspect.signature(op).parameters.values() if p.default is p.empty]) - if args_len == 0: - pytest.skip('no arg') - result = Undefined() - if name in ['lt', 'gt', 'le', 'ge', 'truth', 'is', 'contains']: - result = False - if name in ['ne', 'not', 'is_not']: - result = True - if name in ['length_hint']: - result = 0 - result_reversed = result - if name in ['imod', 'mod'] and obj == '': - result_reversed = '' - if args_len == 1: - assert op(Undefined()) is result, f'failed with {op}' - elif args_len == 2: - assert op(Undefined(), obj) is result, f'failed with {op}' - if name not in ['contains', 'getitem'] or (name == 'ctonains' and obj != '' and hasattr(obj, '__contains__')) or (name == 'getitem' and hasattr(obj, '__getitem__') and obj != '' and obj != [] and obj != () and obj != {}): - assert op(obj, Undefined()) is result_reversed, f'failed with reversed {op}' - else: - raise NotImplementedError(f'{op} {args_len}') +assert Undefined() + Undefined() is Undefined() +assert (Undefined() == 1) is Undefined() +assert not bool(Undefined()) +assert undefined.a is undefined +assert undefined.a.b.c is undefined +assert undefined.a.b.c.d() is undefined +assert undefined[undefined] is undefined +undefined[1] = 0 +undefined.x = 1 +assert undefined.x is undefined