питон3:Singledispatch в классе, как отправить собственный тип
-
02-01-2020 - |
Вопрос
Использование Python3.4.Здесь я хочу использовать singledispatch для отправки разных типов в __mul__
метод .Код такой:
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
Как вы можете видеть код, мне нужна поддержка Vector*Vertor
, Ошибка имени
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
Может возникнуть вопрос: как я могу использовать имя класса и тип в методе класса?Я знаю, что в С++ есть оператор класса шрифта.Как Python решает мою проблему?И странно видеть result = Vector(len(self))
где Vector
может использоваться в теле метода.
После того, как взглянем на http://lukasz.langa.pl/8/single-dispatch-generic-functions/Я могу выбрать такой способ реализации:
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()
Ответ странный:
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)
может работать, но v*3
не могу работать.Это странно, мой вариант v*3
это то же самое, что v.__mul__(3)
.
Обновление после комментария @Martijn Pieters, я все еще хочу реализовать v*3
в классе.Поэтому я пробую это
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()
На этот раз . v.__mul__(3)
есть ошибка:
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
Для меня статический метод должен действовать как метод экземпляра.
Решение
Вы не можете использовать functools.singledispatch
о методах вообще, по крайней мере, не как декоратор.Python 3.8 добавляет новую опцию, только для методов: functools.singledispatchmethod()
.
Это не имеет значения, что Vector
здесь это еще не определено;первым аргументом любого метода всегда будет self
, в то время как вы бы использовали единую отправку для второй аргумент вот.
Потому что декораторы применяются к функциональные объекты перед созданием объекта класса вы могли бы с таким же успехом зарегистрировать свои "методы" как функции, снаружи тела класса, так что у вас есть доступ к 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
Для неподдерживаемых типов вам необходимо вернуть NotImplemented
синглтон, не вызывает исключения.Таким образом, Python также попробует выполнить обратную операцию.
Однако, поскольку отправка будет осуществляться с помощью ключа на неверный аргумент (self
) в любом случае, здесь вам придется придумать свой собственный единый механизм отправки.
Если вы действительно хотите использовать @functools.singledispatch
вам пришлось бы делегировать обычной функции с аргументами перевернутый:
@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
Что касается ваших обновлений, использующих __init__mul__
: v * 3
является нет переведенный на v.__mul__(3)
.Вместо этого оно переводится на type(v).__mul__(v, 3)
, видеть Поиск по специальному методу в ссылке на Python datamodel.Это всегда обходит все методы, установленные непосредственно в экземпляре.
Здесь type(v)
является Vector
;Python просматривает функция, здесь не будет использоваться связанный метод.Опять же, потому что functools.singledispatch
депеши по Первый аргумент, всегда, вы не можете использовать одиночную отправку непосредственно для методов Vector
, потому что этот первый аргумент всегда будет Vector
пример.
Другими словами, Python будет нет используйте методы, которые вы установили на self
в __init__mul__
;специальными методами являются никогда посмотрел на этот экземпляр, видите Поиск по специальному методу в документации datamodel.
Тот Самый functools.singledispatchmethod()
опция, которую добавляет Python 3.8, использует класс как декоратор, который реализует протокол дескриптора, точно так же, как это делают методы.Это позволяет ему затем обрабатывать отправку до того, как привязка (так что перед self
будет добавляться к списку аргументов), а затем привяжите зарегистрированную функцию, которая singledispatch
возвращается диспетчер.Тот Самый исходный код для этой реализации полностью совместим со старыми версиями Python, так что вы могли бы использовать это вместо:
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)
и примените это к своему Vector()
класс.Вам все равно придется зарегистрировать свой Vector
реализация для единой отправки после класс был создан, потому что только после этого вы сможете зарегистрировать отправку для этого класса:
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))
Конечно, вы могли бы также сначала создать подкласс и отправить его на основе этого, поскольку отправка работает и для подклассов:
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))
Другие советы
Это немного некрасиво, так как вам нужно отложить привязку реализации Vector
/Vector
умножение до тех пор, пока Vector
фактически определяется.Но идея состоит в том, что функции одиночной диспетчеризации необходимо, чтобы первый аргумент был произвольного типа, поэтому Vector.__mul__
вызовет эту функцию с помощью self
в качестве второго аргумента.
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")