5

This is a question about which of these methods would be considered as the most Pythonic. I'm not looking for personal opinions, but, instead, what is idiomatic. My background is not in Python, so this will help me.

I'm working on a Python 3 project which is extensible. The idea is similar to the factory pattern, except it is based on functions.

Essentially, users will be able to create a custom function (across packages and projects) which my tool can locate and dynamically invoke. It will also be able to use currying to pass arguments down (but that code is not included here)

My goal is for this to follow good-Pythonic practice. I'm torn between two strategies. And, since Python is not my expertise, I would like to know the pros/cons of the following practices:

  1. Use a decorator

    registered = {}
    
    def factoried(name):
        def __inner_factory_function(fn):
            registered[name] = fn
            return fn
        return __inner_factory_function
    
    
    def get_function(name):
        return registered[name]
    

    Then, the following function is automatically registered...

    @factoried('foo')
    def build_foo():
      print('hi')
    

    This seems reasonable, but does appear slightly magical to those who are not familiar with decorators.

  2. Force sub-classing of an abstract class and use __subclasses__()

    If subclasses are used, there's no need for registration. However, I feel like this forces classes to be defined when a full class may be unnecessary. Also, the use of .__subclasses__() under the hood could seem magical to consumers as well. However, even Java can be used to search for classes with annotations.

  3. Explicit registration

    Forget all of the above and force explicit registration. No decorators. No subclasses. Just something like this:

    def build_foo():
      # ...
    
    factory.register('foo', build_foo)
    
martineau
  • 119,623
  • 25
  • 170
  • 301
Ben Reed
  • 824
  • 1
  • 8
  • 26
  • The only standard practices promoted by the Python Foundation are PEP 8. And PEP 8 is only a guideline for "code comprising the standard library in the main Python distribution". And it hammers home the point that it's only a guideline, not a rigid recommendation. But, more to the point, it has very little related to higher-level "design-pattern" questions like this, and nothing related to your specific question. – abarnert Jun 28 '18 at 00:01
  • So, the only answer is that, as far as the PSF's "promoted standard practices" are concerned, it doesn't matter what you do. The only way to decide is based on subjective opinions—which may be guided by what's considered idiomatic by the larger Python community, but that won't be enshrined in any kind of objective document you can cite. – abarnert Jun 28 '18 at 00:02
  • I see, @abarnert. Thank you. That makes sense. Part of me wishes there was a recommendation beyond PEP 8, but I guess the flexibility also allows the community to flush out and develop preferred practices. -- It's hard decisions like this that people generally seem to dig roots in, and I was hoping to avoid bike-shedding. – Ben Reed Jun 28 '18 at 00:06
  • If you want some subjective comments: code written by the core devs, even the stdlib, does both of these (as well as other options, like requiring a specific base class or metaclass). For example, the reason `isinstance(iter([1]), collections.abc.Iterable)` is true is that `collections.abc` calls `Iterator.register(bytes_iterator)` (and the glossary even explains this "virtual subclassing", so it's not just some implementation detail hidden under the covers). – abarnert Jun 28 '18 at 00:11
  • 2
    But if your main reason not to go with decorators is that they "appear slightly magical to those who are not familiar with decorators", I don't think that's a very good reason unless your code is meant to be read mostly by rank novices or people who will only be working with Python sparingly. Decorators are an important feature of the language, and anyone working on a project that uses them will pick them up quickly. – abarnert Jun 28 '18 at 00:13
  • @abarnert makes sense. It's interesting to see both are used in stdlib. I think the decorator pattern is appealing here. Just wanted to make sure I'm not violating some bad practice. – Ben Reed Jun 28 '18 at 00:15
  • You can use `__new__()` to make subclasses auto-register themselves (which internally uses `__subclasses__()`). See my answer to [Improper use of `__new__` to generate classes?](https://stackoverflow.com/questions/28035685/improper-use-of-new-to-generate-classes). – martineau May 03 '19 at 16:31

1 Answers1

2

There is no answer to this question.

The only standard practices promoted by the Python Foundation are PEP 8.

PEP 8 has very little related to higher-level "design-pattern" questions like this, and, in particular, nothing related to your specific question.

And, even if it did, PEP 8 is explicitly only a guideline for "code comprising the standard library in the main Python distribution", and Guido has rejected suggestions to make it some kind of wide-ranging standard that should be enforced on every Python project.

Plus, it hammers home the point that it's only a guideline, not a rigid recommendation.


Of course there are subjective reasons to prefer one design over another.

Ideally, these subjective reasons will usually be driven by some community consensus on what's "idiomatic" or "pythonic". But that community consensus isn't written down anywhere as some objective source you can cite.

There may be arguments that appeal to The Zen of Python, but that itself is just Tim Peters' attempts to distill Guido's own subjective guidelines into a collection of pithy sound bites, not an objective source. (And anyone who takes a brief look at, e.g., the python-ideas list can see that both sides of almost any question can appeal to the Zen…)

abarnert
  • 354,177
  • 51
  • 601
  • 671