Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object that raises exception when used in any way

I need to create an object that would raise a custom exception, UnusableObjectError, when it is used in any way (creating it should not create an exception though).

a = UnusableClass()     # No error
b = UnusableClass()     # No error
a == 4                  # Raises UnusableObjectError
'x' in a                # Raises UnusableObjectError
for i in a:             # Raises UnusableObjectError
    print(i)

# ..and so on

I came up with the code below which seems to behave as expected.

class UnusableObjectError(Exception):
    pass

CLASSES_WITH_MAGIC_METHODS = (str(), object, float(), dict())

# Combines all magic methods I can think of.
MAGIC_METHODS_TO_CHANGE = set()
for i in CLASSES_WITH_MAGIC_METHODS:
    MAGIC_METHODS_TO_CHANGE |= set(dir(i))
MAGIC_METHODS_TO_CHANGE.add('__call__')
# __init__ and __new__ must not raise an UnusableObjectError
# otherwise it would raise error even on creation of objects.
MAGIC_METHODS_TO_CHANGE -= {'__class__', '__init__', '__new__'}


def error_func(*args, **kwargs):
    """(nearly) all magic methods will be set to this function."""
    raise UnusableObjectError


class UnusableClass(object):
    pass

for i in MAGIC_METHODS_TO_CHANGE:
    setattr(UnusableClass, i, error_func)

(some improvements made, as suggested by Duncan in comments)


Questions:
Is there an already existing class that behaves as described?

If not, is there any flaw in my UnusableClass() (e.g., situations when using the instances of the class wouldn't raise an error) and if so, how can I fix those flaws?

like image 809
user Avatar asked Oct 19 '22 02:10

user


2 Answers

Turns out metaclasses and dunder (double underscore) methods don't go well together (which is unfortunate, since that would have been a more streamlined way to implement this).

I couldn't find any importable listing of magic method names, so I created one and put it on PyPi (https://pypi.python.org/pypi/magicmethods/0.1.1). With it, the implementation of UnusableClass can be written as a simple class decorator:

import magicmethods

class UnusableObjectError(Exception): 
    pass

def unusable(cls):
    def _unusable(*args, **kwargs):
        raise UnusableObjectError()

    for name in set(magicmethods.all) - set(magicmethods.lifecycle):
        setattr(cls, name, _unusable)
    return cls

@unusable
class UnusableClass(object):
    pass

magicmethods.lifecycle contains __new__, __init__, and __del__. You might want to adjust this..

This implementation also handles:

a = UnusableClass()
with a:
    print 'oops'
like image 108
thebjorn Avatar answered Nov 15 '22 05:11

thebjorn


You can use __getattribute__ to block all access to attributes, except special __ attributes like __contains__ or __eq__ which are not catched by __getattribute__, and use a whitelist to allow access to some methods:

class UnuseableClass(object):

    whitelist = ('alpha', 'echo',)

    def __init__(self):
        self.alpha = 42

    def echo(self, text):
        print text

    def not_callable(self):
        return 113

    def __getattribute__(self, name):
        if name in type(self).whitelist:
            return super(UnuseableClass, self).__getattribute__(name)
        else:
            raise Exception('Attribute is not useable: %s' % name)


unuseable_object = UnuseableClass()
print(unuseable_object.alpha)
unuseable_object.echo('calling echo')

try:
    unuseable_object.not_callable()
except Exception as exc:
    print(exc.message)

If you really need to catch even special method calls, you can use How to catch any method called on an object in python?.

like image 23
amirouche Avatar answered Nov 15 '22 07:11

amirouche