我有多个类,比如说来自外部包的
OrigClass1
到 OrigClass100
。
我想“扩展”它们,即为它们中的每一个添加相同的一组属性和方法。
我特别希望原始类和扩展中的所有属性/方法都是“可见的”,以便在 IDE 中自动完成和检查。
我可以通过简单地创建一堆继承自原始类和扩展类的类来做到这一点:
# Code in external package
class OrigClass1:
def __init__(self, arg_1_1):
self.val_1_1 = arg_1_1
# a bunch of other classes...
# My extension class
class Extension:
def __init__(self, arg_ext):
self.val_ext = arg_ext
def do_cool_stuff(self):
print(self.val_ext)
# Define the extended classes
class ExtClass1(OrigClass1, Extension):
def __init__(self, arg_1_1, arg_ext):
OrigClass1.__init__(self, arg_1_1)
Extension.__init__(self, arg_ext)
# a bunch more of those...
# All the attributes/methods below are auto completed.
obj1 = ExtClass1(arg_1_1=1, arg_ext="A")
print(obj1.val_1_1)
print(obj1.val_ext)
obj1.do_cool_stuff()
问题:上面的解决方案非常麻烦。我可以编写一些代码,基本上自动生成所有这些定义,但这仍然给我留下了成千上万行本质上无用的代码。这似乎不利于可读性和将来的维护。
最好有一个解决方案,允许我定义继承而无需显式定义
__init__
。
# Code in external package
class OrigClass1:
def __init__(self, arg_1_1):
self.val_1_1 = arg_1_1
# My extension class
class Extension:
def __init__(self, arg_ext):
self.val_ext = arg_ext
def do_cool_stuff(self):
print(self.val_ext)
# Define the extended classes
class ExtClass1(OrigClass1, Extension):
pass
# The following doesn't actually work
obj1 = ExtClass1(arg_1_1=1, arg_ext="A")
print(obj1.val_1_1)
print(obj1.val_ext)
obj1.do_cool_stuff()
问题:上面的方法当然不起作用,因为
Extension
类永远不会被初始化,并且arg_ext
被传递给原始类。
基于这些答案,我尝试用装饰器来解决这个问题。
首先,我尝试了这样的事情:
# Code in external package
class OrigClass1:
def __init__(self, arg_1_1):
self.val_1_1 = arg_1_1
# My extension class
class Extension:
def __init__(self, arg_ext):
self.val_ext = arg_ext
def do_cool_stuff(self):
print(self.val_ext)
# Decorator
def class_extendor(cls):
init_func = cls.__init__
def __init__(self, arg_ext, *args, **kwargs):
init_func(self, *args, **kwargs)
Extension.__init__(self, arg_ext)
cls.__init__ = __init__
return cls
# Define the extended classes
@class_extendor
class ExtClass1(OrigClass1):
pass
# All the attributes/methods below are auto completed.
obj1 = ExtClass1(arg_1_1=1, arg_ext="A")
print(obj1.val_1_1)
print(obj1.val_ext)
obj1.do_cool_stuff()
问题:这不起作用,因为虽然属性
val_ext
在初始化时分配给对象,但方法do_cool_stuff
却没有。我想我可以通过单独分配额外的方法来解决这个问题。
无论如何,生成的类并不提供自动完成功能,因为它是在运行时定义的,而且至少现在 PyCharm 似乎还不够出色,无法处理这个问题。
import typing as t
# Code in external package
class OrigClass1:
def __init__(self, arg_1_1):
self.val_1_1 = arg_1_1
ORIG_TYPE = t.TypeVar("ORIG_TYPE")
# My extension class
class Extension(t.Generic[ORIG_TYPE]):
def __init__(self, arg_ext, *args, **kwargs):
ORIG_TYPE.__init__(*args, **kwargs)
self.val_ext = arg_ext
def do_cool_stuff(obj):
print(obj.val_ext)
class ExtClass1(Extension[OrigClass1]):
pass
# All the attributes/methods below are auto completed.
obj1 = ExtClass1(arg_1_1=1, arg_ext="A")
print(obj1.val_1_1)
print(obj1.val_ext)
obj1.do_cool_stuff(obj1)
问题:现在上面的方法显然不起作用,不仅因为
ORIG_TYPE.__init__
没有任何意义,还因为它仍然不会公开原始类的属性/方法。话虽这么说,这种“风格”的解决方案在某种程度上直观上似乎是可能的。
您对如何简化解决方案 1 有什么建议,或者是否有任何类似于解决方案 4 的解决方案?对于解决方案 3,是否有可能以某种方式“强制”分配属性和方法以用于类型检查器?
因此,经过很长时间的搜索,我发现最好的(尽管仍然不完美)方法是基于使用元类的:
# Code in external package
class OrigClass1:
def __init__(self, arg_1_1):
self.val_1_1 = arg_1_1
class Extension:
def __init__(self, arg_ext):
self.val_ext = arg_ext
def do_cool_stuff(self):
print(self.val_ext)
class ClassExtender(type):
def __new__(cls, name, bases, attrs):
# Update the __init__ method of the new class
def new_init(self, **kwargs):
# Initialize base classes with relevant kwargs
for base in bases:
expected_keys = base.__init__.__code__.co_varnames[1:]
pass_kwargs = {key: value for key, value in kwargs.items() if key in expected_keys}
base.__init__(self, **pass_kwargs)
attrs['__init__'] = new_init
return super().__new__(cls, name, bases, attrs)
# Define the extended classes
class ExtClass1(OrigClass1, Extension, metaclass=ClassExtender):
pass
# All the attributes/methods below are auto completed.
obj1 = ExtClass1(arg_1_1=1, arg_ext="A")
print(obj1.val_1_1)
print(obj1.val_ext)
obj1.do_cool_stuff()
这让我能够:
__init__
它仍然遗漏了一件事 - 由于
__init__
被动态覆盖,检查在通过 arg_ext
中的 ExtClass1(arg_1_1=1, arg_ext="A")
时会抛出警告。我可以接受这一点,否则上面的解决方案似乎是一个优雅的解决方案。
如果有人偶然发现这篇文章并有任何其他建议,我很乐意听到这些。