2015-09-10 14 views
5

Potrzebuję utworzyć obiekt, który podniósłby niestandardowy wyjątek, UnusableObjectError, gdy jest używany w jakikolwiek sposób (tworzenie go nie powinno jednak powodować wyjątku).Obiekt, który wywołuje wyjątek, gdy jest używany w jakikolwiek sposób

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 

wymyśliłem poniższy kod, który wydaje się zachowywać zgodnie z oczekiwaniami.

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) 

(niektóre ulepszenia dokonane, jak sugeruje Duncan w komentarzach)


pytania:
Czy istnieje już istniejące klasy, która zachowuje się jak opisano?

Jeśli nie, czy jest jakaś usterka w moim UnusableClass() (np. Sytuacje, w których użycie wystąpień klasy nie spowodowałoby wystąpienia błędu), a jeśli tak, jak mogę naprawić te błędy?

+3

Setting '__' metody na instancji nie generalnie będzie działał zgodnie z oczekiwaniami. Powinieneś zdefiniować je bezpośrednio w klasie. Przedefiniowanie '__getattribute__' w celu zablokowania dostępu do dowolnego atrybutu innego niż te, które chcesz zatwierdzić, prawdopodobnie byłby prostszym sposobem wykonania tego, co próbujesz. – Duncan

+1

Jeden przykład, który nie zadziała tak, jak oczekujesz, spróbuj wywołać instancję: 'a()' daje obiekt 'TypeError: 'UnusableClass' nie można wywołać' ponieważ ustawienie '__call__' na instancji jest ignorowane. Podobnie 'a [1] = 0'. Oczywiście, ponieważ nie zostaną one zaimplementowane, nadal będą zgłaszać błąd, a nie zwyczajny błąd. Definicja klasy może być z 'def' lub po prostu użyj' setattr' na klasie, zamiast na instancji. – Duncan

+0

To także wydaje się być idealnym przypadkiem użycia dla metaclasses ... – thebjorn

Odpowiedz

2

Okazuje się, że metody metaclasses i dunder (double podkreślenia) nie pasują do siebie (co jest niefortunne, ponieważ byłby to usprawniony sposób wdrożenia tego).

Nie mogłem znaleźć żadnych importowalnych list nazw magicznych metod, więc stworzyłem jeden i umieściłem go na PyPi (https://pypi.python.org/pypi/magicmethods/0.1.1). Dzięki niemu, realizacja UnusableClass można zapisać jako prosty klasy dekoratora:

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 zawiera __new__, __init__ i __del__. Może chcesz, aby dostosować ten ..

Ta implementacja obsługuje również:

a = UnusableClass() 
with a: 
    print 'oops' 
1

Można użyć __getattribute__ zablokować wszystkie dostęp do atrybutów, wyjątkiem szczególnych __ atrybutów takich jak __contains__ lub __eq__ które nie są przechwyconych przez __getattribute__ i użyć whitelist aby umożliwić dostęp do niektórych metod:

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) 

Jeśli naprawdę potrzebujesz złapać nawet specjalne wywołania metod, możesz użyć How to catch any method called on an object in python?.

Powiązane problemy