Search code examples
pythondictionaryinputgetattr

How to know which attribute and object has a specified value


I'm a newbie with python, and would really appreciate your help with my issue.

I have a class A and three subclasses B, C, D, which all inherit from A - like this.

class A:
    def __init__(self, arg1, arg2, arg3):
        self.attr1 = B
        self.attr2 = C
        self.attr3 = D

class B(A):
    name = name
    other = other

class C(A):
    name = name
    other = other

class D(A):
    name = name
    other = other

I want to write a global method which has this meaning: "If the input attribute is not equal to ANY of the attributes listed in the specified Object, then take the input attribute from WHATEVER Object B, C, D has it". I thought I could implement the code like this.

def GlobalMethod(self, input):
    if input != self.__dict__:
        getattr(A, name=input)                         
        print('attribute found here:'+getattr(self.name))
    else:
        print('attribute not found')

After assigning instances to subclasses

b = B()
c = C()
d = D()

I'd eventually pass the values like this (for sake of the example, I'll only write one instance)

GlobalMethod(b, 'big bang')

Main problems:

  1. getattr() takes no keyword arg, as the ErrorType returns.

  2. I don't exactly know how to implement "ANY of the attributes". I thought dict could be a good way.

  3. I don't exactly know how to implement "from WHATEVER Object B, C, D has it"

Thank you in advance for helping! (and sorry if my description and code are quite messy)


Solution

  • If you are looking for an attribute value within an instance of A, you can simply put all of them in a pool and scan it to fetch a matching object.

    So:

    class MappingPool:
        def __init__(self):
            self.pool = []
        def add_scannable_object(self, obj):
            self.pool.append(obj)
        def search(self, some_value):
            for obj in self.pool:
                for attr in obj.__dict__:
                    if getattr(obj, attr) == some_value:
                        return obj
    

    Should return the first object found containing an attribute matching the desired value. I'm limiting the search to instances atrributes and not including class attributes because you seem to only be interested in those. (Those defined in A.__init__.)

    To use it:

    mapping = MappingPool()
    b = B('a','b','c')
    c = C('test','d','e')
    d = D('f','g','h')
    mapping.add_scannable_object(b)
    mapping.add_scannable_object(c)
    mapping.add_scannable_object(d)
    x = mapping.search('test') # x is now an alias to c
    

    Update to react to comments and clarify between attributes and class attributes

    You’re are not restricted about the objects you put into the mapping pool. For what it’s worth, you can put anything that has a __dict__ attribute in it. Any class and any instance of any class are suitable. I.e. if in addition to your A, B, C and D classes you define:

    class Test:
        where_is_it = 5
        def __init__(self):
            it_is_here = 9
    

    then, you can add any instance of Test to the same MappingPool you used to put b, c, and d as show above. Thus:

    mapping = MappingPool()
    b = B('a','b','c')
    c = C('test','d','e')
    d = D('f','g','h')
    test = Test()
    mapping.add_scannable_object(b)
    mapping.add_scannable_object(c)
    mapping.add_scannable_object(d)
    mapping.add_scannable_object(test)
    x = mapping.search(9)
    print(x is test) # prints True
    

    What I wanted to warn about when saying that the search is not including class attributes is that, using the setup just above, mapping.search(5) returns None even though test.where_is_it is 5.

    This is due to the fact that instance variables and class variables are defined in two different namespaces and using obj.__dict__ returns only the namespace of the instance.

    You can convince yourself by typing test.__dict__ which returns {'it_is_here': 9}. So where did the where_is_it attribute go? In the class namespace, since it’s defined at the class level:

    >>> test.__class__.__dict__
    mappingproxy({'__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None, '__init__': <function Test.__init__ at 0x7f4bf6f09d08>, '__module__': '__main__', 'where_is_it': 5})
    

    Thus if you need to take into account both instance attributes and class attributes of the objects you are scanning, then you need to combine both:

    from itertools import chain
    
    for attr in chain(test.__dict__, test.__class__.__dict__):
        print(attr)
    

    outputs:

    it_is_here
    __dict__
    __weakref__
    __doc__
    __init__
    __module__
    where_is_it
    

    Conclusion, just replace for attr in obj.__dict__: by for attr in chain(obj.__dict__, obj.__class__.__dict__): if you want to support class attributes on top of instances attributes.