Use of dir() in code recommended?

So opinion time… working with a codebase that has several uses of dir on objects to step through their attributes. Dir is a fully documented function, but the documentation also weasels it a bit by saying

dir() is supplied primarily as a convenience for use at an interactive prompt

so I’m wondering if that means one should avoid using it, or whether it’s nothing to worry about. Here’s an example use (the uckiest of the bunch):

   properties = [(p, getattr(obj, p, "None")) for p in dir(obj) if not (p[:2] == '__' or inspect.ismethod(getattr(obj, p)) or inspect.isbuiltin(getattr(obj,p))) ]

The tool you want depends on what you want to do. I suggest putting that in words, write a comment rather than just a code snippet. I doubt you want “all attributes except methods and those whose names end with __”.

You might want vars() to get attributes of an instance, but not it class. It only works for objects that store their attributes in __dict__: objects like integers or strings don’t have any attributes that are relevant in this way.

It you’re building something like a debugger or attribute browser for developers, then dir() would be perfect for you.

I think the documentation is pretty clear: there is no guarantee of
exactly what dir(obj) will return, whether it will return all methods
and attributes of an object, or only some, or even potentially add
entries that don’t correspond to any actual attribute.

(That would be weird, but not forbidden by the docs. I don’t know why
you would do that, but you could.)

Unless a class documents that dir() will support “everything you need
and nothing you don’t”, then you really shouldn’t be using it for
anything more than a convenience for interactive use.

(And not really that convenient, in my humble opinion. Too many dunders,
too many private single-underscore names, I nearly never want to see
them. But YMMV.)

Right now, if dir() returns an incomplete list of attributes, that’s
“not a bug”, just an inconvenience that the class writer may or may not
care about fixing.

Unfortunately there is no bulletproof function or method for iterating
over the attributes of an object. Perhaps we should reconsider the
“convenience” warning and document that dir is expected to give a
complete set of attributes, not just a convenient set. Then we can
fairly say that any object where dir() is incomplete is a bug in the
object.

You give this example:

properties = [(p, getattr(obj, p, "None")) for p in dir(obj) if 
    not (p[:2] == '__' or inspect.ismethod(getattr(obj, p)) or 
    inspect.isbuiltin(getattr(obj,p)))]

What’s the intent here? My guess it is to skip dunders and
callables, and yield … what? All other attributes? Only
properties?

Is it all callables that should be skipped or just those that are
builtin functions and methods? Should it list class methods, static
methods, other descriptors, callable instances, function objects?

If there is an attribute called ‘__’ should it be included? Currently it
isn’t. How about an instance attribute ‘__x’. Again, currently that’s
skipped, but being on the instance, it won’t be name-mangled.

Introspection is hard.

I prefer something like this:

properties = [(name, obj) for name, obj in vars(obj)
    if not (isdunder(name) or callable(obj))]

where “isdunder” is a utility function:

def isdunder(string):
    return (string.startswith('__') 
            and string.endswith('__')
            and string != '__')

but my version won’t be quite identical to the existing version. For
starters, mine only looks at attributes on the instance, not class
attributes, or slots, or superclasses, or dynamic attributes.

Did I mention introspection is hard? :slight_smile:

And of course I may have completely misunderstood the intent of the
code.

I don’t actually know what the precise intent is yet, it’s not commented and it’s a mature codebase where this long predates my involvement (most of the others of the small number of uses of dirare rather more clear) - and yes, the intent to exclude methods is intentional as there’s a later attempt to capture the methods from the same object. Thanks for the notes so far!