125

When you code in other languages, you will sometimes create a block scope, like this:

statement
...
statement
{
    statement
    ...
    statement
}
statement
...
statement

One purpose (of many) is to improve code readability: to show that certain statements form a logical unit or that certain local variables are used only in that block.

Is there an idiomatic way of doing the same thing in Python?

gsamaras
  • 71,951
  • 46
  • 188
  • 305
Johan Råde
  • 20,480
  • 21
  • 73
  • 110
  • 3
    `One purpose (of many) is to improve code readability` - Python code, written correctly (ie, following the [zen of python](http://www.python.org/dev/peps/pep-0020/)) would not need such garnish to be readable. In fact, it is one of the (many) things I like about Python. – Burhan Khalid Oct 08 '12 at 03:59
  • I have tried to play with `__exit__` and `with` statement, changing the `globals()` but I failed. – Ruggero Turra Aug 14 '15 at 11:03
  • 2
    it would be very useful to define variable lifetime, connected to the resource acquisition – Ruggero Turra May 20 '16 at 14:04
  • 37
    @BurhanKhalid: That is not true. The zen of Python does not prevent you from polluting a local scope with a temporary variable here and there. If you turn _every usage_ of a single temporary variable into e.g. defining a nested function which is called immediately, the zen of Python won't be happy either. Explicitly limiting the scope of a variable _is_ tool to improve readability, because it directly answers "are these identifiers used below?" -- a question that can arise reading even the most elegant Python code. – bluenote10 Jun 05 '16 at 09:19
  • 30
    @BurhanKhalid It's fine to not have a feature. But calling that "zen" is just disgusting. – Phil Nov 29 '17 at 19:26

7 Answers7

113

No, there is no language support for creating block scope.

The following constructs create scope:

  • module
  • class
  • function (incl. lambda)
  • generator expression
  • comprehensions (dict, set, list(in Python 3.x))
ThomasH
  • 22,276
  • 13
  • 61
  • 62
  • 2
    Despite the title and a literal part of the question, I think the question was really more about *grouping* than *scope* (limit/hide variables). An `if True` construct achieves the desired effect (but [Pylint](https://en.wikipedia.org/wiki/Pylint) will complain with *"W0125: Using a conditional statement with a constant value (using-constant-test)"*) – Peter Mortensen Jan 13 '21 at 00:06
  • 1
    @PeterMortensen I totally disagree. Neither do I think visual indentation was the true reason of the original question, nor do I think a `if True:` is a nice solution. Empty lines before and after the group while having no empty lines in it is better imo. You can add a line comment at the start. For anything more: why not creating a function? It forces you to create a one-word-comment for it (i.e. you need a function name) and brings scopes... – Robert Siemer Apr 27 '22 at 14:16
43

The idiomatic way in Python is to keep your functions short. If you think you need this, refactor your code! :)

Python creates a new scope for each module, class, function, generator expression, dict comprehension, set comprehension and in Python 3.x also for each list comprehension. Apart from these, there are no nested scopes inside of functions.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 14
    "The most important thing in programming is the ability to give something a name. The second most important thing is to not be required to give something a name." For the most part, Python requires that scopes (for variables, etc.) be given names. In this respect, Python variables the second most important test. – Krazy Glew May 21 '16 at 00:59
  • 26
    The most important things in programming is the ability to manage the dependencies of your application and manage scopes of code blocks. Anonymous blocks let you limit the lifetime of callbacks, whereas otherwise, your callbacks get only used once, but live for the duration of the program, this causes global scope clutter and harms readability of code. – Dmytro Oct 24 '16 at 23:55
  • I just noticed that variables are also local to dict/set comprehensions. I tried Python 2.7 and 3.3, but I'm not sure if it's version-dependent. – wjandrea Apr 30 '19 at 14:03
  • 1
    @wjandrea You are right – added to the list. There shouldn't be any difference between Python versions for these. – Sven Marnach Apr 30 '19 at 20:23
  • 5
    I would re-word the last sentence, as you can very well create functions within functions. So there are nested scopes inside functions. – ThomasH Aug 19 '19 at 10:57
  • But this is overkill if there is only one or two lines of code (and perhaps some comments) per block scope. – Peter Mortensen Jan 13 '21 at 00:12
26

You can do something similar to a C++ block scope in Python by declaring a function inside your function and then immediately calling it. For example:

def my_func():
    shared_variable = calculate_thing()

    def do_first_thing():
        ... = shared_variable
    do_first_thing()

    def do_second_thing():
        foo(shared_variable)
        ...
    do_second_thing()

If you're not sure why you might want to do this then this video might convince you.

The basic principle is to scope everything as tightly as possible without introducing any 'garbage' (extra types/functions) into a wider scope than is absolutely required - Nothing else wants to use the do_first_thing() method for example so it shouldn't be scoped outside the calling function.

Ben
  • 261
  • 3
  • 3
  • 1
    It's also the way that is being used by Google developers in TensorFlow tutorials, as seen for example [here](https://www.tensorflow.org/tutorials/keras/basic_regression#build_the_model) – Nino Filiu Apr 16 '19 at 13:12
  • @NinoFiliu I dont understand, why would you want to call the function immediately? – astralwolf Sep 19 '21 at 18:31
  • 1
    okay, that part of the linked video is on c++ and why it can be beneficial to evolve into sub-functions. as soon as you accept this didactic approach, you might probably best prepared for getting your benefit in catching the analogies for python. – Alexander Stohr Dec 16 '21 at 09:38
16

I agree that there is no block scope. But one place in Python 3 makes it seem as if it has block scope.

What happened which gave this look?

This was working properly in Python 2, but to make variable leakage stop in Python 3 they have done this trick and this change makes it look like as if it has block scope here.

Let me explain.


As per the idea of scope, when we introduce variables with same names inside the same scope, its value should be modified.

This is what is happening in Python 2:

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'W'

But in Python 3, even though the variable with same name is introduced, it does not override, and the list comprehension acts like a sandbox for some reason, and it seems like creating a new scope in it.

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'OLD'

And this answer goes against the answerer ThomasH's statement The only means to create scope is functions, classes or modules because this looks like one other place of creating a new scope.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Harish Kayarohanam
  • 3,886
  • 4
  • 31
  • 55
  • Comprehensions are at least *de facto* equivalent to functions; CPython generates an anonymous function to produce the list/set/dict. – chepner Jul 07 '22 at 18:44
9

I have come up with a solution with the easiest interface and the least amount of extra names to be introduced to your code.

from scoping import scoping
a = 2
with scoping():
    assert(2 == a)
    a = 3
    b = 4
    scoping.keep('b')
    assert(3 == a)
assert(2 == a)
assert(4 == b)

https://pypi.org/project/scoping/

l74d
  • 177
  • 1
  • 2
  • 1
    That looks like a very useful module. Thanks for this contribution to Python. Although this answer is basically a plug for your module, I'm still happy to upvote it. – joanis Jun 11 '21 at 13:20
  • This is just the best! I've been looking for something like this forever. I want something that allows me to make some local variables that don't pollute the main namespace. Furthermore, I think I'll be wrapping `for` loops inside these with statements so that the for loop variable doesn't pollute the namespace (which I've never liked). – mpettis Sep 19 '21 at 21:02
3

For completeness sake: You can end the scope of local variables using del. See also When is del useful in Python?. Itʼs certainly not idiomatic, though.

statement
statement

# Begin block
a = ...
b = ...
statement
statement
del a, b
# End block

statement
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
hfs
  • 2,433
  • 24
  • 37
2

Modules (and packages) are a great Pythonic way to divide your program into separate namespaces, which seems to be an implicit goal of this question. Indeed, as I was learning the basics of Python, I felt frustrated by the lack of a block scope feature. However once I understood Python modules, I could more elegantly realize my previous goals without the need for block scope.

As motivation, and to point people towards the right direction, I think it's useful to give provide explicit examples of some of Python's scoping constructs. First I explain my failed attempt at using Python classes to implement block scope. Next I explain how I achieved something more useful using Python modules. At the end I outline a practical application of packages to loading and filtering data.

Attempting block scope with classes

For a few moments I thought that I had achieved block scope by sticking code inside of a class declaration:

x = 5
class BlockScopeAttempt:
    x = 10
    print(x) # Output: 10
print(x) # Output: 5

Unfortunately this breaks down when a function is defined:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(x) 
    printx2() # Output: 5!!!

That’s because functions defined within a class use global scope. The easiest (though not the only) way to fix this is to explicitly specify the class:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(BlockScopeAttempt.x)  # Added class name
    printx2() # Output: 10

This is not so elegant because one must write functions differently depending on whether or not they’re contained in a class.

Better results with Python modules

Modules are very similar to static classes, but modules are much cleaner in my experience. To do the same with modules, I make a file called my_module.py in the current working directory with the following contents:

x = 10
print(x) # (A)

def printx():
    print(x) # (B)

def alter_x():
    global x
    x = 8

def do_nothing():
    # Here x is local to the function.
    x = 9

Then in my main file or interactive (e.g. Jupyter) session, I do

x = 5
from my_module import printx, do_nothing, alter_x  # Output: 10 from (A)
printx()  # Output: 10 from (B)
do_nothing()
printx()  # Output: 10
alter_x()
printx()  # Output: 8
print(x) # Output: 5
from my_module import x  # Copies current value from module
print(x) # Output: 8
x = 7
printx()  # Output: 8
import my_module
my_module.x = 6
printx()  # Output: 6

As explanation, each Python file defines a module which has its own global namespace. The import my_module command allows you to access the variables in this namespace with the . syntax. I think of modules like static classes.

If you are working with modules in an interactive session, you can execute these two lines at the beginning

%load_ext autoreload
%autoreload 2

and modules will be automatically reloaded when their corresponding files are modified.

Packages for loading and filtering data

The idea of packages is a slight extension of the modules concept. A package is a directory containing a (possibly blank) __init__.py file, which is executed upon import. Modules/packages within this directory can be accessed with the . syntax.

For data analysis, I often need to read a large data file and then interactively apply various filters. Reading a file takes several minutes, so I only want to do it once. Based on what I learned in school about object-oriented programming, I used to believe that one should write the code for filtering and loading as methods in a class. A major disadvantage of this approach is that if I then redefine my filters, the definition of my class changes, so I have to reload the entire class, including the data.

Nowadays with Python, I define a package called my_data which contains submodules named load and filter. Inside of filter.py I can do a relative import:

from .load import raw_data

If I modify filter.py, then autoreload will detect the changes. It doesn't reload load.py, so I don't need to reload my data. This way I can prototype my filtering code in a Jupyter notebook, wrap it as a function, and then cut-paste from my notebook directly into filter.py. Figuring this out revolutionized my workflow, and converted me from a skeptic to a believer in the “Zen of Python.”

Ben Mares
  • 1,764
  • 1
  • 15
  • 26