scope of function parameters (take two)

Steven D'Aprano steve+comp.lang.python at pearwood.info
Tue May 31 00:35:35 EDT 2011


On Mon, 30 May 2011 20:28:34 -0400, Henry Olders wrote:

> I am trying to write python programs in a more-or-less functional
> programming mode, ie functions without side effects (except for print
> statements, which are very helpful for debugging). This is easiest when
> all variables declared in functions are local in scope 

They are.


> (it would also be
> nice if variables assigned within certain constructs such as for loops
> and list comprehensions were local to that construct, but I can live
> without  it).

for loop variables are local to the function, by design.

List comprehension variables leak outside the comprehension. That is an 
accident that is fixed in Python 3. Generator expression variables never 
leaked.


> It appears, from my reading of the python documentation, that a
> deliberate decision was made to have variables that are referenced but
> not assigned in a function, have a global scope.
[...]
> This suggests that the decision to make unassigned (ie "free" variables)
> have a global scope, was made somewhat arbitrarily to  prevent clutter.
> But I don't believe that the feared clutter would materialize.

Then you haven't understood the problem at all.


> My
> understanding is that when a variable is referenced, python first looks
> for it in the function's namespace, then the module's, and finally the
> built-ins. So why would it be necessary to declare references to
> built-ins as globals?

How else would the functions be found if they were ONLY treated as local?

Consider this code:

def spam():
    print a_name
    print len(a_name)

This includes two names: "a_name" and "len". They both follow the same 
lookup rules. Functions in Python are first-class values, just like ints, 
strings, or any other type.

You don't want spam() to see any global "a_name" without it being 
declared. But how then can it see "len"?

Don't think that Python could change the rules depending on whether 
you're calling a name or not. Consider this:

def spam():
    print a_name
    print a_name(len)


Do you expect the first lookup to fail, and the second to succeed?



> What I would like is that the variables which are included in the
> function definition's parameter list, would be always treated as local
> to that function (and of course, accessible to nested functions) but NOT
> global unless explicitly defined as global.

They are. Always have been, always will be.


> This would prevent the sort
> of problems that I encountered as described in my original post.

No it wouldn't. You are confused by what you are seeing, and interpreting 
it wrongly.

I believe that what you want is pass-by-value semantics, in the old-
school Pascal sense, where passing a variable to a function makes a copy 
of it. This is not possible in Python. As a design principle, Python 
never copies objects unless you as it to.


> I may
> be wrong here, but it seems that the interpreter/compiler should be able
> to deal with this, whether the parameter passing is by value, by
> reference, by object reference, or something else. 

Of course. You could create a language that supports any passing models 
you want. Pascal and VB support pass-by-value ("copy on pass") and pass-
by-reference. Algol supports pass-by-value and (I think) pass-by-name. 
There's no reason why you couldn't create a language to support pass-by-
value and pass-by-object. Whether this is a good idea is another story.

However, that language is not Python.


> If variables are not
> assigned (or bound) at compile time, but are included in the parameter
> list, then the binding can be made at runtime. 

Er, yes? That already happens.


> And I am NOT talking
> about variables that are only referenced in the body of a function
> definition; I am talking about parameters (or arguments) in the
> function's parameter list. 

You keep bring up the function parameter list as if that made a 
difference. It doesn't.

Perhaps this will enlighten you:

>>> alist = [1, 2, 3, 4]
>>> blist = alist
>>> blist[0] = -999
>>> alist
[-999, 2, 3, 4]


Passing alist to a function is no different to any other name binding. It 
doesn't make a copy of the list, it doesn't pass a reference to the name 
"alist", it passes the same object to a new scope.



> As I stated before, there is no need to
> include a global variable in a parameter list, and if you want to have
> an effect outside of the function, that's what the return statement is
> for.

Function parameters are never global. You are misinterpreting what you 
see if you think they are.


[...]
> If making python behave this way is impossible, then I will just have to
> live with it. But if it's a question of "we've always done it this way",
> or, " why change? I'm not bothered by it", then I will repeat my
> original question: Are there others who feel as I do that a function
> parameter should always be local to the function?

They are. You are misinterpreting what you see.




-- 
Steven



More information about the Python-list mailing list