Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I define a scope anywhere in Python?

Tags:

python

scope

Sometimes I find that I have to use functions with long names such as os.path.abspath and os.path.dirname a lot in just a few lines of code. I don't think it's worth littering the global namespace with such functions, but it would be incredibly helpful to be able to define a scope around the lines where I need those functions. As an example, this would be perfect:

import os, sys

closure:
    abspath = os.path.abspath
    dirname = os.path.dirname

    # 15 lines of heavy usage of those functions

# Can't access abspath or dirname here

I'd love to know if this is doable somehow

like image 759
Hubro Avatar asked Nov 08 '11 05:11

Hubro


3 Answers

Python doesn't have a temporary namespace tool like let in Lisp or Scheme.

The usual technique in Python is to put names in the current namespace and then take them out when you're done with them. This technique is used heavily in the standard library:

abspath = os.path.abspath
dirname = os.path.dirname
# 15 lines of heavy usage of those functions
a = abspath(somepath)
d = dirname(somepath)
...
del abspath, dirname

An alternative technique to reduce typing effort is to shorten the recurring prefix:

>>> import math as m
>>> m.sin(x / 2.0) + m.sin(x * m.pi)

>>> p = os.path
...
>>> a = p.abspath(somepath)
>>> d = p.dirname(somepath)

Another technique commonly used in the standard library is to just not worry about contaminating the module namespace and just rely on __all__ to list which names you intend to make public. The effect of __all__ is discussed in the docs for the import statement.

Of course, you can also create your own namespace by storing the names in a dictionary (though this solution isn't common):

d = dict(abspath = os.path.abspath,
         dirname = os.path.dirname)
...
a = d['abspath'](somepath)
d = d['dirname'](somepath)

Lastly, you can put all the code in a function (which has its own local namespace), but this has a number of disadvantages:

  • the setup is awkward (an atypical and mysterious use of functions)
  • you need to declare as global any assignments you want to do that aren't temporary.
  • the code won't run until you call the function
 def temp():                        # disadvantage 1: awkward setup
    global a, d                     # disadvantage 2: global declarations
    abspath = os.path.abspath
    dirname = os.path.dirname
    # 15 lines of heavy usage of those functions
    a = abspath(somepath)
    d = dirname(somepath)
 temp()                             # disadvantage 3: invoking the code
like image 66
Raymond Hettinger Avatar answered Sep 28 '22 04:09

Raymond Hettinger


The short answer is "No.".

Python has three scopes. It has function scope, global (aka module) scope, and the builtin scope. You can declare no other scopes.

A class declaration looks sort of like a scope, but it isn't. It's basically shorthand for assigning a bunch of fields on an object. The functions in that class can't access those fields without going through the object they're defined on.

This sounds a bit more limiting than it is. In Python you can also nest function definitions. A nested function definition gets read-only access to the outer scope. This is 'dynamic'. The name does not have to be mentioned before the function is defined. Here is an example:

def joe(x):
    def bar():
        return y
    def baz(z):
        y = x + 20
        return x
    y = x+5
    return bar, baz

>>> a, b = joe(5)
>>> b(20)
5
>>> a()
10

So, you can sort of get this effect without sacrificing too much locality by defining a nested function that creates the values you need, uses them, and returns a result.

I remember, when learning Python, that getting used to the rather strange scoping rules was one of the more difficult parts. When nested functions were introduced, in my opinion, they made the scoping rules even stranger because of the read-only semantics for outer scopes and dynamic scoping of closures.

Apparently, in Python3 there is a way of 'importing' a variable from the enclosing scope using the nonlocal keyword (analogous to the global keyword) so you can use it in a read/write context:

def joe(x):
    def bar():
        return y
    def baz(z):
        nonlocal y
        y = x + 20
        return x
    y = x+5
    return bar, baz

>>> a, b = joe(5)
>>> b(20)
5
>>> a()
25

Otherwise, whenever Python sees a variable on the left-hand side of an = sign, it assumes you're creating a new local variable. The global and nonlocal keywords are a way of stating that you intend to modify a variable that's not in the scope of the function.

like image 24
Omnifarious Avatar answered Sep 28 '22 02:09

Omnifarious


This sort of does what you want, but you do have to repeat the names

try:
    abspath = os.path.abspath
    dirname = os.path.dirname
    # fifteen lines of code
finally:
    del abspath
    del dirname

This avoids pollution the namespace if there is an exception in a situation as below

try:
    ...
    try:
        abspath = os.path.abspath
        dirname = os.path.dirname
        # fifteen lines of code
    finally:
        del abspath
        del dirname

    ... # don't want abspath or dirname in scope here even if there was
    ... # an exception in the above block

except:
    ...
like image 21
John La Rooy Avatar answered Sep 28 '22 04:09

John La Rooy