Home > front end >  python ArgParse and ClassInheritance
python ArgParse and ClassInheritance

Time:12-17

I have 2 classes, class A and Class B which is a child of class A. In the __init__ method of class A I used argparse to define Class A's methods. In class B __init__, I use the super().__init__() to call class A's attributes. The problem is that class B needs another attribute and I would like to parse it with argparse module

Any ideas on how to do that???

class A:
    def __init__(self):
        parser = argparse.ArgumentParser()
        parser.add_argument('a')
        parser.add_argument('a2')
        args = parser.parse_args()
        self.a1, self.a2 = args.a1, args.a2
    
    

class B(A):
    def __init__(self):
        self.a1,self.a2 = super().__init__()
        # from here on the code does not work; I would like to know how to do that.
        parser = argparse.ArgumentParser()
        parser.add_argument('b')
        args = parser.parse_args()
        self.b = args.b

The only way I found is to give an optional argument to class A parser; the optional argument will be inserted only in case Class B is being executed. This solution seems to have no logical sense to me.

CodePudding user response:

Because class B inherits from A it has properties a1 and a2 already. It's enough to call its constructor as follows:

super().__init__()

As for the argument 'b' I think the best is to instantiate parser object as a property of class A and then use it in class B if necessary:

class A:
    def __init__(self):
        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('a1')
        self.parser.add_argument('a2')
        self.parser.add_argument('b')
        args = self.parser.parse_args()
        self.a1, self.a2 = args.a1, args.a2

although as suggested in comments better not to manipulate the command line arguments parsing during class instantiation. It should be done somewhere in the upper level of your project (e.g. main()).

CodePudding user response:

So, first off, don't do this. Classes that don't exist solely for the purpose of parsing arguments (a job better done by a simple function, or just handled inline in the script entry point) should not be pulling information from sys.argv (global state that is "owned" by the script entry point, not random utility classes).

Using positional command-line arguments makes this nigh impossible to do in even a bad way (leaving argument parsing in __init__, and using parse_known_args to parse recognized args while ignoring the rest; broken because B needs its extra argument to be third positionally, and you'd have to link in extra knowledge of A to know that), leaving only truly horrific ways (involving allowing A's constructor to accept an ArgumentParser which it initializes and uses, then B adds more to it and reuses it just to get the extra value).

If the classes must be involved in making the parser, you should still separate the parser from the instance construction, e.g. putting non-instance utility methods in the classes to build a parser, using it outside the class to parse, then passing in the result to construct an instance. Something like the following is weird, but not necessarily awful:

class A:
    def __init__(self, a1, a2):
        self.a1 = a1
        self.a2 = a2
    
    @staticmethod   # Don't need to know the type here, so just static
    def make_parser():
        parser = argparse.ArgumentParser()
        parser.add_argument('a')
        parser.add_argument('a2')
        return parser

class B(A):
    def __init__(self, b, **kwargs):  # Accept and pass along arbitrary keyword args
        super().__init__(**kwargs)    # to remove need to reproduce A's parameters
        self.b = b

    @classmethod  # Use classmethod to make no-arg super work
    def make_parser(cls):
        parser = super().make_parser()
        parser.add_argument('b')
        return parser

Then in the main script, you can do something like:

def main():
    class_to_use = B if should_be_B else A
    parser = class_to_use.make_parser()
    args = parser.parse_args()

    inst = class_to_use(**vars(args))  # Extract parsed arguments as dict and pass as keyword args
    # Do stuff with something that's an A or B

if __name__ == '__main__':
    main()

I don't really recommend this in general (argument parsing should be entirely separate from random classes in general), but in the rare cases it needs to be linked like this, you still shouldn't put it in __init__ (which makes it impossible to reuse said classes with manually provided arguments without modifying sys.argv, which is the ugliest hack imaginable).

  • Related