David Gard
David Gard

Reputation: 12047

Python class attribute 'is not defined' when referenced by another class attribute

Using the following, I am able to successfully create a parser and add my arguments to self._parser through the __init()__ method.

class Parser:
    _parser_params = {
        'description': 'Generate a version number from the version configuration file.',
        'allow_abbrev': False
    }
    _parser = argparse.ArgumentParser(**_parser_params)

Now I wish to split the arguments into groups so I have updated my module, adding some classes to represent the argument groups (in reality there are several subclasses of the ArgumentGroup class), and updating the Parser class.

class ArgumentGroup:
    _title = None
    _description = None

    def __init__(self, parser) -> ArgumentParser:
        parser.add_argument_group(*self._get_args())

    def _get_args(self) -> list:
        return [self._title, self._description]


class ArgumentGroup_BranchType(ArgumentGroup):
    _title = 'branch type arguments'


class Parser:
    _parser_params = {
        'description': 'Generate a version number from the version configuration file.',
        'allow_abbrev': False
    }
    _parser = argparse.ArgumentParser(**_parser_params)
    _argument_groups = [cls(_parser) for cls in ArgumentGroup.__subclasses__()]

However, I'm now seeing an error.

Traceback (most recent call last):
  ...
  File "version2/args.py", line 62, in <listcomp>
    _argument_groups = [cls(_parser) for cls in ArgumentGroup.__subclasses__()]
NameError: name '_parser' is not defined

What I don't understand is why _parser_params do exist when they are referred by another class attribute, but _parser seemingly does not exist in the same scenario? How can I refactor my code to add the parser groups as required?

Upvotes: 0

Views: 1584

Answers (1)

chepner
chepner

Reputation: 531868

This comes from the confluence of two quirks of Python:

  1. class statements do not create a new local scope
  2. List comprehensions do create a new local scope.

As a result, the name _parser is in a local scope whose closest enclosing scope is the global scope, so it cannot refer to the about-to-be class attribute.

A simple workaround would be to replace the list comprehension with a regular for loop.

_argument_groups = []
for cls in ArgumentGroup.__subclasses()__:
    _argument_groups.append(cls(_parser))

(A better solution would probably be to stop using class attributes where instance attributes make more sense.)

Upvotes: 3

Related Questions