【问题标题】:How to set default behaviors of magic methods in python?如何在python中设置魔术方法的默认行为?
【发布时间】:2019-07-25 12:56:46
【问题描述】:

假设我想为 numpy 数组创建一个包装类 Image。我的目标是让它表现得像一个 2D 数组,但有一些额外的功能(这在这里并不重要)。我这样做是因为继承 numpy 数组比较麻烦。

import numpy as np


class Image(object):
    def __init__(self, data: np.ndarray):
        self._data = np.array(data)

    def __getitem__(self, item):
        return self._data.__getitem__(item)

    def __setitem__(self, key, value):
        self._data.__setitem__(key, value)

    def __getattr__(self, item):
        # delegates array's attributes and methods, except dunders.
        try:
            return getattr(self._data, item)
        except AttributeError:
            raise AttributeError()

    # binary operations
    def __add__(self, other):
        return Image(self._data.__add__(other))

    def __sub__(self, other):
        return Image(self._data.__sub__(other))

    # many more follow ... How to avoid this redundancy?

如您所见,我希望拥有所有用于数值运算的魔法方法,就像普通的 numpy 数组一样,但返回值为 Image 类型。所以这些魔术方法的实现,即__add____sub____truediv__等等,几乎是一样的,有点傻。我的问题是是否有办法避免这种冗余?

除了我在这里具体做的事情之外,有没有办法通过某种元编程技术在一个地方编写魔法方法,或者这是不可能的?我搜索了一些有关python元类的信息,但我仍然不清楚。

注意__getattr__ 不会处理魔术方法的委托。见this

编辑

澄清一下,我理解继承是解决此类问题的一般解决方案,尽管我的经验非常有限。但我觉得继承 numpy 数组真的不是一个好主意。因为 numpy 数组需要处理视图转换和 ufunc(参见this)。当你在其他 py-libs 中使用你的子类时,你还需要考虑你的数组子类如何与其他数组子类相处。见我的stupid gh-issue。这就是我寻找替代品的原因。

【问题讨论】:

  • 继承怎么比较麻烦?这听起来像是更好的方法。
  • 因为 numpy 以比普通 python 类型更多的方式构造新的类似数组的对象(数组可以进行视图转换)。请参阅doc 了解更多信息。
  • 你可能想看看__array_function__ 协议(不过你需要一个最近的numpy)
  • 由于从ndarray 继承或编写像你这样的类的笨拙,更倾向于使用一些函数来进行专门的数组操作,并将类方法保留为更大您可以花时间完成全面工作的项目。 np.matrixnp.ma 是子类化 ndarray 的重要示例。 Python 为其集合提供了混合,但我不知道 numpy 的任何内容。

标签: python numpy metaprogramming


【解决方案1】:

魔术方法总是在类中查找并完全绕过 getattribute,因此您必须在类中定义它们。 https://docs.python.org/3/reference/datamodel.html#special-lookup

但是,您可以节省一些打字时间:

import operator
def make_bin_op(oper):
    def op(self, other):
        if isinstance(other, Image): 
            return Image(oper(self._data, other._data))
        else:
            return Image(oper(self._data, other))
    return op

class Image:
    ...
    __add__ = make_bin_op(operator.add)
    __sub__ = make_bin_op(operator.sub)

如果您愿意,您可以创建一个dict 的运算符名称和相应的运算符,并使用装饰器添加它们。例如

OPER_DICT = {'__add__' : operator.add, '__sub__' : operator.sub, ...}
def add_operators(cls):
    for k,v in OPER_DICT.items():
        setattr(cls, k, make_bin_op(v))

@add_operators
class Image:
    ...

您可以使用元类来做同样的事情。但是,除非您真正了解发生了什么,否则您可能不想使用元类。

【讨论】:

    【解决方案2】:

    您所追求的是称为继承的概念,它是面向对象编程的关键部分(参见维基百科here

    当你用class Image(object): 定义你的类时,这意味着Imageobject 的一个子类,它是一个很少做的内置类型。您的功能被添加到那个或多或少的空白概念上。但是如果你用class Image(np.array): 定义你的类,那么Image 将是array 的子类,这意味着它将继承数组类的所有默认功能。本质上,您想要保留的任何类方法都不应该重新定义。如果您不编写__getitem__ 函数,它会使用array 中定义的函数。

    如果您需要在这些函数中添加额外的功能,您仍然可以重新定义它们(称为覆盖),然后使用super().__getitem__(或其他)访问在继承的函数中定义的函数班级。例如,__init__ 经常发生这种情况。

    如需更详尽的解释,请查看 Think Python 中的 the chapter on inheritance

    【讨论】:

    猜你喜欢
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-21
    • 1970-01-01
    • 1970-01-01
    • 2021-08-24
    • 2014-11-29
    相关资源
    最近更新 更多