python - 使用可与代码完成配合使用的继承/装饰器“扩展”多个类

问题描述 投票:0回答:1

我有多个类,比如说来自外部包的

OrigClass1
OrigClass100

我想“扩展”它们,即为它们中的每一个添加相同的一组属性和方法。

我特别希望原始类和扩展中的所有属性/方法都是“可见的”,以便在 IDE 中自动完成和检查。

解决方案1

我可以通过简单地创建一堆继承自原始类和扩展类的类来做到这一点:

# 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__

解决方案2实际上不起作用

# 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
被传递给原始类。

解决方案 3 这样的作品

基于这些答案,我尝试用装饰器来解决这个问题。

首先,我尝试了这样的事情:

# 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 似乎还不够出色,无法处理这个问题。

解决方案 4 不起作用,但如果能起作用那就太好了。

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,是否有可能以某种方式“强制”分配属性和方法以用于类型检查器?

python inheritance autocomplete multiple-inheritance typechecking
1个回答
0
投票

因此,经过很长时间的搜索,我发现最好的(尽管仍然不完美)方法是基于使用元类的:

# 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")
时会抛出警告。我可以接受这一点,否则上面的解决方案似乎是一个优雅的解决方案。

如果有人偶然发现这篇文章并有任何其他建议,我很乐意听到这些。

© www.soinside.com 2019 - 2024. All rights reserved.