2

I need a namespace within a module for many different static methods doing similar jobs. From my research I learnt that having a class full of static methods is considered anti-pattern in Python programming:

class StatisticsBundle:
  @staticmethod
  def do_statistics1(params):
     pass

  @staticmethod
  def do_statistics2(params):
     pass

If this isn't a good solution, what is the best practice instead that allows me to do a namespace lookup like getattr(SomeNameSpace, func_name) within the same module?

AcK
  • 2,063
  • 2
  • 20
  • 27
Jinghui Niu
  • 990
  • 11
  • 28
  • Concerning your *getattr(SomeNameSpace, func_name)* question look at this Stack Overflow question: [What is getattr() exactly and how do I use it?](https://stackoverflow.com/questions/4075190/what-is-getattr-exactly-and-how-do-i-use-it) – Life is complex May 01 '21 at 12:58
  • Why do you want to define the namespace within a given module? Why can't you simply import functions from another module (which would be the namespace then). Defining these "static" functions as part of another, dedicated module is the most Pythonic solution. – a_guest May 04 '21 at 11:00
  • @a_guest This is because the desired `StatisticsBundle` namespace are highly related to the domain module. I would like to have this as a sub-namespace rather than a standalone module in itself. – Jinghui Niu May 04 '21 at 21:23

3 Answers3

6

Use a package. Place the functions in a separate module, without using a class.

Within the statistics folder on your computer define 2 modules:

  1. helpers.py where you define the helper functions.
  2. __init__.py where you write the bulk of your code.

You may rename the helpers module, if you can come up with a better name for the group of functions you define within it. However, the __init__ module of a package is special. When the package is imported, the __init__ module is given the package name and evaluated.

To apply your example:

#statistics\helpers.py

def do_statistics1(params):
     pass

def do_statistics2(params):
     pass

# Rest of module omitted
#statistics\__init__.py
# Relative import
from . import helpers
# Get function using getattr()
do_statistics1 = getattr(helpers, "do_statistics1")
# Get function using dot notation
do_statistics2 = helpers.do_statistics2

# Rest of module omitted

Be sure to test the package by importing it. Relative imports do not work when evaluating a module within a package.

In conclusion, you can get attributes from a module just like you can from a class.

Elijah
  • 206
  • 1
  • 8
  • This is not satisfactory, because I want a namespace inside an existing module, not another module on the side. Good explanation though. – Jinghui Niu Apr 29 '21 at 23:37
  • 2
    A package is a module with sub-modules. The helpers.py is a sub-module of statistics/__init__.py. So the helpers.py file is "inside the existing statistics/__init__.py," so to speak. – Elijah Apr 30 '21 at 03:45
1

[...] what is the best practice instead that allows me to do a namespace lookup like getattr(SomeNameSpace, func_name) within the same module?

Python functions are first-class functions. Hence, the simplest namespace is a dict (which actually isn't far from how instance namespaces work on __dict__). If you want to implement a sort of factory function, it's just:

def _create_foo():
    return Foo(...)

def _create_bar():
    return Bar(...)


_my_ns = {
    'foo': _create_foo,
    'bar': _create_bar,
}

def my_factory(name):
    return _my_ns[name]()

Also in runtime (given how staticmethod descriptor works in Python 3) they will be of the same types.FunctionType.

>>> class ns:
...     
...     @staticmethod
...     def foo():
...         pass
... 
... type(ns.foo)
<class 'function'>
>>> type(_my_ns['foo'])
<class 'function'>
saaj
  • 23,253
  • 3
  • 104
  • 105
  • This is an interesting point. `my_factory` now just helps simplify getting out the needed function. Is there a way to further simplify putting in funcs into `_my_ns`? Any idea? – Jinghui Niu May 01 '21 at 16:23
  • Ideally each domain module will have a `_my_ns` namespace that contains a bundle of statistics funcs. The importing module in the run time would retrieve the needed statistics function from the domain module's `_my_ns` namespace. – Jinghui Niu May 01 '21 at 16:26
  • *Is there a way to further simplify putting in funcs into `_my_ns`?* Yes, just use a dedicated module and import it. Modules are also first-class citizens in Python. You can also provide `__all__` for the module to communicate your intent of its public API. There's technically an alternative with [`locals`](https://docs.python.org/3/library/functions.html#locals) but I don't recommend that, because it'll make the code fragile. – saaj May 01 '21 at 16:57
  • I've also noticed that I mixed "statics" with "statistics" in your `StatisticsBundle`, so I assume you didn't want a factory function. But still to make it clear, in my example everything that starts with underscore is internal API of the module, including `_my_ns`. – saaj May 01 '21 at 17:01
  • But if your intent is having a collection (namespace) of functions, say `my_stats`, for each "domain" module that's part of its public API and that the calling module will access them polymothically, I don't think it's a bad idea. Still if all the stat functions are defined at import time, regular module with `__all__` wound be preferred because the very purpose of a module is to be a collection (namespace) of functions (among other types of objects). – saaj May 01 '21 at 17:15
  • `But if your intent is having a collection (namespace) of functions, say my_stats, for each "domain" module that's part of its public API and that the calling module will access them polymothically, I don't think it's a bad idea. ` You mean, having a class with a bunch of static methods are not a bad idea in this case? – Jinghui Niu May 01 '21 at 18:43
  • I meant that each "domain" module has a module-level attribute `my_stats: Dict[str, Callable]` which is its public API and which the calling module accesses. At the same time there's nothing technically wrong with a class with only static methods, when your intent is a namespace for particular runtime polymorphic operation. But it has a cost. You'll have a class with descriptors instead of plain functions, which means that some decorator APIs that expect callables won't work. Repeated `@staticmethod` will create some visual noise. It's summarised in PEP 20 as *Simple is better than complex*. – saaj May 01 '21 at 19:08
1

Depending on your python version, you might be able to use a SimpleNamespace

import types

def foo():
    print("foo")

def bar():
    print("bar")

namespace = types.SimpleNamespace(foo=foo, bar=bar)
namespace.foo()
namespace.bar()

Myles Hollowed
  • 546
  • 4
  • 16
  • How does it improve from simply using a `dict` to hold each function? – Jinghui Niu May 04 '21 at 21:27
  • It's very similar. In the docs they even mention that the attribute order was changed in 3.9 to behave the same way as a dict. From the link that I posted, it sounds like the gain is just that it removes a small bit of boilerplate by giving you a couple dunder methods for free – Myles Hollowed May 04 '21 at 23:56