0

Note: although my particular use is Flask related, I think the question is more general.

I am building a Flask web application meant to be customized by the user. For example, the user is expected to provide a concrete subclass of a DatabaseInterface and may add to the list of certain ModelObjects that the application knows how to handle.

What is the best way to expose the various hooks to users, and indicate required and optional status? 'Best' here primarily means most 'pythonic', or "easiest for python users to grasp", but other criteria like not causing headaches down the road are certainly worth mentioning.

Some approaches I've considered:

  • Rely solely on documentation

  • Create a template file with documented overrides, much like default config files for many servers. E.g.

    app = mycode.get_app()
    ##Add your list of extra foo classes here
    #app.extra_foos = []
    
  • Create a UserOverrides class with an attr/method for each of the hooks; possibly split into RequiredOverrides and OptionalOverrides

  • Create an empty class with unimplemented methods that the user must subclass into a concrete instance
thegreatemu
  • 495
  • 2
  • 11
  • Inheritance is a crappy extension mechanism. Use functions that call a magic method on their arguments and have extension classes implement the appropriate dunder methods. – Jared Smith Nov 27 '17 at 20:23
  • @JaredSmith I'm afraid most of that was Greek to me. Care to expand that into an answer since the only current answer suggests the opposite? – thegreatemu Nov 27 '17 at 21:04
  • Done. Check out my answer below. – Jared Smith Nov 27 '17 at 21:22

2 Answers2

1

One method is by using abstract base classes (abc module). For example, you can define an ABC with abstract methods that must be overridden by child classes like this:

from abc import ABC


class MyClass(ABC):  # inherit from ABC

   def __init__(self):
      pass

    @abstractmethod
    def some_method(self, args):
        # must be overridden by child class
        pass

You would then implement a child class like:

class MyChild(MyClass):
    # uses parent's __init__ by default
    def some_method(self, args):
        # overrides the abstract method

You can specify what everything needs to do in the overridden methods with documentation. There are also decorators for abstract properties, class methods, and static methods. Attempting to instantiate an ABC that does not have all of its abstract methods/properties overridden will result in an error.

Engineero
  • 12,340
  • 5
  • 53
  • 75
  • 1
    Possibly a bit off-topic, but I've always wondered: what does ABC add compared to just defining your abstract methods in the base class to raise `NotImplementedError`? – thegreatemu Nov 28 '17 at 00:31
  • 1
    @thegreatemu: A subclass of an ABC cannot be instantiated without implementing the abstract methods of the ABC. Thus, an instance of a subclass of an ABC is guaranteed to have certain methods (no guarantees about the behaviour of those methods, though). ABCs also have virtual subclasses, including implicit ones, which make it so you don't need to explicitly subclass the ABC to implement it. – tomasz Aug 23 '21 at 20:21
1

Inheritance. Is. Bad.

This is especially true in Python, which gives you a nice precedent to avoid the issue. Consider the following code:

len({1,2,3}) # set with length 3
len([1,2,3]) # list with length 3
len((1,2,3)) # tuple with length 3

Which is cool and all for the built-in data structures, but what if you want to make your own data structure and have it work with Python's len? Simple:

class Duple(object):
    def __init__(self, fst, snd):
        super(Duple, self).__init__()
        self.fst = fst
        self.snd = snd

    def __len__():
        return 2

A Duple is a two-element (only) data structure (calling it with more or fewer arguments raises) and now works with len:

len(Duple(1,2)) # 2

Which is exactly how you should do this:

def foo(arg):
    return arg.__foo__()

Any class that wants to work with your foo function just implements the __foo__ magic method, which is how len works under the hood.

Jared Smith
  • 19,721
  • 5
  • 45
  • 83
  • 2 questions: (1) isn't defining new double-underscore methods considered a no-no? – thegreatemu Nov 27 '17 at 21:34
  • (2) I am probably missing something, but this seems to only partially answer my question. Rather than telling my users "Subclass this ABC and pass your concrete instance", I say "pass an object that implements these methods." But especially if some methods are allowed default values, having an overloadable base class seems like less work than creating objects from scratch. And it doesn't answer the question "how do I best communicate what methods are overloadable?" – thegreatemu Nov 27 '17 at 21:38
  • @thegreatemu First: compared to what? Inheritance? When there's a zillion and five articles about the evils of inheritance all over the internet? Second: see the answer to the first, rethink your design. Inheritance is inherently *fragile*. You may be able to get away with it in your case (in which case the other answer is *fine*) but Python gives you other tools. Or to turn your question around: why do you think Guido implemented `len` that way rather than having it simply be a method on those objects? – Jared Smith Nov 27 '17 at 22:57
  • well PEP 8 specifically says "`__double_leading_and_trailing_underscore__`: "magic" objects or attributes that live in user-controlled namespaces. E.g.` __init__`, `__import__` or `__file__`. Never invent such names; only use them as documented." https://www.python.org/dev/peps/pep-0008/#naming-conventions – thegreatemu Nov 28 '17 at 00:27
  • @thegreatemu [this answer](https://stackoverflow.com/a/19328146/3757232) to a related-but-not-duplicate question may shed some light. As for PEP 8, fine, don't use double underscores. But the same principle applies: use duck typing, not inheritance. – Jared Smith Nov 28 '17 at 15:36