2015-09-10 9 views
5

Devo creare un oggetto che sollevi un'eccezione personalizzata, UnusableObjectError, quando viene utilizzato in qualsiasi modo (la creazione non dovrebbe tuttavia creare un'eccezione).Oggetto che solleva eccezioni quando utilizzato in qualsiasi modo

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 

mi si avvicinò con il codice qui sotto, che sembra comportarsi come previsto.

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) 

(alcuni miglioramenti apportati, come suggerito da Duncan nei commenti)


Domande:
C'è una classe già esistente che si comporta come descritto?

In caso contrario, c'è qualche difetto nel mio UnusableClass() (ad esempio, situazioni in cui l'utilizzo delle istanze della classe non genera un errore) e, in caso affermativo, come posso risolvere questi difetti?

+3

impostazione '__' metodi su un'istanza non generalmente funzionare come previsto. Dovresti invece definirli direttamente sulla classe. Ridefinire '__getattribute__' per bloccare l'accesso a qualsiasi attributo diverso da quelli che si desidera consentire sarebbe probabilmente un modo più semplice per fare ciò che si sta tentando. – Duncan

+1

Un esempio che non funzionerà come previsto, prova a chiamare un'istanza: 'a()' restituisce 'TypeError: 'UnusableClass' object is not callable' perché l'impostazione' __call__' sull'istanza viene ignorata. Allo stesso modo 'a [1] = 0'. Naturalmente dal momento che questi non sono implementati faranno comunque un errore, ma non il tuo errore personalizzato. Definire sulla classe potrebbe essere con 'def' o usare semplicemente' setattr' sulla classe piuttosto che sull'istanza. – Duncan

+0

Anche questo sembra un perfetto caso d'uso per i metaclassi ... – thebjorn

risposta

2

Risulta che metaclassi e metodi dunder (doppio trattino basso) non vanno bene insieme (il che è un peccato, poiché sarebbe stato un modo più semplice per implementarlo).

Non sono riuscito a trovare alcun elenco di nomi di metodi magico importato, quindi ne ho creato uno e l'ho inserito su PyPi (https://pypi.python.org/pypi/magicmethods/0.1.1). Con esso, l'attuazione di UnusableClass può essere scritto come un semplice decoratore di classe:

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 contiene __new__, __init__ e __del__. Si potrebbe desiderare di regolare questa ..

Questa implementazione gestisce anche:

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

È possibile utilizzare __getattribute__ per bloccare tutto accesso agli attributi, salvo speciali __ attributi come __contains__ o __eq__ che non sono Catturato da __getattribute__, e utilizzare un whitelist per consentire l'accesso ad alcuni metodi:

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) 

Se è davvero necessario prendere anche chiamate con metodi speciali, è possibile utilizzare How to catch any method called on an object in python?.

Problemi correlati