10

I have Python classes with object attributes which are only declared as part of running the constructor, like so:

class Foo(object):
    def __init__(self, base):
        self.basepath = base

        temp = []
        for run in os.listdir(self.basepath):
            if self.foo(run):
                temp.append(run)
        self.availableruns = tuple(sorted(temp))

If I now use either help(Foo) or attempt to document Foo in Sphinx, the self.basepath and self.availableruns attributes are not shown. That's a problem for users of our API.

I've tried searching for a standard way to ensure that these "dynamically declared" attributes can be found (and preferably docstring'd) by the parser, but no luck so far. Any suggestions? Thanks.

bad_coder
  • 11,289
  • 20
  • 44
  • 72
andybuckley
  • 1,114
  • 2
  • 11
  • 24

2 Answers2

11

I've tried searching for a standard way to ensure that these "dynamically declared" attributes can be found (and preferably docstring'd) by the parser, but no luck so far. Any suggestions?

They cannot ever be "detected" by any parser.

Python has setattr. The complete set of attributes is never "detectable", in any sense of the word.

You absolutely must describe them in the docstring.

[Unless you want to do a bunch of meta-programming to generate docstrings from stuff you gathered from inspect or something. Even then, your "solution" would be incomplete as soon as you starting using setattr.]

class Foo(object):
    """
    :ivar basepath:
    :ivar availableruns:
    """
    def __init__(self, base):
S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • Thanks. Yes, I appreciate that the attributes aren't computable in general, just wasn't sure if there was a heuristic for getting some of those declared in a simple/standard way by e.g. source scanning rather than class object inspection. Or by modifying the code to declare these attrs as properties so that Sphinx/help would "find" them. But anyway, that pointer to the Sphinx syntax for declaring their existence for doc purposes will do fine: cheers! – andybuckley Oct 20 '10 at 14:27
  • "Or by modifying the code to declare these attrs as properties"? "declare" is not a Python concept. Using properties method functions for your attributes will work if you are going to have the members automatically documented. That seems like **more** work than simply documenting them in the docstring. – S.Lott Oct 20 '10 at 14:30
  • But would mean that they get documented on an equal footing with other methods, which I think significantly improves the quality of documentation. I'm prepared to put in a little more work for that, if it doesn't either hit performance or make the code impenetrable. That was the point of the initial question: I'm sure I can find a way to do it, but I was wondering if there is a (de facto) standard approach which minimises the downsides. – andybuckley Oct 21 '10 at 14:57
  • 1
    "if there is a (de facto) standard approach". Yes. `:ivar name:` is the standard approach. Creating a bunch of properties won't "significantly improves the quality of documentation" at all. It will not improve it over `:ivar name:` in any way. And you'll do a ton of work for no better documentation. – S.Lott Oct 21 '10 at 15:08
  • "They cannot ever be 'detected' by any parser." Really? I've used at least one Python IDE that can detect them just fine. – Joe White Jul 18 '11 at 20:10
  • 2
    @Joe White: Only the subset that's obvious from code inspection because of the `self.` references. It's really easy to create attributes that are undetectable. Using `__getattr__` or `__getattribute__` will stop the IDE from detecting. – S.Lott Jul 18 '11 at 20:32
  • @S.Lott: But Sphinx [specifically promises to find self.foo attributes](http://sphinx-doc.org/ext/autodoc.html#directive-autoattribute), yet the feature works sporadically at best for me... – Kevin May 09 '15 at 01:26
  • @Kevin: For _documented_ attributes? It can't find undocumented attributes, but not finding a documented should be reported as a bug. Documented means there is a doc comment on the line before or a fake docstring on the line after. – BlackJack Jan 30 '20 at 15:23
2

You could define a class variable with the same name as the instance variable. That class variable will then be shadowed by the instance variable when you set it. E.g:

class Foo(object):
    #: Doc comment for availableruns
    availableruns = ()

    def __init__(self, base):
        ...
        self.availableruns = tuple(sorted(temp))

Indeed, if the instance variable has a useful immutable default value (eg None or the empty tuple), then you can save a little memory by just not setting the variable if should have its default value. Of course, this approach won't work if you're talking about an instance variable that you might want to delete (e.g., del foo.availableruns)-- but I find that's not a very common case.

If you're using sphinx, and have "autoattribute" set, then this should get documented appropriately. Or, depending on the context of what you're doing, you could just directly use the Sphinx .. py:attribute:: directive.

randomir
  • 17,989
  • 1
  • 40
  • 55
Edward Loper
  • 15,374
  • 7
  • 43
  • 52