自动计数器作为整数的子类?

Automatic counter as a subclass of integer?

是否可以创建一个 class 整数,其中某个 class(例如 AutomaticCounter)的实例每次都会增加某个数字(例如 1)叫什么?

>>> n = AutomaticCounter(start=0)
>>> print(n)
1
>>> print(n)
2

这是我目前尝试过的方法:

class AutomaticCounter(int):
    def __init__(self):
        self = 0
    def __str__(self):
        self = self + 1
        return self

如果你真的,真的真的需要破坏一个immutablebuilt-in 类型,那么你可以创建一种指向它的“指针”:

class AutomaticCounter(int):
    def __new__(cls, *args, **kwargs):
        # create a new instance of int()
        self = super().__new__(cls, *args, **kwargs)
        # create a member "ptr" and storing a ref to the instance
        self.ptr = self
        # return the normal instance
        return self

    def __str__(self):
        # first, create a copy via int()
        # which "decays" from your subclass to an ordinary int()
        # then stringify it to obtain the normal __str__() value
        value = str(int(self.ptr))

        # afterwards, store a new instance of your subclass
        # that is incremented by 1
        self.ptr = AutomaticCounter(self.ptr + 1)
        return value

n = AutomaticCounter(0)
print(n)  # 0
print(n)  # 1
print(n)  # 2

# to increment first and print later, use this __str__() instead:
    def __str__(self):
        self.ptr = AutomaticCounter(self.ptr + 1)
        return str(int(self.ptr))

然而,这并不能使类型本身不可变。如果你在 __str__() 的开头做 print(f"{self=}") 你会看到实例没有改变,所以你的对象实际上有 2x int() (+一些垃圾)的大小并且你可以访问通过 self.ptr.

的真实实例

它不能单独与 self 一起使用,因为 self 只是一个只读引用(通过 __new__() 创建)作为第一个参数传递给实例的方法,所以有些东西像这样:

def func(instance, ...):
    instance = <something else>

你做作业会,正如 Daniel, simply assign a new value to the local variable named instance (self is just a quasi-standard name for the reference 所提到的那样,这并没有真正改变实例。因此,下一个看起来相似的解决方案将是一个指针,当您想按照您描述的方式操作它时,我将它“隐藏”到一个名为 ptr.

的自定义成员中

正如 user2357112 所指出的,实例不可变会导致不同步,因此如果您选择 self.ptr hack,则需要更新魔术方法(__*__()),例如,这是在更新 __add__()。注意 int() 调用,它将其转换为 int() 以防止递归。

class AutomaticCounter(int):
    def __new__(cls, *args, **kwargs):
        self = super().__new__(cls, *args, **kwargs)
        self.ptr = self
        return self

    def __str__(self):
        value = int(self.ptr)
        self.ptr = AutomaticCounter(int(self.ptr) + 1)
        return str(value)

    def __add__(self, other):
        value = other
        if hasattr(other, "ptr"):
            value = int(other.ptr)
        self.ptr = AutomaticCounter(int(self.ptr) + value)
        return int(self.ptr)

    def __rmul__(self, other):
        # [1, 2, 3] * your_object
        return other * int(self.ptr)

n = AutomaticCounter(0)
print(n)    # 0
print(n)    # 1
print(n)    # 2
print(n+n)  # 6

但是,任何尝试提取原始值或尝试使用 C API 访问原始值的操作很可能会失败,即反向操作,例如对于那些你不能可靠地编辑魔法方法的人来说,不可变的内置函数应该是这种情况,所以它在所有模块和所有范围内都得到了纠正。

示例:

# will work fine because it's your class
a <operator> b -> a.__operator__(b)
vs
# will break everything because it's using the raw value, not self.ptr hack
b <operator> a -> b.__operator__(a)

出于某种原因 list.__mul__() 除外。当我在 CPython 中找到代码行时,我会在此处添加它。


或者,更明智的解决方案是创建一个自定义的可变对象,在其中创建一个成员并对其进行操作。然后 return 它,字符串化,在 __str__:

class AutomaticCounter(int):
    def __init__(self, start=0):
        self.item = start
    def __str__(self):
        self.item += 1
        return str(self.item)

这里有两个问题。首先,self 实际上不是对象,而是 对象的变量引用。当您重新分配 self 时,您并没有更改对象,而只是导致仅具有局部范围的 self 变量现在引用其他某个对象。原始对象保持不变。

其次,除非您真的知道自己在做什么(而我不知道),否则在我看来,不建议对不可变的内置函数进行子类化。您可以做的是让对象具有整数属性,然后定义 __getattr__ 方法以将任何属性调用传递给整数。

class AutomaticCounter:
    def __init__(self, start=0):
        self.item = start

    def __str__(self):
        self.item += 1
        return str(self.item)

    def __getattr__(self, attr):
        return getattr(self.item, attr)