如何创建在继承操作下关闭的类型?
How to create a type that is closed under inherited operations?
在数学意义上,如果操作总是 returns 集合本身的成员,则集合(或类型)在操作下 closed。
这个问题是关于制作一个class,它在从它的超classes继承的所有操作下关闭。
考虑以下 class。
class MyInt(int):
pass
由于__add__
没有被覆盖,所以在添加下不关闭
x = MyInt(6)
print(type(x + x)) # <class 'int'>
关闭类型的一种非常繁琐的方法是手动将 returns 和 int
的每个操作的结果强制转换为 MyInt
.
在这里,我使用 metaclass 使该过程自动化,但这似乎是一个过于复杂的解决方案。
import functools
class ClosedMeta(type):
_register = {}
def __new__(cls, name, bases, namespace):
# A unique id for the class
uid = max(cls._register) + 1 if cls._register else 0
def tail_cast(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
out = f(*args, **kwargs)
if type(out) in bases:
# Since the class does not exist yet, we will recover it later
return cls._register[uid](out)
return out
return wrapper
for base in reversed(bases):
for name, attr in base.__dict__.items():
if callable(attr) and name not in namespace:
namespace[name] = tail_cast(attr)
subcls = super().__new__(cls, name, bases, namespace)
cls._register[uid] = subcls
return subcls
class ClosedInt(int, metaclass=ClosedMeta):
pass
这在某些极端情况下失败,例如 property
和通过 __getattribute__
恢复的方法。当基不只由基类型组成时,它也会失败。
例如,这失败了:
class MyInt(int):
pass
class ClosedInt(MyInt, metaclass=ClosedMeta):
pass
ClosedInt(1) + ClosedInt(1) # returns the int 2
我试图解决这个问题,但它似乎越陷越深。
这似乎是一个可能有一些简单的 pythonic 解决方案的问题。实现这种封闭类型的其他更简洁的方法是什么?
这不能做,数据模型禁止这样做。我可以证明给你看:
>>> class MyClass(ClosedInt, metaclass=type):
... def __add__(self, other):
... return 'potato'
...
>>> MyClass(1) + ClosedInt(2)
'potato'
加法首先由左侧对象处理,如果左侧类型处理它(即不 return NotImplemented
单例),则 无 about other
在此操作中被考虑。如果右手类型是左手类型的子类,您可以使用反射方法控制结果__radd__
- 当然在一般情况下这是不可能的。
我认为使用元class 的想法是可行的。诀窍是在获取值时动态转换值,而不是预先转换值。这基本上就是 python 的全部意义所在:在真正得到它之前,不知道你会得到什么或那里有什么。
为此,您必须在 class 上重新定义 __getattribute__
和 __getattr__
,但有一些注意事项:
- 运算符不通过正常的属性访问方法。即使在您的 metaclass 上定义正确的
__getattribute__
和 __getattr__
也无济于事。必须为每个 class. 显式覆盖 Dunder
- 由
__getattribute__
和 __getattr__
编写的 return 方法需要将它们的 return 值转换为目标类型。这同样适用于称为运算符的 dunders。
- #2 中应排除一些方法以确保机器正常运行。
相同的基本铸造包装器可用于所有属性和方法 return 值。当调用 __getattribute__
或 __getattr__
.
的结果时,它只需要恰好递归一次
下面显示的解决方案正是这样做的。它明确地包装了所有未列为例外的 dunder。如果所有其他属性是函数,则立即转换或包装。它允许通过检查 __mro__
中的所有内容来自定义任何方法,包括 class 本身。该解决方案将与 class 和静态方法一起正常工作,因为它存储了转换例程并且不依赖于 type(self)
(正如我之前的一些尝试所做的那样)。它将正确排除 exceptions
中列出的任何属性,而不仅仅是双下划线方法。
import functools
def isdunder(x):
return isinstance(x, str) and x.startswith('__') and x.endswith('__')
class DunderSet:
def __contains__(self, x):
return isdunder(x)
def wrap_method(method, xtype, cast):
@functools.wraps(method)
def retval(*args, **kwargs):
result = method(*args, **kwargs)
return cast(result) if type(result) == xtype else result
return retval
def wrap_getter(method, xtype, cast, exceptions):
@functools.wraps(method)
def retval(self, name, *args, **kwargs):
result = method(self, name, *args, **kwargs)
return result if name in exceptions else check_type(result, xtype, cast)
return retval
def check_type(value, xtype, cast):
if type(value) == xtype:
return cast(value)
if callable(value):
return wrap_method(value, xtype, cast)
return value
class ClosedMeta(type):
def __new__(meta, name, bases, dct, **kwargs):
if 'exceptions' in kwargs:
exceptions = set([
'__new__', '__init__', '__del__',
'__init_subclass__', '__instancecheck__', '__subclasscheck__',
*map(str, kwargs.pop('exceptions'))
])
else:
exceptions = DunderSet()
target = kwargs.pop('target', bases[0] if bases else object)
cls = super().__new__(meta, name, bases, dct, **kwargs)
for base in cls.__mro__:
for name, item in base.__dict__.items():
if isdunder(name) and (base is cls or name not in dct) and callable(item):
if name in ('__getattribute__', '__getattr__'):
setattr(cls, name, wrap_getter(item, target, cls, exceptions))
elif name not in exceptions:
setattr(cls, name, wrap_method(item, target, cls))
return cls
def __init__(cls, *args, **kwargs):
return super().__init__(*args)
class MyInt(int):
def __contains__(self, x):
return x == self
def my_op(self, other):
return int(self * self // other)
class ClosedInt(MyInt, metaclass=ClosedMeta, target=int,
exceptions=['__index__', '__int__', '__trunc__', '__hash__']):
pass
class MyClass(ClosedInt, metaclass=type):
def __add__(self, other):
return 1
print(type(MyInt(1) + MyInt(2)))
print(0 in MyInt(0), 1 in MyInt(0))
print(type(MyInt(4).my_op(16)))
print(type(ClosedInt(1) + ClosedInt(2)))
print(0 in ClosedInt(0), 1 in ClosedInt(0))
print(type(ClosedInt(4).my_op(16)))
print(type(MyClass(1) + ClosedInt(2)))
结果是
<class 'int'>
True False
<class 'int'>
<class '__main__.ClosedInt'>
True False
<class '__main__.ClosedInt'>
<class 'int'>
最后一个例子是对的致敬。它表明您必须想要这样做才能正常工作。
IDEOne link 因为我现在无法访问计算机:https://ideone.com/iTBFW3
附录 1:改进的默认异常
我认为通过仔细阅读文档的 special method names 部分,可以得到比所有 dunder 方法更好的默认异常集。方法可以分为两大类 classes:具有使 python 机制工作的非常具体 return 类型的方法,以及在 return 您感兴趣的实例。还有第三类,即应始终排除在外的方法,即使您忘记明确提及它们也是如此。
以下是始终排除的方法列表:
__new__
__init__
__del__
__init_subclass__
__instancecheck__
__subclasscheck__
以下是默认情况下应排除的所有内容的列表:
__repr__
__str__
__bytes__
__format__
__lt__
__le__
__eq__
__ne__
__gt__
__ge__
__hash__
__bool__
__setattr__
__delattr__
__dir__
__set__
__delete__
__set_name__
__slots__
(不是方法,但还是)
__len__
__length_hint__
__setitem__
__delitem__
__iter__
__reversed__
__contains__
__complex__
__int__
__float__
__index__
__enter__
__exit__
__await__
__aiter__
__anext__
__aenter__
__aexit__
如果我们将此列表存储到一个名为 default_exceptions
的变量中,则 class DunderSet
可以完全删除,并且提取 exceptions
的条件可以替换为:
exceptions = set([
'__new__', '__init__', '__del__',
'__init_subclass__', '__instancecheck__', '__subclasscheck__',
*map(str, kwargs.pop('exceptions', default_exceptions))
])
附录 2:改进的定位
应该可以很容易地定位多种类型。这在扩展 ClosedMeta
的其他实例时特别有用,它可能不会覆盖我们想要的所有方法。
这样做的第一步是使 target
成为 class 的容器,而不是单个 class 引用。而不是
target = kwargs.pop('target', bases[0] if bases else object)
做
target = kwargs.pop('target', bases[:1] if bases else [object])
try:
target = set(target)
except TypeError:
target = {target}
现在用blah in target
(或blah in xtype
)替换blah == target
(或包装器中的blah == xtype
)。
我仍然觉得可能有更自然的方法来完成这个,但我能够解决问题中提供的尝试。
以下是需要修复的要点。
我们必须检查 mro 中所有 类 的方法,而不仅仅是 bases;
__getattribute__
和__getattr__
必须作为特例处理;
具有__get__
的属性必须分开处理;
我们必须编写异常列表,因为 __int__
或 __eq__
等方法显然应该 return 它们的预期类型。
代码
import functools
def get_mro(bases):
# We omit 'object' as it is the base type
return type('', bases, {}).__mro__[1:-1]
class ClosedMeta(type):
_register = {}
# Some methods return type must not change
_exceptions = ('__int__', '__eq__', ...)
def __new__(cls, name, bases, namespace):
# A unique id for the class
uid = max(cls._register) + 1 if cls._register else 0
mro = get_mro(bases)
def tail_cast(f):
"""Cast the return value of f"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
out = f(*args, **kwargs)
if type(out) in mro:
# Since the class does not exist yet, we will recover it later
return cls._register[uid](out)
return out
return wrapper
def deep_tail_cast(f):
"""Cast the return value of f or the return value of f(...)"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
out = f(*args, **kwargs)
if callable(out):
return tail_cast(out)
elif type(out) in mro:
return cls._register[uid](out)
else:
return out
return wrapper
class PropertyCast:
"""Cast the return value of a property"""
def __init__(self, prop):
self.prop = prop
def __get__(self, instance, owner):
return cls._register[uid](self.prop.__get__(instance, owner))
def __set__(self, instance, value):
return self.prop.__set__(instance, value)
def __delete__(self, instance):
return self.prop.__delete__(instance)
for base in reversed(mro):
for name, attr in base.__dict__.items():
if name in ('__getattr__', '__getattribute__'):
namespace[name] = deep_tail_cast(attr)
elif callable(attr) and name not in namespace and name not in cls._exceptions:
namespace[name] = tail_cast(attr)
elif hasattr(attr, '__get__'):
namespace[name] = PropertyCast(attr)
subcls = super().__new__(cls, name, bases, namespace)
cls._register[uid] = subcls
return subcls
例子
class MyInt(int):
def __getattr__(self, _):
return 1
@property
def foo(self):
return 2
class ClosedInt(MyInt, metaclass=ClosedMeta):
pass
x = ClosedInt(2)
print(type(x * x), x * x)
print(type(x.foo), x.foo)
print(type(x.bar), x.bar)
输出
<class '__main__.ClosedIntContainer'> 4
<class '__main__.ClosedIntContainer'> 2
<class '__main__.ClosedIntContainer'> 1
这还有一些问题。举例来说,我们仍然有繁琐的任务来检查所有 dunder 方法并标记已实施规则的例外情况,但除非某处有这些列表,否则这似乎是不可避免的。
每个人都在写短代码和元 classes,而我几乎不写装饰器。 (该死的,哈哈)但我还是要分享它。
from functools import wraps
class CLOSED:
_built_ins = [
'__add__', '__sub__', '__mul__', '__floordiv__',
'__div__', '__truediv__', '__mod__', '__divmod__',
'__pow__', '__lshift__', '__rshift__','__and__',
'__or__', '__xor__',
]
@staticmethod
def register_closed(method): # Or you can use type annotations
method.registered = True # Or you can add the method names as string to closed decorator
return method # In this version you decorate the methods with this
@staticmethod
def closed_method(method, cls):
@wraps(method)
def wrapper(*a, **kw):
return cls(method(*a, **kw))
return wrapper
@classmethod
def closed_class(klass, cls):
for magic in klass._built_ins:
_method = getattr(cls, magic, False)
if _method:
setattr(cls, magic, klass.closed_method(_method, cls))
for method in dir(cls):
c1 = method not in klass._built_ins
c2 = method not in dir(object)
c3 = getattr(getattr(cls, method), 'registered', False)
if all((c1, c2, c3)):
_method = getattr(cls, method)
setattr(cls, method, klass.closed_method(_method, cls))
return cls
完成这么长的设置后,您只需像往常一样装饰 class;我太困了,无法让它与继承的 classes 一起工作,所以现在你必须装饰从封闭的 class.
继承的 class
@CLOSED.closed_class
class foo(int):
@CLOSED.register_closed # or if you can simply add this to CLOSED.closed_class
def bar(self, other): # if you are certain that every method can be casted to its own class
"""Basically just the __add__ method"""
return self + other
print(type(foo(1) + foo(1))); print(foo(1) + foo(1)) # <class '__main__.foo'> 2
print(type(foo(1).bar(2))); print(foo(1).bar(2)) # <class '__main__.foo'> 3
@CLOSED.closed_class
class baz(foo):
pass
print(type(baz(1) + baz(3))); print(baz(1) + baz(3)) # <class '__main__.baz'> 4
print(type(baz(1).bar(4))); print(baz(1).bar(4)) # <class '__main__.baz'> 5
请随意投反对票,因为我仍然不确定我是否正确理解了这个问题。
我认为使用 class 装饰器和不应 return 相同类型对象的方法黑名单会更像 Pythonic:
class containerize:
def __call__(self, obj):
if isinstance(obj, type):
return self.decorate_class(obj)
return self.decorate_callable(obj)
def decorate_class(self, cls):
for name in dir(cls):
attr = getattr(cls, name)
if callable(attr) and name not in ('__class__', '__init__', '__new__', '__str__', '__repr__', '__getattribute__'):
setattr(cls, name, self.decorate_callable(attr))
return cls
def decorate_callable(self, func):
def wrapper(obj, *args, **kwargs):
return obj.__class__(func(obj, *args, **kwargs))
return wrapper
这样:
class MyInt(int):
pass
@containerize()
class ClosedIntContainer(MyInt):
pass
i = ClosedIntContainer(3) + ClosedIntContainer(2)
print(i, type(i).__name__)
会输出:
5 ClosedIntContainer
作为奖励,装饰器也可以有选择地用于个别方法:
class MyInt(int):
@containerize()
def __add__(self, other):
return super().__add__(other)
i = MyInt(3) + MyInt(2)
print(i, type(i).__name__)
这输出:
5 MyInt
在数学意义上,如果操作总是 returns 集合本身的成员,则集合(或类型)在操作下 closed。
这个问题是关于制作一个class,它在从它的超classes继承的所有操作下关闭。
考虑以下 class。
class MyInt(int):
pass
由于__add__
没有被覆盖,所以在添加下不关闭
x = MyInt(6)
print(type(x + x)) # <class 'int'>
关闭类型的一种非常繁琐的方法是手动将 returns 和 int
的每个操作的结果强制转换为 MyInt
.
在这里,我使用 metaclass 使该过程自动化,但这似乎是一个过于复杂的解决方案。
import functools
class ClosedMeta(type):
_register = {}
def __new__(cls, name, bases, namespace):
# A unique id for the class
uid = max(cls._register) + 1 if cls._register else 0
def tail_cast(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
out = f(*args, **kwargs)
if type(out) in bases:
# Since the class does not exist yet, we will recover it later
return cls._register[uid](out)
return out
return wrapper
for base in reversed(bases):
for name, attr in base.__dict__.items():
if callable(attr) and name not in namespace:
namespace[name] = tail_cast(attr)
subcls = super().__new__(cls, name, bases, namespace)
cls._register[uid] = subcls
return subcls
class ClosedInt(int, metaclass=ClosedMeta):
pass
这在某些极端情况下失败,例如 property
和通过 __getattribute__
恢复的方法。当基不只由基类型组成时,它也会失败。
例如,这失败了:
class MyInt(int):
pass
class ClosedInt(MyInt, metaclass=ClosedMeta):
pass
ClosedInt(1) + ClosedInt(1) # returns the int 2
我试图解决这个问题,但它似乎越陷越深。
这似乎是一个可能有一些简单的 pythonic 解决方案的问题。实现这种封闭类型的其他更简洁的方法是什么?
这不能做,数据模型禁止这样做。我可以证明给你看:
>>> class MyClass(ClosedInt, metaclass=type):
... def __add__(self, other):
... return 'potato'
...
>>> MyClass(1) + ClosedInt(2)
'potato'
加法首先由左侧对象处理,如果左侧类型处理它(即不 return NotImplemented
单例),则 无 about other
在此操作中被考虑。如果右手类型是左手类型的子类,您可以使用反射方法控制结果__radd__
- 当然在一般情况下这是不可能的。
我认为使用元class 的想法是可行的。诀窍是在获取值时动态转换值,而不是预先转换值。这基本上就是 python 的全部意义所在:在真正得到它之前,不知道你会得到什么或那里有什么。
为此,您必须在 class 上重新定义 __getattribute__
和 __getattr__
,但有一些注意事项:
- 运算符不通过正常的属性访问方法。即使在您的 metaclass 上定义正确的
__getattribute__
和__getattr__
也无济于事。必须为每个 class. 显式覆盖 Dunder
- 由
__getattribute__
和__getattr__
编写的 return 方法需要将它们的 return 值转换为目标类型。这同样适用于称为运算符的 dunders。 - #2 中应排除一些方法以确保机器正常运行。
相同的基本铸造包装器可用于所有属性和方法 return 值。当调用 __getattribute__
或 __getattr__
.
下面显示的解决方案正是这样做的。它明确地包装了所有未列为例外的 dunder。如果所有其他属性是函数,则立即转换或包装。它允许通过检查 __mro__
中的所有内容来自定义任何方法,包括 class 本身。该解决方案将与 class 和静态方法一起正常工作,因为它存储了转换例程并且不依赖于 type(self)
(正如我之前的一些尝试所做的那样)。它将正确排除 exceptions
中列出的任何属性,而不仅仅是双下划线方法。
import functools def isdunder(x): return isinstance(x, str) and x.startswith('__') and x.endswith('__') class DunderSet: def __contains__(self, x): return isdunder(x) def wrap_method(method, xtype, cast): @functools.wraps(method) def retval(*args, **kwargs): result = method(*args, **kwargs) return cast(result) if type(result) == xtype else result return retval def wrap_getter(method, xtype, cast, exceptions): @functools.wraps(method) def retval(self, name, *args, **kwargs): result = method(self, name, *args, **kwargs) return result if name in exceptions else check_type(result, xtype, cast) return retval def check_type(value, xtype, cast): if type(value) == xtype: return cast(value) if callable(value): return wrap_method(value, xtype, cast) return value class ClosedMeta(type): def __new__(meta, name, bases, dct, **kwargs): if 'exceptions' in kwargs: exceptions = set([ '__new__', '__init__', '__del__', '__init_subclass__', '__instancecheck__', '__subclasscheck__', *map(str, kwargs.pop('exceptions')) ]) else: exceptions = DunderSet() target = kwargs.pop('target', bases[0] if bases else object) cls = super().__new__(meta, name, bases, dct, **kwargs) for base in cls.__mro__: for name, item in base.__dict__.items(): if isdunder(name) and (base is cls or name not in dct) and callable(item): if name in ('__getattribute__', '__getattr__'): setattr(cls, name, wrap_getter(item, target, cls, exceptions)) elif name not in exceptions: setattr(cls, name, wrap_method(item, target, cls)) return cls def __init__(cls, *args, **kwargs): return super().__init__(*args) class MyInt(int): def __contains__(self, x): return x == self def my_op(self, other): return int(self * self // other) class ClosedInt(MyInt, metaclass=ClosedMeta, target=int, exceptions=['__index__', '__int__', '__trunc__', '__hash__']): pass class MyClass(ClosedInt, metaclass=type): def __add__(self, other): return 1 print(type(MyInt(1) + MyInt(2))) print(0 in MyInt(0), 1 in MyInt(0)) print(type(MyInt(4).my_op(16))) print(type(ClosedInt(1) + ClosedInt(2))) print(0 in ClosedInt(0), 1 in ClosedInt(0)) print(type(ClosedInt(4).my_op(16))) print(type(MyClass(1) + ClosedInt(2)))
结果是
<class 'int'>
True False
<class 'int'>
<class '__main__.ClosedInt'>
True False
<class '__main__.ClosedInt'>
<class 'int'>
最后一个例子是对
IDEOne link 因为我现在无法访问计算机:https://ideone.com/iTBFW3
附录 1:改进的默认异常
我认为通过仔细阅读文档的 special method names 部分,可以得到比所有 dunder 方法更好的默认异常集。方法可以分为两大类 classes:具有使 python 机制工作的非常具体 return 类型的方法,以及在 return 您感兴趣的实例。还有第三类,即应始终排除在外的方法,即使您忘记明确提及它们也是如此。
以下是始终排除的方法列表:
__new__
__init__
__del__
__init_subclass__
__instancecheck__
__subclasscheck__
以下是默认情况下应排除的所有内容的列表:
__repr__
__str__
__bytes__
__format__
__lt__
__le__
__eq__
__ne__
__gt__
__ge__
__hash__
__bool__
__setattr__
__delattr__
__dir__
__set__
__delete__
__set_name__
__slots__
(不是方法,但还是)__len__
__length_hint__
__setitem__
__delitem__
__iter__
__reversed__
__contains__
__complex__
__int__
__float__
__index__
__enter__
__exit__
__await__
__aiter__
__anext__
__aenter__
__aexit__
如果我们将此列表存储到一个名为 default_exceptions
的变量中,则 class DunderSet
可以完全删除,并且提取 exceptions
的条件可以替换为:
exceptions = set([
'__new__', '__init__', '__del__',
'__init_subclass__', '__instancecheck__', '__subclasscheck__',
*map(str, kwargs.pop('exceptions', default_exceptions))
])
附录 2:改进的定位
应该可以很容易地定位多种类型。这在扩展 ClosedMeta
的其他实例时特别有用,它可能不会覆盖我们想要的所有方法。
这样做的第一步是使 target
成为 class 的容器,而不是单个 class 引用。而不是
target = kwargs.pop('target', bases[0] if bases else object)
做
target = kwargs.pop('target', bases[:1] if bases else [object])
try:
target = set(target)
except TypeError:
target = {target}
现在用blah in target
(或blah in xtype
)替换blah == target
(或包装器中的blah == xtype
)。
我仍然觉得可能有更自然的方法来完成这个,但我能够解决问题中提供的尝试。
以下是需要修复的要点。
我们必须检查 mro 中所有 类 的方法,而不仅仅是 bases;
__getattribute__
和__getattr__
必须作为特例处理;具有
__get__
的属性必须分开处理;我们必须编写异常列表,因为
__int__
或__eq__
等方法显然应该 return 它们的预期类型。
代码
import functools
def get_mro(bases):
# We omit 'object' as it is the base type
return type('', bases, {}).__mro__[1:-1]
class ClosedMeta(type):
_register = {}
# Some methods return type must not change
_exceptions = ('__int__', '__eq__', ...)
def __new__(cls, name, bases, namespace):
# A unique id for the class
uid = max(cls._register) + 1 if cls._register else 0
mro = get_mro(bases)
def tail_cast(f):
"""Cast the return value of f"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
out = f(*args, **kwargs)
if type(out) in mro:
# Since the class does not exist yet, we will recover it later
return cls._register[uid](out)
return out
return wrapper
def deep_tail_cast(f):
"""Cast the return value of f or the return value of f(...)"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
out = f(*args, **kwargs)
if callable(out):
return tail_cast(out)
elif type(out) in mro:
return cls._register[uid](out)
else:
return out
return wrapper
class PropertyCast:
"""Cast the return value of a property"""
def __init__(self, prop):
self.prop = prop
def __get__(self, instance, owner):
return cls._register[uid](self.prop.__get__(instance, owner))
def __set__(self, instance, value):
return self.prop.__set__(instance, value)
def __delete__(self, instance):
return self.prop.__delete__(instance)
for base in reversed(mro):
for name, attr in base.__dict__.items():
if name in ('__getattr__', '__getattribute__'):
namespace[name] = deep_tail_cast(attr)
elif callable(attr) and name not in namespace and name not in cls._exceptions:
namespace[name] = tail_cast(attr)
elif hasattr(attr, '__get__'):
namespace[name] = PropertyCast(attr)
subcls = super().__new__(cls, name, bases, namespace)
cls._register[uid] = subcls
return subcls
例子
class MyInt(int):
def __getattr__(self, _):
return 1
@property
def foo(self):
return 2
class ClosedInt(MyInt, metaclass=ClosedMeta):
pass
x = ClosedInt(2)
print(type(x * x), x * x)
print(type(x.foo), x.foo)
print(type(x.bar), x.bar)
输出
<class '__main__.ClosedIntContainer'> 4
<class '__main__.ClosedIntContainer'> 2
<class '__main__.ClosedIntContainer'> 1
这还有一些问题。举例来说,我们仍然有繁琐的任务来检查所有 dunder 方法并标记已实施规则的例外情况,但除非某处有这些列表,否则这似乎是不可避免的。
每个人都在写短代码和元 classes,而我几乎不写装饰器。 (该死的,哈哈)但我还是要分享它。
from functools import wraps
class CLOSED:
_built_ins = [
'__add__', '__sub__', '__mul__', '__floordiv__',
'__div__', '__truediv__', '__mod__', '__divmod__',
'__pow__', '__lshift__', '__rshift__','__and__',
'__or__', '__xor__',
]
@staticmethod
def register_closed(method): # Or you can use type annotations
method.registered = True # Or you can add the method names as string to closed decorator
return method # In this version you decorate the methods with this
@staticmethod
def closed_method(method, cls):
@wraps(method)
def wrapper(*a, **kw):
return cls(method(*a, **kw))
return wrapper
@classmethod
def closed_class(klass, cls):
for magic in klass._built_ins:
_method = getattr(cls, magic, False)
if _method:
setattr(cls, magic, klass.closed_method(_method, cls))
for method in dir(cls):
c1 = method not in klass._built_ins
c2 = method not in dir(object)
c3 = getattr(getattr(cls, method), 'registered', False)
if all((c1, c2, c3)):
_method = getattr(cls, method)
setattr(cls, method, klass.closed_method(_method, cls))
return cls
完成这么长的设置后,您只需像往常一样装饰 class;我太困了,无法让它与继承的 classes 一起工作,所以现在你必须装饰从封闭的 class.
继承的 class@CLOSED.closed_class
class foo(int):
@CLOSED.register_closed # or if you can simply add this to CLOSED.closed_class
def bar(self, other): # if you are certain that every method can be casted to its own class
"""Basically just the __add__ method"""
return self + other
print(type(foo(1) + foo(1))); print(foo(1) + foo(1)) # <class '__main__.foo'> 2
print(type(foo(1).bar(2))); print(foo(1).bar(2)) # <class '__main__.foo'> 3
@CLOSED.closed_class
class baz(foo):
pass
print(type(baz(1) + baz(3))); print(baz(1) + baz(3)) # <class '__main__.baz'> 4
print(type(baz(1).bar(4))); print(baz(1).bar(4)) # <class '__main__.baz'> 5
请随意投反对票,因为我仍然不确定我是否正确理解了这个问题。
我认为使用 class 装饰器和不应 return 相同类型对象的方法黑名单会更像 Pythonic:
class containerize:
def __call__(self, obj):
if isinstance(obj, type):
return self.decorate_class(obj)
return self.decorate_callable(obj)
def decorate_class(self, cls):
for name in dir(cls):
attr = getattr(cls, name)
if callable(attr) and name not in ('__class__', '__init__', '__new__', '__str__', '__repr__', '__getattribute__'):
setattr(cls, name, self.decorate_callable(attr))
return cls
def decorate_callable(self, func):
def wrapper(obj, *args, **kwargs):
return obj.__class__(func(obj, *args, **kwargs))
return wrapper
这样:
class MyInt(int):
pass
@containerize()
class ClosedIntContainer(MyInt):
pass
i = ClosedIntContainer(3) + ClosedIntContainer(2)
print(i, type(i).__name__)
会输出:
5 ClosedIntContainer
作为奖励,装饰器也可以有选择地用于个别方法:
class MyInt(int):
@containerize()
def __add__(self, other):
return super().__add__(other)
i = MyInt(3) + MyInt(2)
print(i, type(i).__name__)
这输出:
5 MyInt