是否有可能在Python中“捕获”魔术方法?

时间:2013-09-21 23:31:53

标签: python magic-methods

受到this question的启发,我认为把一个“MutableNum”类放在一起是有趣的,在尽可能多的情况下,它可以像标准的数字类型一样运行,但它会是可变的,所以类似下面的东西会起作用:

def double(x): x *= 2

x = MutableNum(9)
print(x)             # 9
double(x)
print(x)             # 18

我得到了以下内容:

class MutableNum():
    val = None
    def __init__(self, v): self.val = v
    # Comparison Methods
    def __eq__(self, x):    return self.val == x
    def __ne__(self, x):    return self.val != x
    def __lt__(self, x):    return self.val <  x
    def __gt__(self, x):    return self.val >  x
    def __le__(self, x):    return self.val <= x
    def __ge__(self, x):    return self.val >= x
    # Arithmetic
    def __mul__(self, x):   return self.__class__(self.val * x)
    def __rmul__(self, x):  return self.__class__(self.val * x)
    # Casts
    def __int__(self):      return self.val
    # Represenation
    def __str__(self):      return "%d" % (self.val)
    def __repr__(self):     return "%s(%d)" % (self.__class__.__name__, self.val)

哪个有效(到目前为止,据我所知),但我发现自己想要“捕捉”魔法,因为其中很多都会遵循非常相似的结构。

例如,我想抓住__mul____add____sub__等类似的内容:

def catch(self, method, x): return MutableNum(self.val.method(x))

因此对于__add__catch()将返回

return MutableNum(self.val.__add__(x))

这样的事情可能吗?或者我应该像我已经完成的那样实施所有魔术方法?

编辑:我尝试用__getattr__(self,key)捕捉魔术方法进行了一些尝试,但结果好坏参与。

提前致谢。

编辑2

在大家的帮助下,这就是我想出的:

class MutableNum(object):
    __val__ = None
    def __init__(self, v): self.__val__ = v
    # Comparison Methods
    def __eq__(self, x):        return self.__val__ == x
    def __ne__(self, x):        return self.__val__ != x
    def __lt__(self, x):        return self.__val__ <  x
    def __gt__(self, x):        return self.__val__ >  x
    def __le__(self, x):        return self.__val__ <= x
    def __ge__(self, x):        return self.__val__ >= x
    def __cmp__(self, x):       return 0 if self.__val__ == x else 1 if self.__val__ > 0 else -1
    # Unary Ops
    def __pos__(self):          return self.__class__(+self.__val__)
    def __neg__(self):          return self.__class__(-self.__val__)
    def __abs__(self):          return self.__class__(abs(self.__val__))
    # Bitwise Unary Ops
    def __invert__(self):       return self.__class__(~self.__val__)
    # Arithmetic Binary Ops
    def __add__(self, x):       return self.__class__(self.__val__ + x)
    def __sub__(self, x):       return self.__class__(self.__val__ - x)
    def __mul__(self, x):       return self.__class__(self.__val__ * x)
    def __div__(self, x):       return self.__class__(self.__val__ / x)
    def __mod__(self, x):       return self.__class__(self.__val__ % x)
    def __pow__(self, x):       return self.__class__(self.__val__ ** x)
    def __floordiv__(self, x):  return self.__class__(self.__val__ // x)
    def __divmod__(self, x):    return self.__class__(divmod(self.__val__, x))
    def __truediv__(self, x):   return self.__class__(self.__val__.__truediv__(x))
    # Reflected Arithmetic Binary Ops
    def __radd__(self, x):      return self.__class__(x + self.__val__)
    def __rsub__(self, x):      return self.__class__(x - self.__val__)
    def __rmul__(self, x):      return self.__class__(x * self.__val__)
    def __rdiv__(self, x):      return self.__class__(x / self.__val__)
    def __rmod__(self, x):      return self.__class__(x % self.__val__)
    def __rpow__(self, x):      return self.__class__(x ** self.__val__)
    def __rfloordiv__(self, x): return self.__class__(x // self.__val__)
    def __rdivmod__(self, x):   return self.__class__(divmod(x, self.__val__))
    def __rtruediv__(self, x):  return self.__class__(x.__truediv__(self.__val__))
    # Bitwise Binary Ops
    def __and__(self, x):       return self.__class__(self.__val__ & x)
    def __or__(self, x):        return self.__class__(self.__val__ | x)
    def __xor__(self, x):       return self.__class__(self.__val__ ^ x)
    def __lshift__(self, x):    return self.__class__(self.__val__ << x)
    def __rshift__(self, x):    return self.__class__(self.__val__ >> x)
    # Reflected Bitwise Binary Ops
    def __rand__(self, x):      return self.__class__(x & self.__val__)
    def __ror__(self, x):       return self.__class__(x | self.__val__)
    def __rxor__(self, x):      return self.__class__(x ^ self.__val__)
    def __rlshift__(self, x):   return self.__class__(x << self.__val__)
    def __rrshift__(self, x):   return self.__class__(x >> self.__val__)
    # Compound Assignment
    def __iadd__(self, x):      self.__val__ += x; return self
    def __isub__(self, x):      self.__val__ -= x; return self
    def __imul__(self, x):      self.__val__ *= x; return self
    def __idiv__(self, x):      self.__val__ /= x; return self
    def __imod__(self, x):      self.__val__ %= x; return self
    def __ipow__(self, x):      self.__val__ **= x; return self
    # Casts
    def __nonzero__(self):      return self.__val__ != 0
    def __int__(self):          return self.__val__.__int__()               # XXX
    def __float__(self):        return self.__val__.__float__()             # XXX
    def __long__(self):         return self.__val__.__long__()              # XXX
    # Conversions
    def __oct__(self):          return self.__val__.__oct__()               # XXX
    def __hex__(self):          return self.__val__.__hex__()               # XXX
    def __str__(self):          return self.__val__.__str__()               # XXX
    # Random Ops
    def __index__(self):        return self.__val__.__index__()             # XXX
    def __trunc__(self):        return self.__val__.__trunc__()             # XXX
    def __coerce__(self, x):    return self.__val__.__coerce__(x)
    # Represenation
    def __repr__(self):         return "%s(%d)" % (self.__class__.__name__, self.__val__)
    # Define innertype, a function that returns the type of the inner value self.__val__
    def innertype(self):        return type(self.__val__)
    # Define set, a function that you can use to set the value of the instance
    def set(self, x):
        if   isinstance(x, (int, long, float)): self.__val__ = x
        elif isinstance(x, self.__class__): self.__val__ = x.__val__
        else: raise TypeError("expected a numeric type")
    # Pass anything else along to self.__val__
    def __getattr__(self, attr):
        print("getattr: " + attr)
        return getattr(self.__val__, attr)

我使用了标题和粗略的测试套件here放了整个班级。

mgilson建议使用@total_ordering将简化这一点。

只要您遵循使用指南(例如,使用x *= 2代替x = x * 2),您似乎就可以了。

虽然,简单地将参数包装在列表中然后修改x[0]似乎要容易得多 - 仍然是一个有趣的项目。

2 个答案:

答案 0 :(得分:2)

最简单的方法是手动实现它们。如果这是你要添加到很多类的东西那么你可能会看到元类(可以是大脑融化)或类装饰器(更容易处理),但是你应该手动完成它,这样你就知道发生了什么

__getattr__仅在某些情况下有效的原因是,只有在类或其任何基类中找不到它所寻找的名称时才会调用它。因此,如果__xyz__上找到object,则不会调用__getattr__

答案 1 :(得分:2)

大多数魔术方法都是在类型而不是实例上查找的。您既不能在实例中覆盖它们,也不能用__getattr__捕获它们。为了挂钩双功能,你必须实现它。

示例:

obj < 1

不会打电话

obj.__lt__(1)

而是呼叫通过该类型。它几乎等于(除了它在元类上跳过getattr)。

type(obj).__lt__(obj, 1)

查找记录为http://docs.python.org/3/reference/datamodel.html#special-method-lookup