7.0
Local Scopes
7.1
Local Var Access & Scope Chain
As we learned is section 5, all variables are attributes of scope objects.
In section 6 we concentrated on the "self" scope object in
methods. We learned how attribute lookup in an object goes through a
chain of prototype links. In a "self" scope object this is
the whole story when looking up a "self" variable like "self.width"..
When
a variable has no prefix like the "self" keyword, such as
in a simple variable like "x", the variable uses the "local"
scope object. Attribute lookup in the "local" scope object
is not as simple as in the "self" scope object.
Attribute
lookup starts in the "local" scope object as usual, but when
it fails to find the attribute, instead of going down the prototype
chain, it goes down a different scope chain, the "local scope chain".
This is a chain of pointers to each of the surrounding "local"
scopes in the source file. This is easier to show in an example than
to describe in words:
# Prothon source file tut13.pr
a = 1 # this is outermost scope-1
with a:
b = 2 # This is second scope-2 inside scope-1
def func():
c = 3 # this is third scope-3 inside scope-2
print a, b, c
a.func() # this prints "1 2 3"
As
you can see, a nested block from a "object", "with",
or "def" statement block will create a new scope. Blocks from
any other statements such as "if", "for", etc. will
not create scopes. The way to remember this is that the "object",
"with", and "def" blocks change the scope of the
variables inside so they create new scope objects.
Inside func there is an unamed scope object that we will call "scope-3"
here. It has a pointer to "scope-2" and "scope-2"
has a pointer to "scope-1". These pointers to local scopes
make up the "local scope chain". When an attribute such as
"a" is refernced in func(), it will be looked up in "scope-1"
and not found, then looked up in "scope-2" and then looked
up in "scope-1" and finally found.
In
the language Python, which Prothon is heavily based on, "scope-1"
is called "global". Prothon has no such thing as global. Python's
"global" is just the last scope in the "local scope chain"
in Prothon.
What
happens when an attribute isn't found in scope-1? In that case the search
continues in the prototype chain of scope-1, which eventually ends up
at Object, just like the "self" search. This means that all
searches end up at Object. This is important because built-in functions
and methods like Len(str) and Cmp(a,b) are stored in Object.
7.2
Setting Local Vars & Outer Keyword
What
if you wanted to access the variable named "a" in scope-1,
but there was already a local variable named "a" in the immediate
"local" scope object? This problem of a name in a closer scope
blocking access to a more remote scope if called "shadowing".
We will show a solution to this in just a moment.
The
last section concerned read access only. In other words access on the
right side of assignment statements. If you noticed, in the tut13 example,
each of the variable's (a, b, or c) scope locations was determined by
where it was set on the left side on an assignment statement. If you
wanted to change the value of scope-1's variable "a" from
inside of func(), and you just used the statement "a = 9",
you would actually create a new variable "a" inside of scope-3
in func(), not change the value of "a" in scope-1. How do
you assign a value to the scope-1 variable "a"?
In
order to solve both the "shadowing" problem and the assignment
problem, Prothon has a special keyword named "outer" that
when prefixed to a variable name, tells the interpreter to skip over
the first local scope when looking for that attribute, and also to only
use existing attributes when assigning values, never create new attributes.
Here is an example of it's usage:
# Prothon source file tut14.pr
a = 1 # this is outermost scope-1
with a:
b = 2 # This is second scope-2 inside scope-1
def func():
c = 33 # this is third scope-3 inside scope-2
a = 11 # these shadow the outer a and b variables
b = 22
print a, b, c
print outer.a, outer.b # accessing outer variables
outer.a = 99 # modifying outer variables
a.func()
print a
This is the output:
11 22 33
1 2
99
Outer
can also be used to access the local scope objects of functions that
are not even running any more. This feature is called a "closure"
and requires no special syntax in Prothon. Accessing a closure variable
doesn't even require using the "outer" keyword.
# Prothon source file tut15.pr
def getFunc():
counter = 0
def func():
outer.counter += 1
print counter # outer not needed for access
return func
func = getFunc()
# func has closure with local scope object from getFunc
func() # prints 1
func() # prints 2
func() # prints 3
func() # prints 4
7.3
Caller Keyword
There
is one last keyword that relates to "local" scope objects.
This one is simple though. The "caller" keyword is a prefix
that, like "self", is only available in functions. It gives
you access to the "local" scope object of the code that called
the currently running function. There is no scope chain involved, so
like "self", if an attribute lookup fails it uses the prototype
chain for lookup.
There
is some debate about the usefulness of "caller". It provides
a simple macro-like access to variables in the calling code. It also
provides a (not very good) alternative to parameter passing.
>>> def func():
... print caller.a, caller.b
... caller.a = 11
... print caller.a, caller.b
...
>>> a = 1
1
>>> b = 2
2
>>> func()
1 2
11 2
>>>
|