питон3:Singledispatch в классе, как отправить собственный тип

StackOverflow https://stackoverflow.com//questions/24063788

Вопрос

Использование 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")
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top