Ad-hoc scoping to organize code

People really like to organize their code into self-contained logical blocks. If you subscribe to Clean Code, you’ll use functions to achieve something like this:

def my_task(data):
    section_a = part_a(data)
    section_b = part_b(data, section_a)
    section_c = part_c(section_b)
    result = some_more_computation(data, section_c)
    return result

Pros of doing this over having the code of all called functions in my_task directly:

  • you can assign blocks of code a meaningful name
  • you cleanly separate code blocks from each other
  • you can re-use variable names in different functions without causing unexpected behavior

Cons:

  • you have to jump around in the file if you actually want to read all of the code, making it less readable
  • you create a bunch of functions on the top-level, polluting it for anyone inspecting the module, or trying to import from it
  • you’re abusing functions to effectively just create a scope, which is something that functions definitely do, but more as a detail than its purpose

I’m only listing points here that relate to readability and organization. Enabling code re-usage or creating testable units are very good reasons to turn blocks of code into functions, but those cases are explicitly not what I’m trying to address here.


I’d also like to organize my code, optimally keeping all the pros and avoiding all the cons I’ve listed. A way that I feel would allow me to express this intent in a clear manner would be something very similar to SimpleNamespace from the typing module, but without using the class-keyword.

That might sound like a minor thing, but as a matter of fact, SimpleNamespace is used very rarely. I’ve honestly never seen it used in “real” code during my whole career, and I think it’s because, again, we’d be abusing something with a very specific and different purpose. And classes in particular can have wild side-effects with respect to the code defined in its body, so it’s only natural to shy away from it, even if it probably works just fine most of the time.


What I had in mind:

>>> scope foo:
...     bar = "bar"
... 
>>> foo.bar
bar
>>> bar
NameError: name 'bar' is not defined

I don’t care that much about the details, maybe namespace is a better keyword, or maybe it would be nice to go just foo:\n. Maybe something like export bar to leak a name into the surrounding scope would be better than foo.bar-like access, who knows. I do think indenting the scoped code makes a lot of sense though.

As your link argues, can you not do this very much with class scope already, since it behaves as you want? For example, creating a short decorator

from types import SimpleNamespace

NAMESPACE_IGNORE = "__dict__", "__weakref__"

def namespace(cls):
    data = dict(cls.__dict__)
    for key in NAMESPACE_IGNORE:
        data.pop(key)
    return SimpleNamespace(**data)

you would be able to write

@namespace
class foo:
    x = 5
    y = 10
    def f(): pass

and have the resulting foo be the SimpleNamespace you want,

namespace(__doc__=None, __module__='__main__', f=<function foo.f>, x=5, y=10)

Even “exporting” variables using global and nonlocal works as expected (of course, it’s a class definition). The only caveat is the often unintuitive scoping of variables in functions within the namespace.

PS: It would be super easy to implement almost all of these small, syntactic, quality-of-life ideas (e.g. this here, record types, etc.) with PEP 638.

3 Likes

I didn’t know about PEP 638, thanks for pointing that out. I got to say I agree very much with the reasoning it provides on new features, so I guess I’d prefer if that PEP were accepted instead of adding a new keyword to python itself.

That being said,

can you not do this very much with class scope already, since it behaves as you want

From a purely technical point of view, yes. But what I want is to have a way to clearly express an intention. I don’t want to create a class, so I don’t want to use the class keyword.

The only caveat is the often unintuitive scoping of variables in functions within the namespace.

For me, the common-sense spec of a scope would be “code within a scope (or namespace) should run as if it weren’t in the scope, the scope only affects the visibility/access of names defined within the scope”. That’s something which isn’t possible if the code is within a class-definition, code runs different in there, as you have pointed out:

def fib(q):
    if q == 0:
        return 0
    if q in (1, 2):
        return 1
    return fib(q-1) + fib(q-2)
print(fib(10))  # 55

@namespace  # copied from your post
class math_funcs:
    def fib(q):
        if q == 0:
            return 0
        if q in (1, 2):
            return 1
        return fib(q-1) + fib(q-2)
print(math_funcs.fib(10))  # whoops, NameError: name 'fib' is not defined

Bottom line, I’d love for PEP 638 to be accepted in order to implement something like this for my projects.

This is somewhat similar to what was proposed in PEP 359 – The “make” Statement.

Specifically, the given example

scope foo:
    bar = "bar"

would do the same essentially the same thing [1] as PEP 359’s make namespace:

make namespace foo:
    bar = "bar"

  1. Though the same caveat about the scoping of variable in functions within the namespace would likely apply. ↩︎