'Is this the best way for subclass in python [closed]

I have I timer where I want to increase and decrease the timer based on addition and subtraction operations.

So, I have came to this solution to a subclass of int for non-negative numbers. Is this a correct way to do the subclass work? For me looks awkward the 'return Positive(self.value)'.

class Positivo(int):
    def __new__(self, value: int =0):
        self.value = value
        return int.__new__(Positivo, self.value)

    def __sub__(self, op_value):
        if self.value < op_value:
            self.value = 0
        else:
            self.value -= op_value
        return Positivo(self.value)

    def __add__(self, op_value):
        self.value += op_value
        return Positivo(self.value)

EDIT1: The alternative with 'self':

class Positivo(int):
    def __init__(self, value=0):
        self = int.__new__(Positivo, value)

    def __sub__(self, op_value):
        return Positivo(super().__sub__(op_value) if op_value <= self else 0)

    def __add__(self, op_value):
        return Positivo(super().__add__(op_value))


Solution 1:[1]

disclaimer: the answer gives general hints of what is going wrong, and how to improve your code, and learn with it. A modified int class that will break a lot of mathematical basic properties of integers should not be used in production code. I am assuming this is a toy.

If you need a "non-negative" class of numbers for a real situation, at least make it raise ValueError on any operation that would yield an invalid value, instead of silently changing the operation value to 0.


Yes, if you subclass one of the built-in types and want the operators to work correctly, you have to explicitly return an instance of your subclass on each of the special methods.

So, what you are doing is the way to go. If you want to "look smart", it would be possible to run a for-loop on the body of the class itself, that would automatically apply a wrapper on each of the relevant methods of "int" - but that, despite using less lines of code and avoiding copy/paste of methods, may not be the best for readability and maintainability, depending on your project.

That said, there are other important points in your code, already pointed in the comments - one of the most important is that the __new__ method must be correct, and annotating value in the instance is redundant. Besides, there is only a self after you call int.__new__, before that there is no instance at all where you can anotate anything. The first parameter to __new__ is the class itself, regardless of how you name it. Anyway, if you are not modfying the value when the instance is created, there is no need to modify __new__ at all.

The comparisons using self.value in the methods can be made against self itself: it is an int, after all: if self < op_value, instead of self.value.

And also, you might want to add a __slots__=() to your int subclass to keep it lean: otherwise you have a whole new dict instance for each of your special ints.

Another thing: int is an immutable object type, and unless you are playing around to create a "mutable int subclass", you should never modify its state once it is created. I am again talking of assigning to "self.value". AND, of course,even if it WAS mutable, __sub__, __add__ never mutate the object they are operating with: they always will return new instances. It is the "i" prefixed operators that should operate "inplace" and modify self if it is mutable (__iadd__, __isub__, ...).

Yet another note concerning the Python language, and unrelated to subclassing int, are type annotations. You have to keep in mind that they are an optional thing, and most of the times, if used naively, they will restrict your code more than it needs to be restricted. Your line def __new__(self, value: int =0): restricts the value parameter to create an instance of the class to an int, but Python's int accepts a lot more than that. And also, there is an optional base parameter you are simply letting out.

Now, for a somewhat "serious" version of this, sice all operators should create a new instance, you can check the values in __new__ alone, and just wrap the operator methods to return a new instance of it:

from functools import wraps

class Positivo(int):
    __slots__ = ()
    def __new__(cls, value, /, base=10):
        if isinstance(value, str):
            self = super().__new__(cls, value, base)
        else:
            self = super().__new__(cls, value)
        if self < 0:
            self = super().__new__(cls, 0)
            # It would be safer:
            # raise ValueError(f"Positivo out of domain value for Positivo: {self}")
        return self

    def _decorate_op(method):
        def wrapper(self, *args):
            result = getattr(super(), method)(*args)
            if isinstance(result, int):
                return Positivo(result)
            return result
        wrapper.__name__ = method
        return wrapper

    num_methods = "__add__ __sub__ __truediv__ __mul__ __pow__ __neg__ __pos__ __floor__ __floordiv__ __ceil__ __trunc__ __abs__"
 
    # dynamically creates the mathematical methods that would result in an "int":
    for method in num_methods.split():
        locals()[method] = _decorate_op(method)

    # optional: remove tooling used when creating the class body
    #so that it does not show up in the final class:
    del num_methods, _decorate_op

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1