尽管当前的答案是正确的,但仍有可能不完整。
例如:
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示例,以使新类能够正确打印自身。此外,即使OP使用Python 2,也更改为Python 3语法以保持相关性。
更新于04/22:
最近的两个项目中,我发现自己想要做类似的事情。其中一个是我想要一个无符号类型(即x-y,其中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
"""
_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,)
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)
创建新的无符号类型示例用法
class Unsigned(metaclass=ModifiedType, base_type=int):
"""
The Unsigned type behaves like int, with all it's methods present but updated for unsigned behaviour
"""
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(" !! 异常\n", er, "(这是预期的)")
...: b = -10 # 好吧,让我们继续,但使用int类型
...: print(f"仍然让b={b}")
...:
!! 异常
Unsigned numbers can't be negative (这是预期的)
仍然让b=-10
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),它作为一个例子,展示了如何修改行为以匹配所需的子类行为,而无需单独重载每个可能的参数类型组合。
@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):
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