python nested function's accessibility to outer variable [duplicate] - python

This question already has answers here:
Immutable vs Mutable types
(18 answers)
Closed 7 years ago.
I wonder why reference variable(dict, list) is accessible from nested function while simple object is not.
In [1]: a = 1
In [2]: b = [1]
In [3]: c = {"c" : 1}
In [4]:
In [4]: def f():
...: a = 2
...: b[0] = 2
...: c["c"] = 2
...:
In [5]: f()
In [6]:
In [6]: print a
1
In [7]: print b
[2]
In [8]: print c
{'c': 2}

Integer is immutable, that means when python execute
a=2 inside f()
It create a new local object. While list and dictionary is mutable, so it can be modified in place.
>>> a = 0
>>> id(a)
31367908
>>> ls = [1,2]
>>> id(ls)
50082760
>>> def f():
a = 2
print id(a)
ls[0] = 5
print id(ls)
>>> f()
31367884
50082760

With statement a = 2 you are creating new local variable with value 2. However when you modify b[0] or c['c'] you are mutating global variable. If you have to deal with mutable types in function (work with them as with local variables) you have to initialize appropriate variable before mutating it:
def f():
c = {}
b = []
c['c'] = 2
b.append(1)
See Variables and scope chapter with great description of how scope works in python and get more info about mutable and immutable types.

Related

List and generator comprehensions with class variabes in conditional statement [duplicate]

This question already has answers here:
Accessing class variables from a list comprehension in the class definition
(8 answers)
Closed 7 years ago.
Consider following code snippet:
class C(object):
a = 0
b = 1
seq = [1, 2, 4, 16, 17]
list_comp = [a if v%2 else b for v in seq]
gen_comp = (a if v%2 else b for v in seq)
Code above in interpreted fine. Printing object bound to class variables results in:
print C.list_comp # [0, 1, 1, 1, 0]
print C.gen_comp # <generator object <genexpr> at ...>
Sad part is - attempt to retrieve value from generator results in NameError:
next(C.gen_comp) # NameError: global name 'a' is not defined
Expected behavior should be similar to list comprehension - it should yield 5 values and raise StopIteration on each next next() call.
What makes a difference here? How names are resolved in each case and why discrepancy occures?
The issue is that generator expressions run in their own namespace , hence they do not have access to names in class scope (class variables like a or b).
This is given in PEP 227 -
Names in class scope are not accessible. Names are resolved in the innermost enclosing function scope. If a class definition occurs in a chain of nested scopes, the resolution process skips class definitions.
Hence you get the NameError when trying to access the class variable in the generator expression.
A way to workaround this would be to access the required values through the class like C.a or C.b . Since the expressions inside the generator expression are only executed when next() is called on it, we can be sure that C class would have been defined by then. Example -
>>> class C(object):
... a = 0
... b = 1
... seq = [1, 2, 4, 16, 17]
... list_comp = [a if v%2 else b for v in seq]
... gen_comp = (C.a if v%2 else C.b for v in seq)
...
>>> next(C.gen_comp)
0
>>> next(C.gen_comp)
1
>>> next(C.gen_comp)
1
>>> next(C.gen_comp)
1
>>> next(C.gen_comp)
0
Note, the same issue occurs for list comprehension in Python 3.x, since in Python 3.x , list comprehensions have their own scopes. See Accessing class variables from a list comprehension in the class definition for more details.

Python function with argument not changing a variable [duplicate]

This question already has answers here:
How do I pass a variable by reference?
(39 answers)
Passing an integer by reference in Python
(13 answers)
Closed 8 years ago.
How can I get this function:
def test(var1):
var1=5
a=0
test(a)
print(a)
To set variable a to equal 5.
You'll have to use a mutable variable, for example a list. This does something similar.
def test(var1):
var1[0]=5
a=[0]
test(a)
print(a[0])
You probably want to return the value:
def test(var1):
var1 = 5
return var1
a = 0
a = test(a)
print(a)
Yes..Brad is correct...But a small correction there. We should say it as mutable object. When you use a mutable object like List, Dictionary you will achieve this..
>>> def test(var1):
... var1.append(5)
...
>>> a=[]
>>> test(a)
>>> a
[5]
whereas, When you use immutable object like integer, string, tuple it wont change the value referenced by the object.
>>> def test(val1):
... val1+(1,)
...
>>> test(a)
>>> a
()

changing the value of var1 [duplicate]

This question already has an answer here:
Understanding mutability in Python [closed]
(1 answer)
Closed 9 years ago.
My question is more of an understanding question than a strict programming question.
I know that python variables are actually pointers, which means they don't actually store a value, but rather point to the place in memory where that value is stored.
What i can't figure out is how the following 2 cases differ:
>>> a = 3
>>> b = a
>>> a
3
>>> b
3
>>>b = 4
>>> b
4
>>> a
3
The new value assigned to 'b' does not change the value pointed to by 'a'. As oppose to:
>>> a = [1,2,3]
>>> b = a
>>> a
[1,2,3]
>>> b
[1,2,3]
>>> b.append(4)
>>> b
[1,2,3,4]
>>> a
[1,2,3,4]
The new value assigned to b changed the value pointed to by a
Calling b.append doesn't assign b to a new list. It still points to the same position in memory.
>>> b = [1,2,3]
>>> id(b)
36586568L
>>> b.append(4)
>>> id(b)
36586568L
Since it is the underlying data that changes, any other identifiers also pointing to that data will be affected.
This has been covered many times before. Short answer is that they aren't strictly pointers. They are more like labels. In your top case, b is re-labeled to 4 and thus changes. In your bottom case (with the array) b is not re-labeled, but only it's contents appended to. That's why arrays seem to act differently.
Python "variables" ("binding" would be a more appropriate term) are not pointers, they are key->value pairs in a namespace. How the namespace and lookup are implemented is, well, an implementation detail, but you can consider it's a hash table (Python's dict).
The new value assigned to b changed the value pointed to by a
Where do you see the assignement operator in b.append(4) ? You're not rebinding b, you are mutating it. It's still the same list object that is bound to both names a and b.

Pass list to function by value [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 5 months ago.
I want to pass a list into function by value.
By default, lists and other complex objects passed to function by reference.
Here is some desision:
def add_at_rank(ad, rank):
result_ = copy.copy(ad)
.. do something with result_
return result_
Can this be written shorter?
In other words, I wanna not to change ad.
You can use [:], but for list containing lists(or other mutable objects) you should go for copy.deepcopy():
lis[:] is equivalent to list(lis) or copy.copy(lis), and returns a shallow copy of the list.
In [33]: def func(lis):
print id(lis)
....:
In [34]: lis = [1,2,3]
In [35]: id(lis)
Out[35]: 158354604
In [36]: func(lis[:])
158065836
When to use deepcopy():
In [41]: lis = [range(3), list('abc')]
In [42]: id(lis)
Out[42]: 158066124
In [44]: lis1=lis[:]
In [45]: id(lis1)
Out[45]: 158499244 # different than lis, but the inner lists are still same
In [46]: [id(x) for x in lis1] = =[id(y) for y in lis]
Out[46]: True
In [47]: lis2 = copy.deepcopy(lis)
In [48]: [id(x) for x in lis2] == [id(y) for y in lis]
Out[48]: False
This might be an interesting use case for a decorator function. Something like this:
def pass_by_value(f):
def _f(*args, **kwargs):
args_copied = copy.deepcopy(args)
kwargs_copied = copy.deepcopy(kwargs)
return f(*args_copied, **kwargs_copied)
return _f
pass_by_value takes a function f as input and creates a new function _f that deep-copies all its parameters and then passes them to the original function f.
Usage:
#pass_by_value
def add_at_rank(ad, rank):
ad.append(4)
rank[3] = "bar"
print "inside function", ad, rank
a, r = [1,2,3], {1: "foo"}
add_at_rank(a, r)
print "outside function", a, r
Output:
"inside function [1, 2, 3, 4] {1: 'foo', 3: 'bar'}"
"outside function [1, 2, 3] {1: 'foo'}"
A shallow copy is usually good enough, and potentially mush faster than deep copy.
You can take advantage of this if the modifications you are making to result_ are not mutating the items/attributes it contains.
For a simple example if you have a chessboard
board = [[' ']*8 for x in range(8)]
You could make a shallow copy
board2 = copy.copy(board)
It's safe to append/insert/pop/delete/replace items from board2, but not the lists it contains. If you want to modify one of the contianed lists you must create a new list and replace the existing one
row = list(board2[2])
row[3] = 'K'
board2[2] = row
It's a little more work, but a lot more efficient in time and storage
In case of ad is list you can simple call your function as add_at_rank(ad + [], rank).
This will create NEW instance of list every time you call function, that value equivalented of ad.
>>>ad == ad + []
True
>>>ad is ad +[]
False
Pure pythonic :)

python: list changes when global edited

a = [1]
def do():
global a
b=a
print b
a[0] = 2
print b
do()
outputs:
1
2
I am pretty sure it has something to do with the fact that 'a' is a global list.
Could someone please explain to me why the variable b changes when the global changes. And how i could possibly stop it from happening?
an extension to the question:
how would you handle further nesting, such as:
a = []
b = []
def do():
global a, b
b.append(a[:])
print a, b
a[0][0] +=1
print a, b
a.append([1])
do()
In this line b=a you essentially create a reference b, which points to a. This in python does not create a new copy of the list, but just creates a new link to it.
If you want to create a copy of a then you need to do it explicitly. Using list comprehensions, you can do it in this way :
b = a[:]
This will create a copy of a which will be referenced by b. See it in action :
>>> a = [1]
>>> b = a #Same list
>>> a[0] = 2
>>> b
[2] #Problem you are experiencing
You can see for yourself whether they refer to the same object or not by :
>>> a is b
True
The true signifies that they refer to the same object.
>>> b = a[:] #Solution <<--------------
Doing the same test again :
>>> a is b
False
And problem solved. They now refer to different objects.
>>> b
[2]
>>> a[0] = 3 #a changed
>>> a
[3]
>>> b
[2] #No change here
When you assign b = a you are copying the reference to a list object that is held in a to b, so they point at the same list. The changes to the underlying object will be reflected by either reference.
If you want to create a copy of the list use
b = list(a)
or, a method that will work on most objects:
import copy
b = copy.copy(a)
I think you have a misunderstanding of python's variable model. This is the article I read that made it click for me (the sections "Other languages have variables" and "Python has names").

Categories

Resources