1
\$\begingroup\$

I've written a text parser that extracts useful information from a given text. Texts are formatted different, so there are different specialized parsers. I've designed them so that they all must define the methods from the interface Parser.

class Parser:
    def parse(text):
        raise NotImplementedError

    def pre_validate(text):
        raise NotImplementedError

    def post_validate():
        raise NotImplementedError

How can I force all subclasses of Parser to call pre_validate before running parse and post_validate after calling parse?

I could solve it like this, but I don't think it's very elegant to wrap methods like this:

class Parser:       
    def parse(text):
        self.pre_validate(text)
        self._parse()
        self.post_validate(text)

    def _parse(text):
        raise NotImplementedError

    def pre_validate(text):
        raise NotImplementedError

    def post_validate():
        raise NotImplementedError

Ideally I'd like to allow the subclasses to implement the method parse instead of _parse. What's the recommended way of solving this? Would it make sense to use a python decorator here?

\$\endgroup\$

closed as off-topic by Stephen Rauch, Sᴀᴍ Onᴇᴌᴀ, Billal Begueradj, t3chb0t, Toby Speight Jun 25 '18 at 8:17

This question appears to be off-topic. The users who voted to close gave this specific reason:

  • "Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site." – Stephen Rauch, Sᴀᴍ Onᴇᴌᴀ, Billal Begueradj, Toby Speight
If this question can be reworded to fit the rules in the help center, please edit the question.

1
\$\begingroup\$

You could fix the issue by making parser a collection of callables, rather than a class with fixed methods. This would be handy for other aspects of this kind of parsing, so you could easily reuse pre-parsing and post-parsing functions that might be more similar than the parsing:

class ParseOperation(object):

    def __init__(self, pre_validator, parser, post_validator):
       self.pre_validator = pre_validator
       self.parser= parser
       self.post_validator= post_validator

   def parse(self, text):
      self.pre_validator(self, text)
      self.parser(self, text)
      self.post_validator(self, text)

class Validator(object):

   def validate(self, owner, text):
     print 'validating %s' % text

   def __call__(self, owner, text):
     self.validate(owner, text)

class Parser (object):
   def parse(self, owner, text):
      print 'parsing %s' % text

   def __call__(self, owner, text):
     self.parse(owner, text)

Instead of overriding methods, you subclass Parser and Validator to compose your parse operation.

\$\endgroup\$
2
\$\begingroup\$

You could achieve that like this:

class Parser:
    def __init__(self):
        self.parse = self._validated_parsing(self.parse)

    def parse(self, text):
        raise NotImplementedError

    def pre_validate(self, text):
        raise NotImplementedError

    def post_validate(self, text):
        raise NotImplementedError

    def _validated_parsing(self, parse_func):
        def parse_wrapper(text):
            self.pre_validate(text)
            parse_func(text)
            self.post_validate(text)
        return parse_wrapper

class ParserChild(Parser):
    def parse(self, text):
        print('parsing')

    def pre_validate(self, text):
        print('pre-validating')

    def post_validate(self, text):
        print('post-validating')

ParserChild().parse('spam')
# pre-validating
# parsing
# post-validating

I recommend doing it with an extra method that is called from parse though, like the _parse method you wrote. It should not be called directly from outside, so it should be written with a leading underscore. You might want to want to call it something more explicit, such as (in lack of a better name) _core_parse.

While we're at it, maybe pre_validate and post_validate should be preceded with an underscore too (depending on whether you want them to be called from outside the class).

Instead of raising NotImplementedError in the base class, you should use abc.ABCMeta for an abstract base class. This is better for two reasons:

  • You can't create a subclass that doesn't implement all abstract methods.
  • It will work together properly with the super function which delegates calls to superclasses (base classes).

So, I think I would implement the class like this:

import abc

class Parser(object):
    __metaclass__ = abc.ABCMeta

    def parse(self, text):
        self._pre_validate(text)
        self._core_parse(text)
        self._post_validate(text)

    @abc.abstractmethod
    def _pre_validate(self, text):
        pass

    @abc.abstractmethod
    def _core_parse(self, text):
        pass

    @abc.abstractmethod
    def _post_validate(self, text):
        pass

class ParserChild(Parser):
    def _core_parse(self, text):
        print('parsing')

    def _pre_validate(self, text):
        print('pre-validating')

    def _post_validate(self, text):
        print('post-validating')

ParserChild().parse('spamspam')
# pre-validating
# parsing
# post-validating

(I'm assuming you use Python 3, but if you're using Python 2, you should let Parser inherit from object. In Python 3, this happens automatically.)

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.