Creating a local variable scope.

J

Johan Grönqvist

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
 
C

Carl Banks

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.
 
S

Steven D'Aprano

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.
 
B

Bearophile

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
 
J

Jorgen Grahn

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
 
E

Ethan Furman

Daniel said:
On Fri, Sep 11, 2009 at 8:29 PM, Steven D'Aprano
<[email protected]

(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~
 
M

markolopa

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
 
N

Neal Becker

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

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.
 
E

Ethan Furman

Neal said:
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


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~
 
S

Sean DiZazzo

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

I would do something like this:
.... pass
....Traceback (most recent call last):
2

~Sean
 
J

Johan Grönqvist

Sean DiZazzo skrev:
I would do something like this:

... pass
...
Traceback (most recent call last):

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
 
D

Dave Angel

Johan said:
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
 
E

Ethan Furman

Dave said:
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~
 
A

Albert van der Horst

Steven D'Aprano:


Several people after someone gives this anwer.



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.

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,731
Messages
2,569,432
Members
44,832
Latest member
GlennSmall

Latest Threads

Top