【问题标题】:python3: singledispatch in class, how to dispatch self typepython3:类中的singledispatch,如何调度self类型
【发布时间】:2014-07-26 15:48:47
【问题描述】:

使用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

问题可能是如何在类的方法中使用类名类型?我知道 C++ 有字体类声明。 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*3v.__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

对我来说,静态方法应该像实例方法一样。

【问题讨论】:

  • v * 3v.__mul__(3) 不一样;它与type(v).__mul__(v, 3) 相同。见Special method lookup
  • 我编码的v.__mul__(3),确实像__mul__(v,3),这里v是对象,3是我在·__init__mul__·中注册的类型(就像我绑定第二个这个有趣的 arg 发送)。如果 type(v).__mul__(v, 3) 不一样。与 v.__mul__(3) 有什么区别。在c语言级别?
  • 了解 Python 函数充当描述符很重要;见From Function to Methoddescriptor HOWTO;这就是 Python 如何绑定一个函数到一个实例,给你一个方法。
  • 但是,对于特殊方法(所以__mul____add____hash__等,以支持创建一个类和其他用例的哈希), Python 绕过绑定方法,而是手动传入selftype(v).__mul__(v, 3) 接受未绑定的函数,将v 直接作为self 传入。
  • 不清楚你为什么在你的赏金信息中问你的小问题;无论哪种方式,你都不能在方法上使用functools.singledispatch,因为装饰器总是应用于未绑定函数,而不是方法,所以第一个参数是alwaysself。我的回答通过将方法委托给单独的外部 singledispatch 函数来回避这个问题。

标签: python class types python-3.4 dispatch


【解决方案1】:

你不能在方法上使用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

对于不支持的类型,您需要返回NotImplementedsingleton,而不是引发异常。这样 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 数据模型参考中的 Special method lookup。这总是绕过直接在实例上设置的任何方法。

这里type(v)Vector; Python 查找函数,它不会在这里使用绑定方法。同样,因为functools.singledispatchfirst 参数上调度,所以你不能直接在Vector 的方法上使用单个调度,因为第一个参数总是一个Vector 实例。

换句话说,Python 将不会使用您在__init__mul__ 中设置的self 方法; 从不在实例上查找特殊方法,请参阅数据模型文档中的 Special method lookup

Python 3.8 添加的functools.singledispatchmethod() 选项使用 作为实现descriptor protocol 的装饰器,就像方法一样。这让它可以处理调度 before 绑定(所以在 self 之前将被添加到参数列表中),然后绑定 singledispatch 调度程序返回的注册函数。 source code for this implementation 与较旧的 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))

【讨论】:

  • 您能否定义一个静态方法_mul_by_others(other, self) 用于单次调度,然后有def __mul__(self, other): return Vector._mul_by_other(other, self)
  • @chepner:我想是的;那么至少第一个参数是另一种类型。
  • @Sardathrion: 啊,我记得 other 原因这不起作用:在 type 上查找特殊方法,而不是实例。所以在实例上设置__mul__ 属性将被忽略,singledispatch 钩子也是如此。
  • 事情在 3.8 中发生了变化stackoverflow.com/q/24601722/5986907
  • @JoelBerkeley:感谢您的提醒,我已更新此答案以引入该实施。
【解决方案2】:

这有点难看,因为您需要将绑定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")

【讨论】:

  • 我可以在有趣的正文中使用 Vector,例如 Vector() 。确实,类中方法的装饰器喜欢 static var make self.__mul__ = @functools.singledispatch(self.__mul__) 所以定义类之前的这个 eval ?
  • 在Vector之外还是有乐趣的,能不能在课堂上移动一下。
  • 您只能在方法定义中使用Vector(),因为直到您调用函数(全局)名称Vector 之后才会真正查找名称已绑定到您定义的类。您不能在类范围内使用Vector,因为名称尚未退出,并且 Python 没有前向声明。
猜你喜欢
  • 1970-01-01
  • 2021-07-14
  • 2020-12-18
  • 1970-01-01
  • 2015-04-28
  • 2019-04-09
  • 2019-07-11
  • 1970-01-01
  • 2017-12-01
相关资源
最近更新 更多