Eliy Arlev
Eliy Arlev

Reputation: 578

How can I know which base class adds a specific attribute to a child class object

i am working on a project with long hierarchy of classes from several modules in different files.

I want to know when in the inheritance chain class C got the attribute A (then I can get the module M where C is defined and inspect the code)

consider the following code example, imagine that all classes except GrandChildOf1ChildOf2 are in other modules, is there a command, something like: attribute_base(o4,'a') with output: Base1?

class SweetDir:
    def __sweet_dir__(self):
        """
        Same as dir, but will omit special attributes
        :return: string
        """
        full_dir = self.__dir__()
        sweet_dir  = []
        for attribute_name in full_dir:

            if not (    attribute_name.startswith('__')
                    and  attribute_name.endswith('__')):
                #not a special attribute
                sweet_dir.append(attribute_name)
        return sweet_dir

class Base1(SweetDir):
    def __init__(self):
        super(Base1,self).__init__()
        self.a = 'a'

class Base2(SweetDir):
    def __init__(self):
        super(Base2,self).__init__()
        self.b = 'b'

class ChildOf1 (Base1):
    def __init__(self):
        super(ChildOf1,self).__init__()
        self.c = 'c'

class GrandChildOf1ChildOf2 (Base2,ChildOf1):
    def __init__(self):
        super(GrandChildOf1ChildOf2,self).__init__()
        self.d = 'd'   


o1 = Base1()
o2 = Base2()
o3 = ChildOf1()
o4 = GrandChildOf1ChildOf2()
print(o1.__sweet_dir__())
print(o2.__sweet_dir__())
print(o3.__sweet_dir__())
print(o4.__sweet_dir__())

output:

['a']
['b']
['a', 'c']
['a', 'b', 'c', 'd']

Upvotes: 2

Views: 66

Answers (1)

Viper
Viper

Reputation: 505

I don't think there is a builtin functions for that but something like that would work (It needs improvements) :

def attribute_base(your_class, your_attr):

    for base_class in your_class.__mro__:
        if base_class != your_class:
            tmp_inst = base_class()
            if hasattr(tmp_inst, your_attr):
                return base_class

This will return the first base class of your class that got the attribute you are looking for. It is clearly not perfect. If two or more of your bases classes have the same attribute (with the same name), it might not return the actual class where you got the attribute, but in your exemple it would work. [Update with AKX comment : using __mro__ should actually solve this problem]

[Update : There is a way to do it without an instance, following this greatly documented answer : list-the-attributes-of-a-class-without-instantiating-an-object ]

from inspect import getmembers

def attribute_base(your_class, your_attr):
    for base_class in your_class.__mro__:
        if base_class != your_class:
            members = [member[1].__code__.co_names for member in getmembers(base_class) if '__init__' in member and hasattr(member[1], "__code__")]
            for member in members:
                if your_attr in members:
                    return base_class

getmembers gives you every member of the class, including the init methods which is what we want. We need to check if it's really a function (hasattr(member[1], "__code__")) because if no __init__ function is define for a class (like SweetDir in your example), this will return a wrapper_descriptor. We loop on members in the rare (possible?) case there is several __init__ method.

Upvotes: 1

Related Questions