Field Calculator - Python - Global and Local scope

2661
13
Jump to solution
03-25-2021 01:38 PM
DavidPike
MVP Frequent Contributor

I recently came across something I found a bit confusing.

In field calculator if say I want to auto-increment a field, I can create a simple function to add 1 to a variable starting at 0, then return that value - but I have to set that variable as a global:

counter = 0
def increment():
    global counter
    counter += 1
    return counter

If however I want to keep appending to a list such as to find duplicate values - How To: Identify duplicate field values in ArcGIS 10.x (esri.com)

The list is retained in a global scope (I guess?)

uniqueList = []
def isDuplicate(inValue):
  if inValue in uniqueList:
    return 1
  else:
    uniqueList.append(inValue)
    return 0

My thinking was always that the pre-logic code block and function is executed in isolation on a per-row basis unless a global variable is set - which then (in my mind) isolates that variable from recreation each time.

Do I need to do some back-to-basics reading on local and global scope, or is this just an esri quirk?

I don't have a computer science background and am self-taught (as most probably are) and still learning every day, but would appreciate if someone could explain it, since it's bugging me.

 

Cheers.

0 Kudos
13 Replies
JoeBorgione
MVP Emeritus

Yes, indeed....

That should just about do it....
0 Kudos
DavidPike
MVP Frequent Contributor

what is this? 😂

0 Kudos
JoshuaBixby
MVP Esteemed Contributor

It has to do with lists being a mutable data type while strings, integers, floats, etc... are not mutable.  Python variables reference addresses in memory.  With CPython, the id() function returns an object's address in memory, which makes it convenient for illustrating this point about mutability.

Looking at two examples of immutable data types:

 

 

>>> # look at memory address change when changing integer variable value
>>> i = 5
>>> id(i)
140714082966800
>>> i = 6
>>> id(i)
140714082966832
>>> 
>>> # look at memory address change when changing string variable value
>>> s = "foobar"
>>> id(s)
1355804104048
>>> s = "hello world"
>>> id(s)
1355809971568
>>> 
>>> s += ","
>>> s
'hello world,'
>>> id(s)
1355804104048
>>>

 

 

As you can see, updating a variable that stores an immutable data type updates the memory address because a new object is created and the variable pointer changed to the location of that new object.

Now, look at an example of a mutable data type:

 

>>> l = [5]
>>> id(l)
1355802301384
>>> l[0] = 6
>>> id(l)
1355802301384
>>>
>>> l.append("foobar")
>>> l
[6, 'foobar']
>>> id(l)
1355802301384
>>>

 

As the list is updated and modified, the memory address for the entry point into that list remains the same even though the contents within the list change.  Mutable.

When a list is used as a parameter in a function, any modifications of it in the function are seen by outside/calling namespaces because the memory address for the list has not changed with the changes made within the function.

When an immutable data type is used as a parameter in a function, the local namespace of the function inherits the pointers to variable memory addresses and can use the values stored in those addresses.  When a function modifies the inherited variable, the function cannot modify the global namespace to update the pointer so only the local namespace knows of the new memory address to the updated value.

The global keyword tells the Python parser that the function can or is allowed to modify the outside/calling namespace for the variable with that keyword.  I guess you could look at it as the global keyword merges the global and local namespaces for the variable in question, so when the function updates the variable and changes the pointer to a new memory address, the calling namespace sees the new memory address and value.

DavidPike
MVP Frequent Contributor

Excellent, I can see what's going on with that ID function perfectly.  Still gives me a headache though.

 

Thanks all.

0 Kudos