[Python] attributi astratti?

enrico franchi enrico.franchi a gmail.com
Mar 15 Mar 2016 17:38:54 CET


2016-03-11 16:13 GMT+00:00 Marco Santamaria <marco.santamaria a gmail.com>:

sto lavorando ad un piccolo framework nel quale mi sento autorizzato ad
> usare il modulo abc per creare delle classi astratte/interfacce per
> permetterne l'estensione.
>

Ok. Va bene.


>
> Mi sono trovato spesso a dover assicurare la presenza di un attributo
> nelle classi derivate da una classe astratta, senza avere la possibilità di
> fornire un valore di default ragionevole.
>

O a me non e' chiaro quale sia il tuo problema concreto, oppure tu non hai
guardato la doc per abstractproperty.

https://docs.python.org/2/library/abc.html#abc.abstractproperty

E' una cosa sottilmente diversa da quello che vuoi fare tu... che vuole
dire che potrebbe essere perfetto (o essere perfetto una volta che ti
orienti nella sua direzione) oppure totalmente inadatto. Questo lo puoi
sapere solo tu.

Personalmente lo considererei perche' si muove nello stesso spazio di
problemi che descrivi. Ovviamente non funziona *esattamente* come quello
che descrivi. In pratica lui ti assicura la presenza di una *property* che
fa quello che vuoi, mentre un attributo e' un concetto un pochetto piu'
generale.

In [1]: import abc

In [2]: class B(object):
   ...:     __metaclass__ = abc.ABCMeta
   ...:     @abc.abstractproperty
   ...:     def foo(self): pass
   ...:

In [3]: class A(B): pass

In [4]: A()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-3cd318a12eea> in <module>()
----> 1 A()

TypeError: Can't instantiate abstract class A with abstract methods foo

In [5]: A(foo='bar')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-99b4610496e4> in <module>()
----> 1 A(foo='bar')

TypeError: object() takes no parameters

In [6]: class AOK(B):
   ...:     @property
   ...:     def foo(self): return 42
   ...:

In [7]: AOK().foo
Out[7]: 42

In [8]: class ABad(B):
   ...:     def __init__(self, foo):
   ...:         self.foo = foo
   ...:

In [9]: ABad(foo=42).foo
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-d3c627ae930d> in <module>()
----> 1 ABad(foo=42).foo

TypeError: Can't instantiate abstract class ABad with abstract methods foo


> Data la natura dinamica di Python questo può essere fatto in vari modi, ma
> mi chiedo quale sia quello più 'idiomatico' in Python 3.4, facendo in modo
> che venga sollevata il prima possibile un'eccezione chiara se
> quell'attributo non viene definito.
>
> Al momento mi sono venute in mente tre soluzioni:
>
>    1. controllare con hasattr la presenza dell'attributo nel metodo
>    __init__ della classe astratta
>    2. definire nella classe astratta una proprietà (decorata con
>    @property) che solleva un'eccezione NotImplementedError
>    3. definire nella classe astratta un metodo astratto (decorato con
>    @abstractmethod) con lo stesso nome dell'attributo
>
> Sto usando la 3. quando possibile perché solleva un'eccezione prima delle
> altre, ma non mi piace molto l'idea di richiedere un metodo quando voglio
> un attributo.
>

Ok. Se stai usando la 3, passare ad abstractproperty ti avvicina un po' a
quello che vuoi. La 2 e' una versione fatta con lo scotch di
abstractproperty (o meglio, sta ad abstractproperty come fare un metodo che
ti tira not implemented sta ad abstractmethod: specificamente la differenza
e' quando ottieni l'errore; abstract* ti da un errore se quando istanzi
l'oggetto un determinato ente non e' nell'mro dell'oggetto, viceversa
tirare sulla chiamata te lo segnala appunto se e solo se lo chiami... non
entro nel merito del quando preferisco uno o l'altro perche' e' complicato).

hasattr funzionicchia, ma hai un sacco di edge case fastidiosi. Per esempio
ti rende molto vulnerabile all'ordine di risoluzione dei metodi (mro) e al
fatto che qualche derivata *non* chiami l'__init__ che vorresti tu. Secondo
me e'facile impiccarsi con una soluzione che apparentemente funziona, ma in
pratica lascia fuori sufficienti casi da scoprire in produzione che
effettivamente non funzionava.

abstractproperty ha il vantaggio che a fronte di essere marginalmente piu'
scomodo da scrivere e' molto chiaro nell'intento ed e' relativamente
improbabile scavalcarlo "per errore".



-- 
.
..: -enrico-
-------------- parte successiva --------------
Un allegato HTML è stato rimosso...
URL: <http://lists.python.it/pipermail/python/attachments/20160315/add401aa/attachment.html>


Maggiori informazioni sulla lista Python