Python: Singledispatch in classe, come spedire il tipo di auto
-
02-01-2020 - |
Domanda
usando Python3.4. Qui voglio usare Singledispatch per spedire diversi tipi in metodo __mul__
. Il codice come questo:
class Vector(object):
## some code not paste
@functools.singledispatch
def __mul__(self, other):
raise NotImplementedError("can't mul these type")
@__mul__.register(int)
@__mul__.register(object) # Becasue can't use Vector , I have to use object
def _(self, other):
result = Vector(len(self)) # start with vector of zeros
for j in range(len(self)):
result[j] = self[j]*other
return result
@__mul__.register(Vector) # how can I use the self't type
@__mul__.register(object) #
def _(self, other):
pass # need impl
.
Come puoi vedere il codice, voglio supportare Vector*Vertor
, questo ha un errore Nome
Traceback (most recent call last):
File "p_algorithms\vector.py", line 6, in <module>
class Vector(object):
File "p_algorithms\vector.py", line 84, in Vector
@__mul__.register(Vector) # how can I use the self't type
NameError: name 'Vector' is not defined
.
La domanda potrebbe essere come posso usare il nome della classe un tipo nel metodo della classe? So che C ++ ha una dichiarazione di classe font. Come Python risolve il mio problema? Ed è strano vedere result = Vector(len(self))
in cui il Vector
può essere utilizzato nel corpo del metodo.
.
Dopo aver guardato http://lukasz.langa.pl / 8 / distruzione singola-funzioni generiche / Posso scegliere questo modo per implementare:
import unittest
from functools import singledispatch
class Vector(object):
"""Represent a vector in a multidimensional space."""
def __init__(self, d):
self._coords = [0 for i in range(0, d)]
self.__init__mul__()
def __init__mul__(self):
__mul__registry = self.__mul__.registry
self.__mul__ = singledispatch(__mul__registry[object])
self.__mul__.register(int, self.mul_int)
self.__mul__.register(Vector, self.mul_Vector)
def __setitem__(self, key, value):
self._coords[key] = value
def __getitem__(self, item):
return self._coords[item]
def __len__(self):
return len(self._coords)
def __str__(self):
return str(self._coords)
@singledispatch
def __mul__(self, other):
print ("error type is ", type(other))
print (type(other))
raise NotImplementedError("can't mul these type")
def mul_int(self,other):
print ("other type is ", type(other))
result = Vector(len(self)) # start with vector of zeros
for j in range(len(self)):
result[j] = self[j]*other
return result
def mul_Vector(self, other):
print ("other type is ", type(other))
#result = Vector(len(self)) # start with vector of zeros
sum = 0
for i in range(0,len(self)):
sum += self._coords[i] * other._coords[i]
return sum
class TestCase(unittest.TestCase):
def test_singledispatch(self):
# the following demonstrates usage of a few methods
v = Vector(5) # construct five-dimensional <0, 0, 0, 0, 0>
for i in range(1,6):
v[i-1] = i
print(v.__mul__(3))
print(v.__mul__(v))
print(v*3)
if __name__ == "__main__":
unittest.main()
.
La risposta è strana:
..other type is <class 'int'> [3, 6, 9, 12, 15] other type is <class '__main__.Vector'> 55 error type is <class 'int'> Traceback (most recent call last): File "p_algorithms\vector.py", line 164, in <module> print(v*3) File "C:\Python34\lib\functools.py", line 710, in wrapper return dispatch(args[0].__class__)(*args, **kw) File "p_algorithms\vector.py", line 111, in __mul__ raise NotImplementedError("can't mul these type")
v.__mul__(3)
può funzionare ma v*3
non può funzionare. Questo è strano dalla mia opzione v*3
è uguale a v.__mul__(3)
.
.
Aggiornamento Dopo il commento di @Martijn Pieters, voglio ancora implementare v*3
in classe. Quindi provo questo
import unittest
from functools import singledispatch
class Vector(object):
@staticmethod
def static_mul_int(self,other):
print ("other type is ", type(other))
result = Vector(len(self)) # start with vector of zeros
for j in range(len(self)):
result[j] = self[j]*other
return result
@singledispatch
@staticmethod
def __static_mul__(cls, other):
print ("error type is ", type(other))
print (type(other))
raise NotImplementedError("can't mul these type")
__mul__registry2 = __static_mul__.registry
__mul__ = singledispatch(__mul__registry2[object])
__mul__.register(int, static_mul_int)
def __init__(self, d):
self._coords = [0 for i in range(0, d)]
self.__init__mul__()
def __init__mul__(self):
__mul__registry = self.__mul__.registry
print ("__mul__registry",__mul__registry,__mul__registry[object])
self.__mul__ = singledispatch(__mul__registry[object])
self.__mul__.register(int, self.mul_int)
print ("at last __mul__registry",self.__mul__.registry)
# @singledispatch
# def __mul__(self, other):
# print ("error type is ", type(other))
# print (type(other))
# raise NotImplementedError("can't mul these type")
def mul_int(self,other):
print ("other type is ", type(other))
result = Vector(len(self)) # start with vector of zeros
for j in range(len(self)):
result[j] = self[j]*other
return result
def __setitem__(self, key, value):
self._coords[key] = value
def __getitem__(self, item):
return self._coords[item]
def __len__(self):
return len(self._coords)
def __str__(self):
return str(self._coords)
class TestCase(unittest.TestCase):
def test_singledispatch(self):
# the following demonstrates usage of a few methods
v = Vector(5) # construct five-dimensional <0, 0, 0, 0, 0>
for i in range(1,6):
v[i-1] = i
print(v.__mul__(3))
print("type(v).__mul__'s registry:",type(v).__mul__.registry)
type(v).__mul__(v, 3)
print(v*3)
if __name__ == "__main__":
unittest.main()
.
questa volta. v.__mul__(3)
ha un errore:
..Traceback (most recent call last): File "test.py", line 73, in test_singledispatch type(v).__mul__(v, 3) File "/usr/lib/python3.4/functools.py", line 708, in wrapper return dispatch(args[0].__class__)(*args, **kw) TypeError: 'staticmethod' object is not callable
Per me il metodo statico dovrebbe agire come il metodo di istanza.
Soluzione
Non è possibile utilizzare functools.singledispatch
sui metodi a tutti , non come un decoratore almeno. Python 3.8 Aggiunge una nuova opzione, solo per i metodi: functools.singledispatchmethod()
.
Non importa che Vector
non sia ancora definito qui; Il primo argomento a qualsiasi metodo sarà sempre self
, mentre useresti la spedizione singola per secondo argomento qui.
Poiché i decoratori si applicano agli oggetti funzionalità prima che venga creato l'oggetto CLASS, è possibile registrare altrettare i tuoi "metodi" come funzioni, esterni del corpo della classe , quindi hai accesso al nome Vector
:
class Vector(object):
@functools.singledispatch
def __mul__(self, other):
return NotImplemented
@Vector.__mul__.register(int)
@Vector.__mul__.register(Vector)
def _(self, other):
result = Vector(len(self)) # start with vector of zeros
for j in range(len(self)):
result[j] = self[j]*other
return result
.
Per i tipi non supportati, è necessario restituire il NotImplemented
singleton , non sollevare un'eccezione. In questo modo Python proverà anche l'operazione inversa.
Tuttavia, poiché la spedizione sta per la chiave su argomento errato (self
) qui comunque, dovrai inventare il proprio meccanismo di spedizione singolo.
Se vuoi davvero utilizzare @functools.singledispatch
che dovresti delegare su una funzione regolare, con gli argomenti investiti :
@functools.singledispatch
def _vector_mul(other, self):
return NotImplemented
class Vector(object):
def __mul__(self, other):
return _vector_mul(other, self)
@_vector_mul.register(int)
def _vector_int_mul(other, self):
result = Vector(len(self))
for j in range(len(self)):
result[j] = self[j] * other
return result
.
Per quanto riguarda i tuoi aggiornamenti utilizzando __init__mul__
: v * 3
è non tradotto in v.__mul__(3)
. È invece tradotto in type(v).__mul__(v, 3)
, vedere Ricerca metodo speciale nel riferimento di DataModel Python. Questo sempre scavalca tutti i metodi impostati direttamente sull'istanza.
Qui type(v)
è Vector
; Python cerca la funzione , non userà un metodo rilegato qui. Ancora una volta, poiché functools.singledispatch
disposta su Primo argomento , sempre, non è possibile utilizzare la spedizione singola direttamente sui metodi di Vector
, perché quel primo argomento sarà sempre un'istanza Vector
.
In altre parole, Python NON Utilizzare i metodi impostati su self
in __init__mul__
; I metodi speciali sono mai Alzati lo sguardo sull'istanza, consultare Metodo speciale Ricerca nella documentazione DataModel.
L'opzione functools.singledispatchmethod()
che Python 3.8 aggiunge utilizza una classe come il decoratore che implementa il Protocollo del Descrittore , proprio come i metodi fanno. Ciò consente di gestire quindi la spedizione prima vincolante (quindi prima che self
venisse antependere all'elenco degli argomenti) e quindi assumere la funzione registrata che restituisce il dispatcher singledispatch
. codice sorgente per questa implementazione è completamente Compatibile con versioni Python più anziane, quindi puoi usarlo invece:
from functools import singledispatch, update_wrapper
# Python 3.8 singledispatchmethod, backported
class singledispatchmethod:
"""Single-dispatch generic method descriptor.
Supports wrapping existing descriptors and handles non-descriptor
callables as instance methods.
"""
def __init__(self, func):
if not callable(func) and not hasattr(func, "__get__"):
raise TypeError(f"{func!r} is not callable or a descriptor")
self.dispatcher = singledispatch(func)
self.func = func
def register(self, cls, method=None):
"""generic_method.register(cls, func) -> func
Registers a new implementation for the given *cls* on a *generic_method*.
"""
return self.dispatcher.register(cls, func=method)
def __get__(self, obj, cls):
def _method(*args, **kwargs):
method = self.dispatcher.dispatch(args[0].__class__)
return method.__get__(obj, cls)(*args, **kwargs)
_method.__isabstractmethod__ = self.__isabstractmethod__
_method.register = self.register
update_wrapper(_method, self.func)
return _method
@property
def __isabstractmethod__(self):
return getattr(self.func, '__isabstractmethod__', False)
.
e applicalo alla tua classe Vector()
. Devi ancora registrare la tua implementazione Vector
per il singolo Dispatch dopo la classe è stata creata, perché solo allora puoi registrare una spedizione per la classe:
class Vector(object):
def __init__(self, d):
self._coords = [0] * d
def __setitem__(self, key, value):
self._coords[key] = value
def __getitem__(self, item):
return self._coords[item]
def __len__(self):
return len(self._coords)
def __repr__(self):
return f"Vector({self._coords!r})"
def __str__(self):
return str(self._coords)
@singledispatchmethod
def __mul__(self, other):
return NotImplemented
@__mul__.register
def _int_mul(self, other: int):
result = Vector(len(self))
for j in range(len(self)):
result[j] = self[j] * other
return result
@Vector.__mul__.register
def _vector_mul(self, other: Vector):
return sum(sc * oc for sc, oc in zip(self._coords, other._coords))
.
Potresti ovviamente creare una sottoclasse prima e una spedizione in base a ciò, dal momento che la spedizione funziona anche per sottoclassi:
class _Vector(object):
def __init__(self, d):
self._coords = [0] * d
class Vector(_Vector):
def __setitem__(self, key, value):
self._coords[key] = value
def __getitem__(self, item):
return self._coords[item]
def __len__(self):
return len(self._coords)
def __repr__(self):
return f"{type(self).__name__}({self._coords!r})"
def __str__(self):
return str(self._coords)
@singledispatchmethod
def __mul__(self, other):
return NotImplemented
@__mul__.register
def _int_mul(self, other: int):
result = Vector(len(self))
for j in range(len(self)):
result[j] = self[j] * other
return result
@__mul__.register
def _vector_mul(self, other: _Vector):
return sum(sc * oc for sc, oc in zip(self._coords, other._coords))
. Altri suggerimenti
Questo è un po 'brutto, come è necessario differire il legame sull'implementazione della moltiplicazione Vector
/ Vector
fino a quando la generazione Vector
è effettivamente definita.Ma l'idea è che la funzione di spedizione singola ha bisogno del primo argomento per essere di tipo arbitrario, quindi Vector.__mul__
chiamerà quella funzione con self
come secondo argomento.
import functools
class Vector:
def __mul__(self, other):
# Python has already dispatched Vector() * object() here, so
# swap the arguments so that our single-dispatch works. Note
# that in general if a*b != b*a, then the _mul_by_other
# implementations need to compensate.
return Vector._mul_by_other(other, self)
@functools.singledispatch
def _mul_by_other(x, y):
raise NotImplementedError("Can't multiply vector by {}".format(type(x)))
@_mul_by_other.register(int)
def _(x, y):
print("Multiply vector by int")
@Vector._mul_by_other.register(Vector)
def _(x, y):
print("Multiply vector by another vector")
x = Vector()
y = Vector()
x * 3
x * y
try:
x * "foo"
except NotImplementedError:
print("Caught attempt to multiply by string")
.