简体   繁体   中英

A good practice to implement with python multiple inheritance class?

The Scenario:

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()
    ...

What I Want:

  1. I want the instance of class C initialize both class A and B, because they are different api key.
  2. And I want rename class A and B's same_name_method, so I will not confused at which same_name_method.

What I Have Done:

For problem one, I have done this:

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)

Comment: I know about super(), but for this situation I do not know how to use it.

For problem two, I add a __new__ for class C

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)

Comment: because I rename the new method names and add it to cls attr. I need to delete the old method attr, but do not know how to delattr. Now I just leave them alone, did not delete the old methods.

Question:

Any Suggestions?

So, you want some pretty advanced things, some complicated things, and you don't understand well how classes behave in Python.

So, for your first thing: initializing both classes, and every other method that should run in all classes: the correct solution is to make use of cooperative calls to super() methods.

A call to super() in Python returns you a very special proxy objects that reflects all methods available in the next class, obeying the proper method Resolution Order.

So, if A.__init__ and B.__init__ have to be called, both methods should include a super().__init__ call - and one will call the other's __init__ in the appropriate order, regardless of how they are used as bases in subclasses. As object also have __init__ , the last super().__init__ will just call it that is a no-op. If you have more methods in your classes that should be run in all base classes, you'd rather build a proper base class so that the top-most super() call don't try to propagate to a non-existing method.

Otherwise, it is just:

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__`.

I think you can get the idea. Of course, the parameter names have to differ - ideally, when writing C you don't have to worry about parameter order - but when calling C you have to worry about suplying all mandatory parameters for C and its bases. If you can't rename the parameters in A or B to be distinct, you could try to use the parameter order for the call, though, with each __init__ consuming two position-parameters - but that will require some extra care in inheritance order.

So - up to this point, it is basic Python multiple-inheritance "howto", and should be pretty straightforward. Now comes your strange stuff.

As for the auto-renaming of methods: first things first -

  • are you quite sure you need inheritance? Maybe having your granular classes for each external service, and a registry and dispatch class that call the methods on the others by composition would be more sane. (I may come back to this later)

  • Are you aware that __new__ is called for each instantiation of the class, and all class-attribute mangling you are performing there happens at each new instance of your classes?

So, if the needed method-renaming + shadowing needs to take place at class creation time, you can do that using the special method __init_subclass__ that exists from Python 3.6. It is a special class method that is called once for each derived class of the class it is defined on. So, just create a base class, from which A and B themselves will inherit, and move a properly modified version the thing you are putting in __new__ there. If you are not using Python 3.6, this should be done on the __new__ or __init__ of a metaclass, not on the __new__ of the class itself.

Another approach would be to have a custom __getattribute__ method - this could be crafted to provide namespaces for the base classes. It would owrk ony on instances, not on the classes themselves (but could be made to, again, using a metaclass). __getattribute__ can even hide the same-name-methods.

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):
    ...

As you can see - this is some fun, but starts getting really complicated - and all the hoola-boops that are needed to retrieve the actual attributes from the superclasses after ading an artificial namespace seem to indicate your problem is not calling for using inheritance after all, as I suggested above.

Since you have your small, functional, atomic classes for each "service" , you could use a plain, simple, non-meta-at-all class that would work as a registry for the various services - and you can even enhance it to call the equivalent method in several of the services it is handling with a single call:

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]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM