在Python中继承int
我想在Python中对内置的 int
类型进行子类化(我用的是2.5版本),但是在初始化的时候遇到了一些麻烦。
这里有一些示例代码,应该比较简单明了。
class TestClass(int):
def __init__(self):
int.__init__(self, 5)
但是,当我尝试使用这个代码时,我得到了:
>>> a = TestClass()
>>> a
0
而我本来期待的结果是 5
。
我到底哪里做错了呢?到目前为止,谷歌没有给我太多帮助,但我也不太确定我应该搜索什么。
2 个回答
虽然现在的回答是正确的,但可能不够全面。
例如:
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'>
更新:
添加了repr和str的示例,这样新类可以正确打印自己。虽然原作者使用的是Python 2,但我改成了Python 3的语法,以保持相关性。
更新 04/22:
我发现自己在最近的两个项目中也想做类似的事情。一个是我想要一个Unsigned()类型(也就是说,x-y,当x是0而y是正数时,结果仍然是0)。
我还想要一个类似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)
一个创建新Unsigned类型的示例用法。
# 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
int
是不可变的,这意味着一旦你创建了一个整数,就不能再修改它。如果你想要改变它,可以使用 __new__
这个方法。
class TestClass(int):
def __new__(cls, *args, **kwargs):
return super(TestClass, cls).__new__(cls, 5)
print TestClass()