python3 :singledispatch en classe, comment répartir le type automatique
-
02-01-2020 - |
Question
En utilisant python3.4.Ici, je veux utiliser singledispatch pour distribuer différents types __mul__
méthode .Le code comme ceci :
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
Comme vous pouvez voir le code, je veux de l'aide Vector*Vertor
, Il y a une erreur de nom
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 question peut être : Comment puis-je utiliser le nom de classe et le type dans la méthode de la classe ?Je sais que C++ a une déclaration de classe de police.Comment Python résout mon problème ?Et c'est étrange de voir result = Vector(len(self))
où le Vector
peut être utilisé dans le corps de la méthode.
Après avoir jeté un oeil à http://lukasz.langa.pl/8/single-dispatch-generic-functions/Je peux choisir cette façon de mettre en œuvre :
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 réponse est étrange :
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)
peut fonctionner mais v*3
je ne peux pas travailler.C'est étrange D'après mon option v*3
est exactement la même chose que v.__mul__(3)
.
Mise à jour après le commentaire de @Martijn Pieters, je veux toujours l'implémenter v*3
en classe.Alors j'essaye ça
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()
Cette fois . v.__mul__(3)
j'ai une erreur :
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
Pour moi, la méthode statique devrait agir comme la méthode d'instance.
La solution
Vous ne pouvez pas utiliser functools.singledispatch
sur les méthodes du tout, pas en tant que décorateur du moins.Python 3.8 ajoute une nouvelle option, uniquement pour les méthodes : functools.singledispatchmethod()
.
Cela n'a pas d'importance Vector
n'est pas encore défini ici ;le premier argument de n'importe quelle méthode sera toujours self
, alors que vous utiliseriez une répartition unique pour le deuxième argument ici.
Parce que les décorateurs s'appliquent au objets de fonction avant la création de l'objet de classe, vous pouvez tout aussi bien enregistrer vos « méthodes » en tant que fonctions, dehors du corps de classe, vous avez donc accès au Vector
nom:
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
Pour les types non pris en charge, vous devez renvoyer le NotImplemented
singleton, ne déclenche pas d’exception.De cette façon, Python tentera également l'opération inverse.
Cependant, puisque l'expédition va se dérouler sur le mauvais argument (self
) ici de toute façon, vous devrez proposer votre propre mécanisme de répartition unique.
Si vous voulez vraiment utiliser @functools.singledispatch
vous devrez déléguer à une fonction régulière, avec les arguments inverse:
@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
Quant à vos mises à jour en utilisant __init__mul__
: v * 3
est pas traduit en v.__mul__(3)
.Il est plutôt traduit par type(v).__mul__(v, 3)
, voir Recherche de méthode spéciale dans la référence du modèle de données Python.Ce toujours contourne toutes les méthodes définies directement sur l'instance.
Ici type(v)
est Vector
;Python recherche le fonction, il n'utilisera pas de méthode liée ici.Encore une fois, parce que functools.singledispatch
des dépêches sur le d'abord argument, toujours, vous ne pouvez pas utiliser la répartition unique directement sur les méthodes de Vector
, parce que ce premier argument sera toujours un Vector
exemple.
En d’autres termes, Python va pas utilisez les méthodes que vous avez définies self
dans __init__mul__
;des méthodes spéciales sont jamais regardé l'instance, voir Recherche de méthode spéciale dans la documentation du modèle de données.
Le functools.singledispatchmethod()
l'option ajoutée par Python 3.8 utilise un classe en tant que décorateur qui met en œuvre le protocole de descripteur, tout comme les méthodes.Cela lui permet ensuite de gérer l'expédition avant contraignant (donc avant self
serait ajouté à la liste d'arguments), puis liez la fonction enregistrée que le singledispatch
le répartiteur revient.Le code source pour cette implémentation est entièrement compatible avec les anciennes versions de Python, vous pouvez donc l'utiliser à la place :
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)
et appliquez-le à votre Vector()
classe.Vous devez encore enregistrer votre Vector
mise en œuvre pour l'envoi unique après la classe a été créée, car ce n'est qu'alors que vous pourrez enregistrer une expédition pour 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))
Vous pouvez bien sûr également créer d'abord une sous-classe et dispatcher sur cette base, car dispatch fonctionne également pour les sous-classes :
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))
Autres conseils
C'est un peu moche, car vous devez différer la mise en œuvre de Vector
/Vector
multiplication jusqu'à après Vector
est effectivement défini.Mais l’idée est que la fonction de répartition unique a besoin que le premier argument soit de type arbitraire, donc Vector.__mul__
appellera cette fonction avec self
comme deuxième argument.
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")