Python inspect member variable comments

Viewed 257

How do I inspect the comments of a python instance variable?

class MyClass:
    def __init__(self):
        self.myVariable=0 # How can I inspect for this comment?

I've tried:

import inspect
inspect.getcomments(MyClass().myVariable) # does nothing
inspect.getdoc(MyClass().myVariable) # returns info about int type conversion

Any thoughts?

2 Answers

I looked at the inspect.py source code, but I didn't find a way to make it do what you wanted.

But you can try this code instead:

NOTE - Tested on Ubuntu 20.04, using Python 3.8

import re

myfile = open("myclass.py")
content = myfile.read().splitlines()
lineno = 1
for c in content:
    # Search for anything after a pound sign
    if re.search("#(.+)", c):
        comment = re.split("#(.+)", c)
        # Remove any empty elements
        comment = [element for element in comment if element != '']
        # The comment will be the last element of the list
        print("Line {0}: {1}".format(lineno, comment[-1].strip()))
    lineno += 1
myfile.close()

Using as myclass.py:

1 class MyClass:
2     # A preceding comment I added to your class for testing
3     my_variable = None
4 
5     def __init__(self):
6         self.my_variable = 0  # How can I inspect for this comment?

Output:

Line 2: A preceding comment I added to your class for testing
Line 6: How can I inspect for this comment?

To avoid false positives, you can optimize the pound sign search, but I leave that to you.

I'm sure there may be edge cases not accounted for, but this works fairly well.

def getClassPropertyComments(cls):
    """
    returns {varname:comment}
    """
    quote_regex=r"""r?("{3}|"|')(?P<contents>(?:[^\1\\]|\\.)*)\1"""
    line_regex=r"""\s*self\.(?P<membername>[a-zA-Z_][a-zA-Z0-9]*)(:(?P<peptype>[^=]*))?\s*=\s*(?P<assignment>(("""+quote_regex+r""")|[^#])*)([#]\s*(?P<comment>.*))?"""
    classlevel_regex=r"""(?P<membername>[a-zA-Z_][a-zA-Z0-9]*)(:(?P<peptype>[^=]*))?\s*=\s*(?P<assignment>(("""+quote_regex+r""")|[^#])*)([#]\s*(?P<comment>.*))?"""
    ret={}
    # just in case they pass in an instance instead of a class type
    if not inspect.isclass(cls):
        cls=cls.__class__
    # inspect things coming from parent classes
    for parent in cls.__bases__:
        if parent!=object:
            ret.update(getClassPropertyComments(parent))
    # inspect class-level values
    for line in inspect.getsourcelines(cls)[0][1:]:
        noindent=line.lstrip()
        if not noindent or noindent[0]=='#': # blank line or ignore
            continue
        if isinstance(classlevel_regex,str):
            indent=line[0:-len(noindent)]
            classlevel_regex=indent+classlevel_regex
            classlevel_regex=re.compile(classlevel_regex)
        m=classlevel_regex.match(line)
        if m is not None:
            membername=m.group('membername')
            comment=m.group('comment')
            if comment is None:
                comment=ret.get(membername,'')
            ret[membername]=comment
    # inspect variables defined in __init__
    initFn=getattr(cls,'__init__')
    if initFn.__class__.__name__!='wrapper_descriptor':
        line_regex=re.compile(line_regex)
        for line in inspect.getsourcelines(initFn)[0][1:]:
            m=line_regex.match(line)
            if m is not None:
                membername=m.group('membername')
                comment=m.group('comment')
                if comment is None:
                    comment=ret.get(membername,'')
                ret[membername]=comment
    return ret

And it can handle inheritance and overrides ok as well. Test code:

class TestParent1:
    parent1ClassProperty=7 # good
class TestParent2:
    parent2ClassProperty=7 # good
    def __init__(self):
        self.parent2MemberProperty=7 # good
        self.parent2OverriddenProperty=7 # bad
class TestChild(TestParent1,TestParent2):
    childClassProperty=7 # good
    def __init__(self):
        self.childMemberProperty=7 # good
        self.parent2OverriddenProperty=7 # good
# NOTE: should have 6 "good" comments
print(getClassPropertyComments(TestChild()))
Related