4

hasattr(obj, attribute) is used to check if an object has the specified attribute but given an attribute is there a way to know where (all) it is defined?

Assume that my code is getting the name of an attribute (or a classmethod) as string and I want to invoke classname.attribute but I don't have the classname.

One solution that comes to my mind is this

def finder(attr):
    for obj in globals():
        try:
            if globals()[obj].__dict__[attr]:
                return(globals()[obj])
        except:
            ...

usage:

class Lime(object):
    @classmethod
    def lfunc(self):
        print('Classic')

getattr(finder('lfunc'),'lfunc')() #Runs lfunc method of Lime class

I am quite sure that this is not the best (oe even proper way) to do it. Can someone please provide a better way.

adrianp
  • 2,491
  • 5
  • 26
  • 44
Dev Maha
  • 1,133
  • 1
  • 11
  • 24
  • It seems that iterating over `globals().items()` would be easier, but still not pretty... – mgilson Apr 16 '13 at 17:03
  • 4
    That seems like an awful situation to be in. –  Apr 16 '13 at 17:05
  • 10
    Why would you ever need to do this? For the functions you are looking for, will they always be class methods for a class defined in the same module? – Andrew Clark Apr 16 '13 at 17:06
  • 6
    This is not the *inverse* of `hasattr()`. The inverse would be `not hasattr(obj, attr)`. This is just an architecture waiting to be tossed out and rethought. What problem are you trying to solve? – Martijn Pieters Apr 16 '13 at 17:08
  • 3
    It would more useful if you would tell us what exactly you want to achieve by having this, because your idea is a clear sign of code smell and you might be missing something obvious. – adrianp Apr 16 '13 at 17:12
  • It's certainly something that was previously written w/o much consideration, there is a file which maintains customer attributes(implemented somewhere as Class attributes), but there is no documentation to tell where they are. We can either search files manually or dynamically use attribute name and determine the class it is contained in. It's really bad, I know – Dev Maha Apr 16 '13 at 17:15
  • @AccEnq Refactoring is clearly a much better investment, IDEs, grep etc. come to your help. – adrianp Apr 16 '13 at 17:17
  • @adraianp,Martijn - Totally agree. I am trying to find something that can keep this stack from falling while we refactor it. – Dev Maha Apr 16 '13 at 17:20
  • 5
    To question downvoters: the fact the question details a code situation that should not happen should not make the question be devalued - the explanation of why this is not the best (or even not a resonable) way to resolve the problem at hand should come in comments and answers. The question is well defined andformulated. If it is not usefull for others, just don't upvote it. – jsbueno Apr 16 '13 at 17:38
  • @AccEnq, try to make a registry of all classes you need to check for specific attribute. It'll make it much easier to implement and later refactor. – Anton Strogonoff Apr 16 '13 at 19:02
  • Take a look at this SO question on Python object introspection: http://stackoverflow.com/questions/546337/how-do-i-perform-introspection-on-an-object-in-python-2-x – Alex Reynolds Apr 16 '13 at 21:51

3 Answers3

4

It is always "possible". Wether it is desirable is another history.

A quick and dirty way to do it is to iterate linearly over all classes and check if any define the attribute you have. Of course, that is subject to conflicts, and it will yield the first class that has such a named attribute. If it exists in more than one, it is up to you to decide which you want:

def finder(attr):
    for cls in object.__subclasses__(): 
         if hasattr(cls, attr):
              return cls
    raise ValueError

Instead of searching in "globals" this searches all subclasses of "object" - thus the classes to be found don't need to be in the namespace of the module where the finder function is.

If your methods are unique in teh set of classes you are searching, though, maybe you could just assemble a mapping of all methods and use it to call them instead.

Let's suppose all your classes inehrit from a class named "Base":

mapper = {attr_name:getattr(cls, attr_name)  for cls in base.__subclasses__() for attr_name, obj in cls.__dict__.items()
             if isinstance(obj, classmethod) }

And you call them with mapper['attrname']()

This avoids a linear search at each method call and thus would be much better.

- EDIT -

__subclassess__ just find the direct subclasses of a class, not the inheritance tree - so it won't be usefull in "real life" - maybe it is in the specifc case the OP has in its hands.
If one needs to find things across a inheritance tree, one needs to recurse over the each subclass as well.

As for old-style classes: of course this won't work - that is one of the motives for which they are broken by default in new code.

As for non-class attributes: they can only be found inspecting instances anyway - so another method has to be thought of - does not seem to be the concern of the O.P. here.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • This is decent for a very indecent situation. Thanks jsbueno. – Dev Maha Apr 16 '13 at 17:39
  • 1
    Since duplicates (aka "conflicts") are possible, I think it would be better to return a list of all classes which have the attribute -- although that would make it _much_ slower since it couldn't quit as soon as it found a match. – martineau Apr 16 '13 at 17:45
  • 3
    This is a clever solution but you'll need to run through the subclasses of the found classes also. It won't find old-style python 2.x classes that don't inherit from object and although it will find class methods, it won't find data attributes, which are not part of the class schema. – tdelaney Apr 16 '13 at 17:48
  • 1
    I think the fact that it won't find data attributes is also a significant limitation. – martineau Apr 16 '13 at 18:04
  • 2
    This doesn't work for class methods of subclasses as illustrated by this [file](https://www.dropbox.com/s/t624egtbsotzfhd/inv_hasattr.py) where it doesn't find `method2()` of the `Derived` class. – martineau Apr 16 '13 at 18:15
  • Indeed - I thought subclasses would find all subclasses of a class, not just the ones that inherit directly. – jsbueno Apr 17 '13 at 11:24
2

This might help:

import gc


def checker(checkee, maxdepth = 3):

    def onlyDict(ls):
        return filter(lambda x: isinstance(x, dict), ls)

    collection = []
    toBeInspected = {}
    tBI = toBeInspected
    gc.collect()
    for dic in onlyDict(gc.get_referrers(checkee)):
        for item, value in dic.iteritems():
            if value is checkee:
                collection.append(item)
            elif item != "checker":
                tBI[item] = value

    def _auxChecker(checkee, path, collection, checked, current, depth):
        if current in checked: return
        checked.append(current)
        gc.collect()
        for dic in onlyDict(gc.get_referents(current)):
            for item, value in dic.iteritems():
                currentPath = path + "." + item
                if value is checkee:
                    collection.append(currentPath)
                else:
                    try:
                        _auxChecker(checkee, currentPath, collection,
                                    checked, value, depth + 1)
                        if depth < maxdepth else None
                    except TypeError:
                        continue
    checked = []
    for item, value in tBI.iteritems():
        _auxChecker(checkee, item, collection, checked, value, 1)
    return collection

How to use:

referrer = []

class Foo:        
        pass          

noo = Foo()       
bar = noo         
import xml        
import libxml2    
import sys        
import os         
op = os.path      
xml.foo = bar     
foobar = noo      

for x  in checker(foobar, 5):
    try:
        y= eval(x)
        referrer.append(x)
    except:
        continue

del x, y

ps: attributes of the checkee will not be further checked, for recursive or nested references to the checkee itself.

Bleeding Fingers
  • 6,993
  • 7
  • 46
  • 74
  • That just works if all classes the OP wnats dto llok for are in the namespace of the current module (the module where "checker" is defined). I don think it is the best way to do it. – jsbueno Apr 16 '13 at 17:26
1

This should work in all circumstances, but still needs a lot of testing:

import inspect
import sys


def finder(attr, classes=None):
    result = []
    if classes is None:
        # get all accessible classes
        classes = [obj for name, obj in inspect.getmembers(
            sys.modules[__name__])]
    for a_class in classes:
        if inspect.isclass(a_class):
            if hasattr(a_class, attr):
                result.append(a_class)
            else:
                # we check for instance attributes
                if hasattr(a_class(), attr):
                    result.append(a_class)
            try:
                result += finder(attr, a_class.__subclasses__())
            except:
                # old style classes (that don't inherit from object) do not
                # have __subclasses; not the best solution though
                pass
    return list(set(result))  # workaround duplicates


def main(attr):
    print finder(attr)
    return 0


if __name__ == "__main__":
    sys.exit(main("some_attr"))
adrianp
  • 2,491
  • 5
  • 26
  • 44