Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Creating a local variable scope.

2 views
Skip to first unread message

Johan Grönqvist

unread,
Sep 11, 2009, 1:36:14 PM9/11/09
to pytho...@python.org
Hi All,

I find several places in my code where I would like to have a variable
scope that is smaller than the enclosing function/class/module definition.

One representative example would look like:

----------
spam = { ... }
eggs = { ... }

ham = (a[eggs], b[spam])
----------

The essence is that for readability, I want spam and eggs in separate
definitions, but for clarity, I would like to express the fact that they
are "local to the definition of ham", i.e., they are not used outside of
the definition of ham.

The language reference at
<http://docs.python.org/reference/executionmodel.html> says that "The
following are blocks: a module, a function body, and a class
definition." (all other cases seem to refer to dynamic execution using
eval() or similar). Python 3 and 2.6 seem to have identical scope rules.

In the other languages I have used I can either use braces (C and
descendants) or use let-bindings (SML, Haskell etc.) to form local scopes.

Are there suggestions or conventions to maximize readability for these
cases in python? (Execution time is not important in the cases I
currently consider.)


Regards

Johan

Neal Becker

unread,
Sep 11, 2009, 2:08:35 PM9/11/09
to pytho...@python.org
Johan Grönqvist wrote:

I'd like this also.

Patrick Sabin

unread,
Sep 11, 2009, 2:37:49 PM9/11/09
to pytho...@python.org
Johan Gr�nqvist schrieb:
> Regards
>
> Johan

I think it is not possible to realize something like braces in C or
let-bindings in python. But here are some Ideas to work around this problem:

1) If you define all this in the global namespace you could remove your
temporary variables afterwards, e.g.

spam = 1
ham = (spam,)
del globals()['spam']

This only works for the global namespace and not for the local! I
wouldn't recommend it.

2) create a method, which initializes ham

def make_ham():
spam = {...}
egg = {...}
return (egg, spam)

ham = make_ham()

This makes it easier to reuse ham in other situations and wouldn't
expose spam or egg.

3) Make a class for your ham data structure

If ham is so complex that you have to split it up, it may makes sense to
create a class for it. Of course this would need refactoring of the
code, but it's more readable and extensible than packing your data in
tuples, lists or dictionaries.

- Patrick

Ethan Furman

unread,
Sep 11, 2009, 3:42:49 PM9/11/09
to Patrick Sabin, pytho...@python.org
Patrick Sabin wrote:
> Johan Gr�nqvist schrieb:
>
>> Hi All,
>>
>> I find several places in my code where I would like to have a variable
>> scope that is smaller than the enclosing function/class/module
>> definition.
>>
>> One representative example would look like:
>>
>> ----------
>> spam = { ... }
>> eggs = { ... }
>>
>> ham = (a[eggs], b[spam])
>> ----------

[snip]

> 1) If you define all this in the global namespace you could remove your
> temporary variables afterwards, e.g.
>
> spam = 1
> ham = (spam,)
> del globals()['spam']

Why use globals()? You could just say

del spam

and be done with it.

~Ethan~

Terry Reedy

unread,
Sep 11, 2009, 4:29:26 PM9/11/09
to pytho...@python.org
Johan Grönqvist wrote:
> Hi All,
>
> I find several places in my code where I would like to have a variable
> scope that is smaller than the enclosing function/class/module definition.
>
> One representative example would look like:
>
> ----------
> spam = { ... }
> eggs = { ... }
>
> ham = (a[eggs], b[spam])
> ----------
>
> The essence is that for readability, I want spam and eggs in separate
> definitions, but for clarity, I would like to express the fact that they
> are "local to the definition of ham", i.e., they are not used outside of
> the definition of ham.

delete spam, eggs

rather effectively says that the reader can also forget the bindings.
Execution time is minimal since no object is gc'ed.

This works with function locals also:

>>> def f():
i=1; del i; print (i)


>>> f()
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
f()
File "<pyshell#5>", line 2, in f
i=1; del i; print (i)
UnboundLocalError: local variable 'i' referenced before assignment

However, since probably a majority of non-function bindings are rather
local, I do not see that this really improved readability.

Within a module intended to be imported, using _spam, _eggs would signal
that they are only inted for local use. Delete if you want to enforce that.

> The language reference at
> <http://docs.python.org/reference/executionmodel.html> says that "The
> following are blocks: a module, a function body, and a class
> definition." (all other cases seem to refer to dynamic execution using
> eval() or similar). Python 3 and 2.6 seem to have identical scope rules.

Not exactly. In 3.0, comprehensions define a new scope by virtue of
being implicit function bodies.

> In the other languages I have used I can either use braces (C and
> descendants) or use let-bindings (SML, Haskell etc.) to form local scopes.

I consider that simplicity of Python scoping to be a major feature; it
is one of the reasons I use it.

tjr


Carl Banks

unread,
Sep 11, 2009, 5:32:03 PM9/11/09
to
On Sep 11, 10:36 am, Johan Grönqvist <johan.gronqv...@gmail.com>
wrote:

> In the other languages I have used I can either use braces (C and
> descendants) or use let-bindings (SML, Haskell etc.) to form local scopes.

I wouldn't mind a let statement but I don't think the language really
suffers for the lack of it. I expect that "leaky scopes" are a really
minor source of bugs in practice**, especially with well-organized
code that results in small functions. The main loss is the
organization opportunity.

Having said that, I'll tell you a pretty spiffy way to do it, even
though it can't be regarded as anything other than a cute hack. I
don't recommend using it in practice.


First define a decorator:

def let(f): return f()


Then, apply this decorator to a nameless function to get a convenient
nested scope:

@let
def _():
a = 1
b = 2
print a,b


But there's more: you can define let bindings in the function
arguments:

@let
def _(a = 1, b = 2):
print a,b


And, as with LISP, the "let" "statement" can return a result which you
can bind to a local variable:

@let
def result(a = 1, b = 2):
return a + b

print result


Don't do this in real code, though. Just live with the limitation, or
define a nested function and call it explicitly without cute decorator
hacks.


Carl Banks


(**) There is one notable common bug that leaky scopes do cause, when
creating closures in a loop. However, that is an advanced usage.

Steven D'Aprano

unread,
Sep 11, 2009, 9:29:55 PM9/11/09
to
On Fri, 11 Sep 2009 19:36:14 +0200, Johan Grönqvist wrote:

> Hi All,
>
> I find several places in my code where I would like to have a variable
> scope that is smaller than the enclosing function/class/module
> definition.

...


> The essence is that for readability, I want spam and eggs in separate
> definitions, but for clarity, I would like to express the fact that they
> are "local to the definition of ham", i.e., they are not used outside of
> the definition of ham.

Personally, I don't think your use-case is the least bit convincing, and
I think that introducing a new scope would hurt readability and clarity
rather than help it, but if you really want this, there are a couple of
approaches:

(1) Use comments to give your intention.

spam = 'abc' # Used only in definition of ham.
eggs = 'def' # Likewise.


ham = (a[eggs], b[spam])


(2) Delete the local names afterwards.

spam = 'abc'
eggs = 'def'


ham = (a[eggs], b[spam])

del spam, eggs


(3) Create an inner function, then call that.

def outer(*args):
a = parrot()
b = spanish_inquistion()
def inner():
spam = 'abc'
eggs = 'def'
return a[eggs], b[spam]
ham = inner()
return do_something_with(ham)


(4) Create a "do nothing" context manager allowing you to visually indent
the block, but otherwise have no effect:

class do_nothing:
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
pass

ham = ()
with do_nothing() as imaginary_local_scope:
spam = 'abc'
eggs = 'def'
ham = a[eggs], b[spam]
del spam, eggs, imaginary_local_scope


I think the fourth is an abomination and I only mention it for completion.

My personal opinion is that if you really need a local scope inside a
function, the function is doing too much and should be split up.


--
Steven

Bearophile

unread,
Sep 11, 2009, 10:07:55 PM9/11/09
to
Steven D'Aprano:

> (3) Create an inner function, then call that.

Several people after someone gives this anwer.


> My personal opinion is that if you really need a local scope inside a function, the function is doing too much and should be split up.<

I agree. And a way to split a function is to define an inner function,
that's one of their main purposes. No need to add other things to the
language as the OP suggests.

Bye,
bearophile

Jorgen Grahn

unread,
Sep 12, 2009, 7:04:43 AM9/12/09
to
On Fri, 11 Sep 2009 19:07:55 -0700 (PDT), Bearophile <bearoph...@lycos.com> wrote:
...

> No need to add other things to the
> language as the OP suggests.

He didn't suggest that (although he did give examples from other
languages).

Personally ... yes, I sometimes feel like the OP, but usually if I
want that kind of subtle improvements, I am also willing to
restructure my code so the natural scopes become short enough.

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .

Ethan Furman

unread,
Sep 12, 2009, 11:02:35 AM9/12/09
to pytho...@python.org
Daniel Stutzbach wrote:
> On Fri, Sep 11, 2009 at 8:29 PM, Steven D'Aprano
> <st...@remove-this-cybersource.com.au
> <mailto:st...@remove-this-cybersource.com.au>> wrote:
>
> (4) Create a "do nothing" context manager allowing you to visually
> indent
> the block, but otherwise have no effect:
>
>
> "with" doesn't create a new scope.

That's probably why he called it as:

with do_nothing() as *imaginary*_local_scope:
...
del spam, eggs, imaginary_local_scope

and then as the last item deleted all the variables, except the one he
wanted to keep.

~Ethan~

mark...@gmail.com

unread,
Sep 18, 2009, 5:36:17 AM9/18/09
to
On Sep 11, 7:36 pm, Johan Grönqvist <johan.gronqv...@gmail.com> wrote:
> Hi All,
>
> I find several places in my code where I would like to have a variable
> scope that is smaller than the enclosing function/class/module definition.

This is one of the single major frustrations I have with Python and a
important source of bugs for me. Here is a typical situation

for i, j in visited:
a[i, j] = 1
for i in range(rows):
a[i, 0] = 1
for j in range(columns):
a[0, i] = 1

As you see the third loop has a bug (I am actually mixing two logics:
1) using i for rows and j for columns 2) using i for the first
iterator and j for the second). The result is a buggy code that is
tolerated by Python. In C++ or Perl I don't have this problem. I
wonder whether other people share this opinion and if we have ever had
PEPs trying to address that...

Marko

Neal Becker

unread,
Sep 18, 2009, 7:32:50 AM9/18/09
to pytho...@python.org
mark...@gmail.com wrote:

I agree. I wish there were a convenient way to switch this 'feature'
on/off. I believe the vast majority of the time I do not want variable
names leaking out into other scopes. OTOH, sometimes it's convenient.

Ethan Furman

unread,
Sep 18, 2009, 9:25:53 AM9/18/09
to pytho...@python.org
Neal Becker wrote:
> mark...@gmail.com wrote:
> I agree. I wish there were a convenient way to switch this 'feature'
> on/off. I believe the vast majority of the time I do not want variable
> names leaking out into other scopes. OTOH, sometimes it's convenient.
>

loop != scope

~Ethan~

Wolfgang Rohdewald

unread,
Sep 18, 2009, 9:42:05 AM9/18/09
to pytho...@python.org
On Friday 18 September 2009, Ethan Furman wrote:
> loop != scope

true for python but in some languages the loop
counter has a scope limited to the loop


--
Wolfgang

Johan Grönqvist

unread,
Sep 18, 2009, 9:55:47 AM9/18/09
to pytho...@python.org
Thanks for all replies.

First, two general comments, and then my conclusions:

I did not intend to ask for new features, but my interest was in writing
readable code that works in python 2.5, although I may upgrade to 2.6 soon.

Also, what I gave was intended as a minimal example, and not a complete
use case.


Summarizing the answers, it seems that I will try to follow three
suggestions:

1) In general, try to restructure the code into smaller modules and
smaller functions.

2) When using many local variables bindings to create one larger object,
define an inner function that hides those bindings.

3) If I define a few values intended to be used very locally, delete
those after use.

Thanks again,

Johan

Gabriel Genellina

unread,
Sep 18, 2009, 4:31:24 PM9/18/09
to pytho...@python.org
En Fri, 18 Sep 2009 10:55:47 -0300, Johan Gr�nqvist
<johan.g...@gmail.com> escribi�:

> Summarizing the answers, it seems that I will try to follow three
> suggestions:
>
> 1) In general, try to restructure the code into smaller modules and
> smaller functions.

That's a good thing - "manageable" modules and functions.

> 2) When using many local variables bindings to create one larger object,
> define an inner function that hides those bindings.

I'd say, not to *hide* those bindings, but because the code fragment
deserves logically to become a function.

> 3) If I define a few values intended to be used very locally, delete
> those after use.

Why bother? Unless they're big objects and you want to ensure they get
deleted as soon as possible.

--
Gabriel Genellina

Sean DiZazzo

unread,
Sep 18, 2009, 6:45:45 PM9/18/09
to
On Sep 11, 10:36 am, Johan Grönqvist <johan.gronqv...@gmail.com>
wrote:

I would do something like this:

>>> class Namespace(object):
... pass
...
>>> n = Namespace()
>>> n.f = 2
>>> n.g = 4
>>> print f


Traceback (most recent call last):

File "<stdin>", line 1, in ?
NameError: name 'f' is not defined
>>> print n.f
2

~Sean

Johan Grönqvist

unread,
Sep 19, 2009, 3:48:42 AM9/19/09
to pytho...@python.org
Gabriel Genellina skrev:

> En Fri, 18 Sep 2009 10:55:47 -0300, Johan Gr�nqvist
> <johan.g...@gmail.com> escribi�:
>
>> Summarizing the answers, it seems that I will try to follow three
>> suggestions:
>>
>> 3) If I define a few values intended to be used very locally, delete
>> those after use.
>
> Why bother? Unless they're big objects and you want to ensure they get
> deleted as soon as possible.

To ease debugging.

Perhaps the problem only appears because I use longer functions than
recommended for python, but I have functions containing 2 to 4 loops,
with several if-clause each, where the different parts use very similar
variable names, like x, y, z, dr, dr_2, dr_3 etc.

None of the code fragments uses all of the names, but every fragment
uses some of them. I have had typos that incorrectly reused values from
a previous fragment. In those cases, it would have been much easier to
debug a raised exception due to using an undefined name, than to debug a
slightly incorrect result due to some if-clause of some loop computing
with an incorrect value.


Regards


Johan

Johan Grönqvist

unread,
Sep 19, 2009, 3:51:32 AM9/19/09
to pytho...@python.org
Sean DiZazzo skrev:

> I would do something like this:
>
>>>> class Namespace(object):
> ... pass
> ...
>>>> n = Namespace()
>>>> n.f = 2
>>>> n.g = 4
>>>> print f
> Traceback (most recent call last):
> File "<stdin>", line 1, in ?
> NameError: name 'f' is not defined
>>>> print n.f
> 2

I like this solution. This also minimizes the extra code if I would want
to explicitly delete the bindings, as I would only need one line to
delete the Namespace object.

Thanks!

Johan

Dave Angel

unread,
Sep 19, 2009, 9:05:14 AM9/19/09
to Johan Grönqvist, pytho...@python.org
Johan Gr�nqvist wrote:
> <div class="moz-text-flowed" style="font-family: -moz-fixed">Sean
> DiZazzo skrev:

>> I would do something like this:
>>
>>>>> class Namespace(object):
>> ... pass
>> ...
>>>>> n = Namespace()
>>>>> n.f = 2
>>>>> n.g = 4
>>>>> print f
>> Traceback (most recent call last):
>> File "<stdin>", line 1, in ?
>> NameError: name 'f' is not defined
>>>>> print n.f
>> 2
>
> I like this solution. This also minimizes the extra code if I would
> want to explicitly delete the bindings, as I would only need one line
> to delete the Namespace object.
>
> Thanks!
>
> Johan
>
>
> </div>
>
Even simpler solution for most cases, use longer names. If the name
means something in the local context, and the next context is different,
you presumably will use a deliberately different name. In your earlier
example, if you called them row and column, you ought to notice if you
used row instead of column in the later "scope".

One thing people don't notice when they ask the compiler to catch all
these types of problems is that there are lots of things the compiler
can't check. In Python if you delete a previous attribute, you'll get
an error when trying to read that attribute, but not when trying to
write it. Because as soon as you write it, you're declaring it again.

I spent years in C++ and Java environments, as well as many other
languages that enforced some of these rules. But once you get used to
the notion that the system won't check you, you're less likely to fall
into the traps that always remain in those other languages -- write your
own code defensively. And that means that for anything bigger than
throw-away samples, use real names for things.,

I spent time years ago in Forth, where a name could be almost anything
(no embedded spaces), and where syntax in the language was almost
non-existent, and you could define first class language constructs
inline, no sweat. It really freed the mind to think about the problem,
instead of the language and its idiosyncracies.

DaveA

Ethan Furman

unread,
Sep 19, 2009, 5:11:50 PM9/19/09
to pytho...@python.org
Dave Angel wrote:
> Johan Gr�nqvist wrote:
>> DiZazzo skrev:

>>
>>> I would do something like this:
>>>
>>>>>> class Namespace(object):
>>>
>>> ... pass
>>> ...
>>>
>>>>>> n = Namespace()
>>>>>> n.f = 2
>>>>>> n.g = 4
>>>>>> print f
>>>
>>> Traceback (most recent call last):
>>> File "<stdin>", line 1, in ?
>>> NameError: name 'f' is not defined
>>>
>>>>>> print n.f
>>>
>>> 2
>>
>>
>> I like this solution. This also minimizes the extra code if I would
>> want to explicitly delete the bindings, as I would only need one line
>> to delete the Namespace object.
>>
> Even simpler solution for most cases, use longer names. If the name
> means something in the local context, and the next context is different,
> you presumably will use a deliberately different name. In your earlier
> example, if you called them row and column, you ought to notice if you
> used row instead of column in the later "scope".
>
> One thing people don't notice when they ask the compiler to catch all
> these types of problems is that there are lots of things the compiler
> can't check.

Well said. One of the things I *love* about Python is that it doesn't
try to do my programming for me. For the mistakes we inevitably make,
good test suites are incredibly useful.

Still, and just for fun, the following is at least mildly entertaining
(but then, I am easily amused :)

class micro_scope(object):
def __enter__(self):
return self


def __exit__(self, type, value, traceback):

if type is value is traceback is None:
self.__dict__.clear()

with micro_scope() as m:
m.this = 'that'
m.value = 89
for m.i in range(10):
do_something_with(m.i)

m.value
#exception raised here, as m.value no longer exists

Don't worry, Steven, I'll never actually use that! ;-)

~Ethan~

Albert van der Horst

unread,
Sep 24, 2009, 7:33:46 AM9/24/09
to
In article <65e8a017-abcb-49ad...@s39g2000yqj.googlegroups.com>,

There are exceptions. E.g. implementations of quicksort have a
recursive inner function, that you may prefer to not have exposed.
Also there may be data to communicate to or between instances of
the inner function.

At least that is the situation in most languages. I would be
interested to learn if and how Python gets around that.

>
>Bye,
>bearophile

Groetjes Albert

--
--
Albert van der Horst, UTRECHT,THE NETHERLANDS
Economic growth -- being exponential -- ultimately falters.
albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

0 new messages