Python 中 int 的子类化

发布于 2024-09-09 05:51:31 字数 376 浏览 2 评论 0原文

我对 Python 中内置 int 类型的子类化感兴趣(我使用的是 v.2.5),但在初始化工作时遇到一些问题。

这是一些示例代码,应该相当明显。

class TestClass(int):
    def __init__(self):
        int.__init__(self, 5)

但是,当我尝试使用它时,我得到:

>>> a = TestClass()
>>> a
0

我期望结果为 5

我做错了什么?到目前为止,谷歌并没有多大帮助,但我不太确定我应该搜索什么

I'm interested in subclassing the built-in int type in Python (I'm using v. 2.5), but having some trouble getting the initialization working.

Here's some example code, which should be fairly obvious.

class TestClass(int):
    def __init__(self):
        int.__init__(self, 5)

However, when I try to use this I get:

>>> a = TestClass()
>>> a
0

where I'd expect the result to be 5.

What am I doing wrong? Google, so far, hasn't been very helpful, but I'm not really sure what I should be searching for

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

伊面 2024-09-16 05:51:31

int 是不可变的,因此创建后无法修改它,请使用 __new__ 代替

class TestClass(int):
    def __new__(cls, *args, **kwargs):
        return  super(TestClass, cls).__new__(cls, 5)

print TestClass()

int is immutable so you can't modify it after it is created, use __new__ instead

class TestClass(int):
    def __new__(cls, *args, **kwargs):
        return  super(TestClass, cls).__new__(cls, 5)

print TestClass()
染柒℉ 2024-09-16 05:51:31

尽管正确,但当前答案可能并不完整。

例如

In [1]: a = TestClass()
In [2]: b = a - 5
In [3]: print(type(b))
<class 'int'>

将 b 显示为整数,您可能希望它是一个 TestClass。

这是一个改进的答案,其中基类的函数被重载以返回正确的类型。

    class positive(int):
        def __new__(cls, value, *args, **kwargs):
            if value < 0:
                raise ValueError("positive types must not be less than zero")
            return  super(cls, cls).__new__(cls, value)
    
        def __add__(self, other):
            res = super(positive, self).__add__(other)
            return self.__class__(max(res, 0))
    
        def __sub__(self, other):
            res = super(positive, self).__sub__(other)
            return self.__class__(max(res, 0))
    
        def __mul__(self, other):
            res = super(positive, self).__mul__(other)
            return self.__class__(max(res, 0))
    
        def __div__(self, other):
            res = super(positive, self).__div__(other)
            return self.__class__(max(res, 0))

        def __str__(self):
            return "%d" % int(self)

        def __repr__(self):
            return "positive(%d)" % int(self)

现在进行相同类型的测试


In [1]: a = positive(10)
In [2]: b = a - 9
In [3]: print(type(b))
<class '__main__.positive'>

更新:
添加了 reprstr 示例,以便新类正确打印自身。即使 OP 使用 Python 2,也更改为 Python 3 语法以保持相关性。

04/22 更新:
我发现自己想在最近的两个项目中做类似的事情。我想要一个 Unsigned() 类型(即 xy,其中 x 为 0,y 为正值仍然为零)
我还想要一个类似 set() 的类型,能够以某种方式更新和查询。
上述方法有效,但重复且乏味。如果有一个使用元类的通用解决方案怎么办?

我找不到,所以我写了一篇。这仅适用于最近的Python(我猜是3.8+,在3.10上进行了测试)

首先,元类

class ModifiedType(type):
    """
    ModifedType takes an exising type and wraps all its members
    in a new class, such that methods return objects of that new class.
    The new class can leave or change the behaviour of each
    method and add further customisation as required
    """

    # We don't usually need to wrap these
    _dont_wrap = {
    "__str__", "__repr__", "__hash__", "__getattribute__", "__init_subclass__", "__subclasshook__",
    "__reduce_ex__", "__getnewargs__", "__format__", "__sizeof__", "__doc__", "__class__"}

    @classmethod
    def __prepare__(typ, name, bases, base_type, do_wrap=None, verbose=False):
        return super().__prepare__(name, bases, base_type, do_wrap=do_wrap, verbose=verbose)

    def __new__(typ, name, bases, attrs, base_type, do_wrap=None, verbose=False):
        bases += (base_type,)

        #  Provide a call to the base class __new__
        attrs["__new__"] = typ.__class_new__

        cls = type.__new__(typ, name, bases, attrs)

        if "dont_wrap" not in attrs:
            attrs["dont_wrap"] = {}
        attrs["dont_wrap"].update(typ._dont_wrap)

        if do_wrap is not None:
            attrs["dont_wrap"] -= set(do_wrap)

        base_members = set(dir(base_type))
        typ.wrapped = base_members - set(attrs) - attrs["dont_wrap"]

        for member in typ.wrapped:
            obj = object.__getattribute__(base_type, member)
            if callable(obj):
                if verbose:
                    print(f"Wrapping {obj.__name__} with {cls.wrapper.__name__}")
                wrapped = cls.wrapper(obj)
                setattr(cls, member, wrapped)
        return cls

    def __class_new__(typ, *args, **kw):
        "Save boilerplate in our implementation"
        return typ.base_type.__new__(typ, *args, **kw)

创建新无符号类型的示例用法

# Create the new Unsigned type and describe its behaviour
class Unsigned(metaclass=ModifiedType, base_type=int):
    """
    The Unsigned type behaves like int, with all it's methods present but updated for unsigned behaviour
    """
    # Here we list base class members that we won't wrap in our derived class as the
    # original implementation is still useful. Other common methods are also excluded in the metaclass
    # Note you can alter the metaclass exclusion list using 'do_wrap' in the metaclass parameters
    dont_wrap = {"bit_length", "to_bytes", "__neg__", "__int__", "__bool__"}
    import functools

    def __init__(self, value=0, *args, **kw):
        """
        Init ensures the supplied initial data is correct and passes the rest of the
        implementation onto the base class
        """
        if value < 0:
            raise ValueError("Unsigned numbers can't be negative")

    @classmethod
    def wrapper(cls, func):
        """
        The wrapper handles the behaviour of the derived type
        This can be generic or specific to a particular method
        Unsigned behavior is:
            If a function or operation would return an int of less than zero it is returned as zero
        """
        @cls.functools.wraps(func)
        def wrapper(*args, **kw):
            ret = func(*args, **kw)
            ret = cls(max(0, ret))
            return ret
        return wrapper

示例的一些测试

In [1]: from unsigned import Unsigned
In [2]: a = Unsigned(10)
   ...: print(f"a={type(a).__name__}({a})")
a=Unsigned(10)

In [3]: try:
   ...:     b = Unsigned(-10)
   ...: except ValueError as er:
   ...:     print(" !! Exception\n", er, "(This is expected)")
   ...:     b = -10  # Ok, let's let that happen but use an int type instead
   ...:     print(f" let b={b} anyway")
   ...:     
 !! Exception
 Unsigned numbers can't be negative (This is expected)
 let b=-10 anyway
In [4]: c = a - b
   ...: print(f"c={type(c).__name__}({c})")
c=Unsigned(20)

In [5]: d = a + 10
   ...: print(f"d={type(d).__name__}({d})")
d=Unsigned(20)

In [6]: e = -Unsigned(10)
   ...: print(f"e={type(e).__name__}({e})")
e=int(-10)

In [7]: f = 10 - a
   ...: print(f"f={type(f).__name__}({f})")
f=Unsigned(0)

以及@Kazz更新 :
来回答你的问题。虽然直接 int(u) * 0.2 会更简单,但

这里有一个小的更新包装器来处理异常情况,例如 (Unsigned * float),它作为如何修改行为以匹配异常情况的示例。所需的子类行为,而不必单独重载每个可能的参数类型组合。

    # NOTE: also add '__float__' to the list of non-wrapped methods

    @classmethod
    def wrapper(cls, func):
        fn_name = func.__name__
        @cls.functools.wraps(func)
        def wrapper(*args, **kw):
            compatible_types = [issubclass(type(a), cls.base_type) for a in args]

            if not all(compatible_types):
                # Try converting
                type_list = set(type(a) for a in args) - set((cls.base_type, cls))
                if type_list != set((float,)):
                    raise ValueError(f"I can't handle types {type_list}")
                args = (float(x) for x in args)
                ret = getattr(float, fn_name)(*args, **kw)
            else:
                ret = func(*args, **kw)
                ret = cls(max(0, ret))
            return ret
        return wrapper

Though correct the current answers are potentially not complete.

e.g.

In [1]: a = TestClass()
In [2]: b = a - 5
In [3]: print(type(b))
<class 'int'>

Shows b as an integer, where you might want it to be a TestClass.

Here is an improved answer, where the functions of the base class are overloaded to return the correct type.

    class positive(int):
        def __new__(cls, value, *args, **kwargs):
            if value < 0:
                raise ValueError("positive types must not be less than zero")
            return  super(cls, cls).__new__(cls, value)
    
        def __add__(self, other):
            res = super(positive, self).__add__(other)
            return self.__class__(max(res, 0))
    
        def __sub__(self, other):
            res = super(positive, self).__sub__(other)
            return self.__class__(max(res, 0))
    
        def __mul__(self, other):
            res = super(positive, self).__mul__(other)
            return self.__class__(max(res, 0))
    
        def __div__(self, other):
            res = super(positive, self).__div__(other)
            return self.__class__(max(res, 0))

        def __str__(self):
            return "%d" % int(self)

        def __repr__(self):
            return "positive(%d)" % int(self)

Now the same sort of test


In [1]: a = positive(10)
In [2]: b = a - 9
In [3]: print(type(b))
<class '__main__.positive'>

UPDATE:

Added repr and str examples so that the new class prints itself properly. Also changed to Python 3 syntax, even though OP used Python 2, to maintain relevancy.

UPDATE 04/22:

I found myself wanting to do something similar on two recent projects. One where I wanted an Unsigned() type (i.e. x-y, where x is 0 and y is positive is still zero)

I also wanted a set() like type the was able to be updated and queried in a certain way.

The above method works but it's repetitive and tedious. What if there was a generic solution using metaclasses?

I could not find one so I wrote one. This will only work in recent Python (I would guess 3.8+, tested on 3.10)

First, the MetaClass

class ModifiedType(type):
    """
    ModifedType takes an exising type and wraps all its members
    in a new class, such that methods return objects of that new class.
    The new class can leave or change the behaviour of each
    method and add further customisation as required
    """

    # We don't usually need to wrap these
    _dont_wrap = {
    "__str__", "__repr__", "__hash__", "__getattribute__", "__init_subclass__", "__subclasshook__",
    "__reduce_ex__", "__getnewargs__", "__format__", "__sizeof__", "__doc__", "__class__"}

    @classmethod
    def __prepare__(typ, name, bases, base_type, do_wrap=None, verbose=False):
        return super().__prepare__(name, bases, base_type, do_wrap=do_wrap, verbose=verbose)

    def __new__(typ, name, bases, attrs, base_type, do_wrap=None, verbose=False):
        bases += (base_type,)

        #  Provide a call to the base class __new__
        attrs["__new__"] = typ.__class_new__

        cls = type.__new__(typ, name, bases, attrs)

        if "dont_wrap" not in attrs:
            attrs["dont_wrap"] = {}
        attrs["dont_wrap"].update(typ._dont_wrap)

        if do_wrap is not None:
            attrs["dont_wrap"] -= set(do_wrap)

        base_members = set(dir(base_type))
        typ.wrapped = base_members - set(attrs) - attrs["dont_wrap"]

        for member in typ.wrapped:
            obj = object.__getattribute__(base_type, member)
            if callable(obj):
                if verbose:
                    print(f"Wrapping {obj.__name__} with {cls.wrapper.__name__}")
                wrapped = cls.wrapper(obj)
                setattr(cls, member, wrapped)
        return cls

    def __class_new__(typ, *args, **kw):
        "Save boilerplate in our implementation"
        return typ.base_type.__new__(typ, *args, **kw)

An example usage to create a new Unsigned type

# Create the new Unsigned type and describe its behaviour
class Unsigned(metaclass=ModifiedType, base_type=int):
    """
    The Unsigned type behaves like int, with all it's methods present but updated for unsigned behaviour
    """
    # Here we list base class members that we won't wrap in our derived class as the
    # original implementation is still useful. Other common methods are also excluded in the metaclass
    # Note you can alter the metaclass exclusion list using 'do_wrap' in the metaclass parameters
    dont_wrap = {"bit_length", "to_bytes", "__neg__", "__int__", "__bool__"}
    import functools

    def __init__(self, value=0, *args, **kw):
        """
        Init ensures the supplied initial data is correct and passes the rest of the
        implementation onto the base class
        """
        if value < 0:
            raise ValueError("Unsigned numbers can't be negative")

    @classmethod
    def wrapper(cls, func):
        """
        The wrapper handles the behaviour of the derived type
        This can be generic or specific to a particular method
        Unsigned behavior is:
            If a function or operation would return an int of less than zero it is returned as zero
        """
        @cls.functools.wraps(func)
        def wrapper(*args, **kw):
            ret = func(*args, **kw)
            ret = cls(max(0, ret))
            return ret
        return wrapper

And some tests for the example

In [1]: from unsigned import Unsigned
In [2]: a = Unsigned(10)
   ...: print(f"a={type(a).__name__}({a})")
a=Unsigned(10)

In [3]: try:
   ...:     b = Unsigned(-10)
   ...: except ValueError as er:
   ...:     print(" !! Exception\n", er, "(This is expected)")
   ...:     b = -10  # Ok, let's let that happen but use an int type instead
   ...:     print(f" let b={b} anyway")
   ...:     
 !! Exception
 Unsigned numbers can't be negative (This is expected)
 let b=-10 anyway
In [4]: c = a - b
   ...: print(f"c={type(c).__name__}({c})")
c=Unsigned(20)

In [5]: d = a + 10
   ...: print(f"d={type(d).__name__}({d})")
d=Unsigned(20)

In [6]: e = -Unsigned(10)
   ...: print(f"e={type(e).__name__}({e})")
e=int(-10)

In [7]: f = 10 - a
   ...: print(f"f={type(f).__name__}({f})")
f=Unsigned(0)

UPDATE for @Kazz:

To answer your question. Though it would be simpler to just int(u) * 0.2

Here is a small updated wrapper to handle the exception case e.g. (Unsigned * float) that serves as an example of how to modify behavior to match the desired subclass behaviour without having to individually overload each possible combination of argument types.

    # NOTE: also add '__float__' to the list of non-wrapped methods

    @classmethod
    def wrapper(cls, func):
        fn_name = func.__name__
        @cls.functools.wraps(func)
        def wrapper(*args, **kw):
            compatible_types = [issubclass(type(a), cls.base_type) for a in args]

            if not all(compatible_types):
                # Try converting
                type_list = set(type(a) for a in args) - set((cls.base_type, cls))
                if type_list != set((float,)):
                    raise ValueError(f"I can't handle types {type_list}")
                args = (float(x) for x in args)
                ret = getattr(float, fn_name)(*args, **kw)
            else:
                ret = func(*args, **kw)
                ret = cls(max(0, ret))
            return ret
        return wrapper
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文