使用python多重继承类实现的一个好习惯?

时间:2018-01-15 05:56:46

标签: python class metaprogramming multiple-inheritance

情景:

class A:
    def __init__(self, key, secret):
        self.key = key
        self.secret = secret
    def same_name_method(self):
        do_some_staff
    def method_a(self):
        pass

class B:
    def __init__(self, key, secret):
        self.key = key
        self.secret = secret
    def same_name_method(self):
        do_another_staff
    def method_b(self):
        pass

class C(A,B):
    def __init__(self, *args, **kwargs):
    # I want to init both class A and B's key and secret
    ## I want to rename class A and B's same method 
        any_ideas()
    ...

我想要的:

  1. 我希望C类的实例初始化A类和B类,因为它们是不同的api密钥。
  2. 我想重命名A类和B的same_name_method,所以我不会混淆same_name_method。
  3. 我做了什么:

    对于第一个问题,我已经这样做了:

    class C(A,B):
        def __init__(self, *args, **kwargs):
            A.__init__(self, a_api_key,a_api_secret)
            B.__init__(self, b_api_key,b_api_secret)
    

    评论:我知道super(),但是对于这种情况我不知道如何使用它。

    对于问题二,我为C类添加__new__

    def __new__(cls, *args, **kwargs):
        cls.platforms = []
        cls.rename_method = []
    
        for platform in cls.__bases__:
            # fetch platform module name
            module_name = platform.__module__.split('.')[0]
            cls.platforms.append(module_name)
            # rename attr
            for k, v in platform.__dict__.items():
                if not k.startswith('__'):
                    setattr(cls, module_name+'_'+k, v)
                    cls.rename_method.append(k)
    
        for i in cls.rename_method:
            delattr(cls, i)   ## this line will raise AttributeError!!
        return super().__new__(cls)
    

    评论:因为我重命名了新的方法名称并将其添加到cls attr。我需要删除旧方法attr,但不知道如何解析。现在我只是让他们独自一人,没有删除旧的方法。

    问题:

    任何建议?

1 个答案:

答案 0 :(得分:3)

所以,你想要一些非常高级的东西,一些复杂的东西,而且你不太了解类在Python中的行为。

所以,首先要做的是:初始化两个类,以及应该在所有类中运行的所有其他方法:正确的解决方案是使用对super()方法的协作调用。

在Python中调用super()会返回一个非常特殊的代理对象,它反映下一个类中可用的所有方法,遵循正确的方法Resolution Order。

因此,如果必须调用A.__init__B.__init__,则两种方法都应包含super().__init__次调用 - 并且会以适当的顺序调用另一种__init__,无论它们如何在子类中用作基础。由于对象也有__init__,最后super().__init__只会将其称为无操作。如果您的类中有更多应该在所有基类中运行的方法,那么您最好构建一个合适的基类,以便最顶层的super()调用不会尝试传播到不存在的方法。 / p>

否则,它只是:

class A:
    def __init__(self, akey, asecret, **kwargs):
        self.key = akey
        self.secret = asecret
        super().__init__(**kwargs)

class B:
    def __init__(self, bkey, bsecret, **kwargs):
        self.key = bkey
        self.secret = bsecret
        super().__init__(**kwargs)

class C(A,B):
    # does not even need an explicit `__init__`.

我认为你可以得到这个想法。当然,参数名称必须不同 - 理想情况下,在编写C时,您不必担心参数顺序 - 但是在调用C时,您必须担心为C及其基础提供所有必需参数。如果您不能将A或B中的参数重命名为不同,则可以尝试使用参数顺序进行调用,每个__init__消耗两个位置参数 - 但这需要额外注意在继承顺序。

所以 - 到目前为止,它是基本的Python多重继承“howto”,应该非常简单。现在来了你的奇怪的东西。

至于方法的自动重命名:首先要做的事情 -

  • 你确定需要继承吗?也许为每个外部服务提供粒度类,以及通过组合调用其他方法的注册表和调度类将更加清晰。 (我稍后可能会回来)

  • 您是否知道为每个类的实例化调用了__new__,并且您在那里执行的所有类属性修改都发生在每个类的新实例中?

因此,如果需要的方法重命名+阴影需要在类创建时进行,则可以使用Python 3.6中存在的特殊方法__init_subclass__来实现。它是一个特殊的类方法,对它定义的类的每个派生类调用一次。因此,只需创建一个基类,AB本身将从中继承,并将正确修改的版本移动到__new__那里。如果您不使用Python 3.6,则应该在元类的__new____init__上进行,而不是在类本身的__new__上进行。

另一种方法是使用自定义__getattribute__方法 - 这可以用来为基类提供名称空间。它会对实例感兴趣,而不是类本身(但可以再次使用元类)。 __getattribute__甚至可以隐藏同名方法。

class Base:

    @classmethod
    def _get_base_modules(cls):
        result = {}
        for base in cls.__bases__:
            module_name = cls.__module__.split(".")[0]
            result[module_name] = base
        return result

    @classmethod
    def _proxy(self, module_name):
        class base:
            def __dir__(base_self):
                return dir(self._base_modules[module_name])
            def __getattr__(base_self, attr):
                original_value = self._base_modules[module_name].__dict__[attr]
                if hasattr(original_value, "__get__"):
                    original_value = original_value.__get__(self, self.__class__)
                return original_value

        base.__name__ = module_name
        return base()

    def __init_subclass__(cls):
        cls._base_modules = cls._get_base_modules()
        cls._shadowed = {name for module_class in cls._base_modules.values() for name in module_class.__dict__ if not name.startswith("_")}


    def __getattribute__(self, attr):
        if attr.startswith("_"):
            return super().__getattribute__(attr)
        cls = self.__class__
        if attr in cls._shadowed:
            raise AttributeError(attr)
        if attr in cls._base_modules:
            return cls._proxy(attr)
        return super().__getattribute__(attr)

    def __dir__(self):
        return super().dir() + list(self._base_modules)


class A(Base):
   ...

class B(Base):
    ...


class C(A, B):
    ...

正如你所看到的 - 这很有趣,但开始变得非常复杂 - 并且在使用人工命名空间后从超类中检索实际属性所需的所有hoola-boops似乎表明你的问题不是要求毕竟使用继承,如上所述。

由于每个“服务”都有一个小的,功能性的原子类,你可以使用一个简单的,非元的所有类,它可以作为各种服务的注册表 - 你甚至可以增强它以通过一次调用在其处理的几个服务中调用等效方法:

class Services:
    def __init__(self):
        self.registry = {}

    def register(self, cls, key, secret):
        name = cls.__module__.split(".")[0]
        service= cls(key, secret)
        self.registry[name] = service

    def __getattr__(self, attr):
        if attr in self.registry:
            return self.registry[attr]