简体   繁体   English

Python3中自动定义的数字运算符

[英]Automatically defined numeric operators in Python3

I'm trying to create a custom class that behaves like an integral numeric type. 我正在尝试创建一个行为类似于整数数字类型的自定义类。 The straightforward way to do that is to consult Python3 documentation on the matter, and then implement all the magic functions: both arithmetic __add__, __radd__, __sub__, ... and comparison __le__, __lt__, ... operators. 直接的方法是参考Python3文档 ,然后实现所有的魔术函数:算术__add__, __radd__, __sub__, ... __le__, __lt__, ...

However, implementing them all by hand is tedious. 然而,手动实施它们是乏味的。 I believe that in many cases, a good enough solution would be to implement them automatically based on the __int__ magic function: we simply do the arithmetic with the object converted to int. 我相信在很多情况下,一个足够好的解决方案是基于__int__魔术函数自动实现它们:我们只需将对象转换为int进行算术运算。 Is this part of any commonly used library? 这是任何常用库的一部分吗? What I'm looking for is something similar to @functools.total_ordering , which automatically derives all comparison operators from only one of the operators. 我正在寻找的是类似于@ functools.total_ordering的东西,它自动从一个运算符派生所有比较运算符。

If there is no such thing, then why? 如果没有这样的事情,为什么呢? Would it be a bad idea to have such automatic conversion, or is it simply an issue one does not encounter too often? 进行这样的自动转换是不是一个坏主意,还是仅仅是一个不经常遇到的问题?

Edit: In case that the details of the custom class are relevant, I provide some of them here. 编辑:如果自定义类的详细信息是相关的,我在这里提供一些。

What I'm constructing is a counter whose value can change not only through arithmetic operations, but also other means. 我正在构建的是一个计数器,它的值不仅可以通过算术运算来改变,还可以通过其他方式改变。 These can be very general: for example, we can tell the counter the following: "If anybody asks you what value you represent, return twice the normal value." 这些可能非常通用:例如,我们可以告诉计数器如下:“如果有人问你代表什么值,则返回正常值的两倍。”

To illustrate, suppose you're playing a board game, for example, similar to Civilization. 举例来说,假设您正在玩棋盘游戏,例如,类似于文明。 The game makes use of many parts, one of them is the counter that counts the military strength your civilization has. 游戏使用了许多部分,其中一个是计算你的文明所具有的军事力量的计数器。 However, there may be an in-game effect which causes each point of strength to count twice. 然而,可能存在游戏中的效果,其导致每个力量点数两次。

Since the class is required to be mutable, I believe subclassing int is not an option (as int is immutable). 由于该类需要是可变的,我认为子类化int不是一个选项(因为int是不可变的)。

You can write your own mixin class or decorator that does this, similarly to the way collections.abc.Sequence implements a bunch of Sequence methods on top of a handful that you write manually, or functools.total_ordering implements a bunch of comparison methods for you. 您可以编写自己的mixin类或decorator来执行此操作,类似于collections.abc.Sequence在您手动编写的少数几个上实现一组Sequence方法的方式,或者functools.total_ordering为您实现了一堆比较方法。 (If you're wondering how to decide which one to use: if the code has to examine your class before modifying it, as with total_ordering , your mixin would need a custom metaclass, so a decorator is easier. Otherwise, a mixin is usually simpler.) (如果你想知道如何决定使用哪一个:如果代码必须在修改它之前检查你的类,就像使用total_ordering ,你的mixin需要一个自定义元类,所以装饰器更容易。否则,通常是一个mixin简单的。)

There are dozens of implementations of this idea floating around PyPI and the ActiveState recipes and random blog posts and GitHub repos. 围绕PyPI和ActiveState配方以及随机博客帖子和GitHub repos,有很多这种想法的实现。 But the problem isn't quite generalizable to turn a solution into a widely-used library. 但是,将解决方案转变为广泛使用的库并不是很普遍。

As for the specific case you want—an object that acts like a mutable int , and implements it on the basis of its __int__ method—I think that specific case just doesn't come up often enough that anyone has sat down and written it. 至于你想要的特定情况 - 一个像mutable int一样的对象,并在它的__int__方法的基础上实现它 - 我认为特定情况并不经常出现,任何人都坐下来写它。


Anyway, getting all of this right—including things like 3.0 + x and x + 3.0 —is a bit of a pain. 无论如何,让所有这一切正确 - 包括3.0 + xx + 3.0 - 有点痛苦。

Implementing the arithmetic operations in the numbers documentation explains exactly what you want to do to get it right. numbers文档中实现算术运算可以准确地解释您想要做什么以使其正确。

It also contains an explanation of the source code used by the fractions library to reduce the boilerplate and repetition of defining all of these methods on fraction.Fraction , which is very handy. 它还包含了fractions库使用的源代码的解释,以减少样板和重复在fraction.Fraction上定义所有这些方法,这非常方便。

The only problem with all of that is that it's written for immutable numeric types, not mutable ones. 所有这一切的唯一问题是它是为不可变数值类型编写的,而不是可变数字类型。 So, if you want to use it, you'll need to modify it, with an operator_fallback function that builds and returns forward, reverse, inplace instead of just forward, reverse . 因此,如果你想使用它,你需要修改它,使用operator_fallback函数构建并返回forward, reverse, inplace而不是forward, reverse


However, your case is simpler than Fraction , so you can simplify the operator-function-creating methods, like this: 但是,您的情况比Fraction简单,因此您可以简化运算符函数创建方法,如下所示:

def _operator_fallbacks(op, sym):
    def forward(a, b):
        result = op(int(a), b)
        if isinstance(result, int): result = type(a)(result)
        return result
    forward.__name__ = '__' + op.__name__ + '__'
    forward.__doc__ = f"a {sym} b"

    def reverse(b, a):
        result = op(a, int(b))
        if isinstance(result, int): result = type(b)(result)
        return result
    reverse.__name__ = '__r' + op.__name__ + '__'
    reverse.__doc__ = f"a {sym} b"

    def inplace(a, b):
        # If you want to work via __int__ rather than by digging
        # into the internals, you can't delegate += to +=, you
        # have to implement it via some kind of set method.
        a.set(op(int(a), b))
        return a
    inplace.__name__ = '__i' + op.__name__ + '__'
    inplace.__doc__ = f'a {sym}= b'

    return forward, reverse, inplace

__add__, __radd__, __iadd__ = _operator_fallbacks(operator.add, '+')
__sub__, __rsub__, __isub__ = _operator_fallbacks(operator.sub, '-')

You might want to consider isinstance(result, numbers.Integral) . 您可能想要考虑isinstance(result, numbers.Integral) You might also want to make your class a subtype of numbers.Integral . 您可能还想让您的类成为数字的子类型numbers.Integral If you do both, be careful about how you construct them, so you don't end up with an Int(Int(Int(5))) instead of an Int(5) . 如果同时执行这两个操作,请注意如何构造它们,因此不要使用Int(Int(Int(5)))而不是Int(5)

If you wanted to generalize this into a mixin that can be used for mutable int , float , complex , Decimal , Fraction , etc. types, you could just add a get method that all of your types have to implement, instead of relying on __int__ . 如果你想将它概括为一个可用于可变intfloatcomplexDecimalFraction等类型的mixin,你可以添加一个get所有类型都必须实现的get方法,而不是依赖于__int__ Although you'd have to think through the conversion issues ( int is easy, being on the base of the numeric tower). 虽然你必须考虑转换问题( int很容易,在数字塔的基础上)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM