what's the difference between assign and bound when talking about variable? - python

Except from http://www.stavros.io/tutorials/python/
# This swaps the variables in one line(!).
# It doesn't violate strong typing because values aren't
# actually being assigned, but new objects are bound to
# the old names.
>>> myvar, mystring = mystring, myvar
I don't understand the point he is making.

He means to say the two variables are essentially swapped without knowing their types or explicitly using an intermediate variable as you normally would. A weakly-typed swap looks like this:
temp = A
A = B
B = temp
A previously-unitialized temporary variable temp must be created in order to perform the swap. However, because no type is specified when temp is first created, it violates strong typing. Here is a strongly-typed swap:
int temp = A
A = B
B = temp
A swap like A, B = B, A does not violate strong typing because an intermediate variable doesn't need to be explicitly defined with or without a type. It's simply an assignment operation, and a basic assignment operation is always ambiguously typed (aka: A = B is the same regardless of whether you are using a strong-typed language or a weak-typed one).

An assignment like a=1 , conceptually Python will perform three distinct steps to carry out the request.
1.Create an object to represent the value 1
2.Create the variable a.
3.Link(or bound as in the link) the variable a to new object 1.
In your case, the statement
myvar, mystring = mystring, myvar
will change the variable-object bound relationship.

Related

How to re-assign a variable in python without changing its id?

When assigning a variable to another, they point to the same object. So, how do I change the value for one of them with the variables still pointing to the same object?
a = 10
b = a
a -= 1
print(b) #expect to print 9 but it print 10
How to re-assign a variable in python without changing its id?
I'm not sure whether you're confused about variables in Python, or about immutable values. So I'm going to explain both, and half the answer will probably seem like "no duh, I already knew that", but the other half should be useful.
In Python—unlike, say, C—a variable is not a location where values live. It's just a name. The values live wherever they want to.1 So, when you do this:
a = 10
b = a
You're not making b into a reference to a. That idea doesn't even make sense in Python. You're making a into a name for 10, and then making b into another name for 10. And if you later do this:
a = 11
… you've made a into a name for 11, but this has no effect on b—it's still just a name for 10.
This also means that id(a) is not giving you the ID of the variable a, because there is no such thing. a is just a name that gets looked up in some namespace (e.g., a module's globals dict). It's the value, 11 (or, if you ran it earlier, the different value 10) that has an ID. (While we're at it: it's also values, not variables, that are typed. Not relevant here, but worth knowing.)
Things get a bit tricky when it comes to mutability. For example:
a = [1, 2, 3]
b = a
This still makes a and b both names for a list.
a[0] = 0
This doesn't assign to a, so a and b are still names for the same list. It does assign to a[0], which is part of that list. So, the list that a and b both name now holds [0, 2, 3].
a.extend([4, 5])
This obviously does the same thing: a and b now name the list [0, 2, 3, 4, 5].
Here's where things get confusing:
a += [6]
Is it an assignment that rebinds a, or is it just mutating the value that a is a name for? In fact, it's both. What this means, under the covers, is:
a = a.__iadd__([6])
… or, roughly:
_tmp = a
_tmp.extend([6])
a = _tmp
So, we are assigning to a, but we're assigning the same value back to it that it already named. And meanwhile, we're also mutating that value, which is still the value that b names.
So now:
a = 10
b = 10
a += 1
You probably can guess that the last line does something like this:
a = a.__iadd__(1)
That's not quite true, because a doesn't define an __iadd__ method, so it falls back to this:
a = a.__add__(1)
But that's not the important bit.2 The important bit is that, because integers, unlike lists, are immutable. You can't turn the number 10 into the number 11 the way you could in INTERCAL or (sort of) Fortran or that weird dream you had where you were the weirdest X-Man. And there's no "variable holding the number 10" that you can set to 11, because this isn't C++. So, this has to return a new value, the value 11.
So, a becomes a name for that new 11. Meanwhile, b is still a name for 10. It's just like the first example.
But, after all this telling you how impossible it is to do what you want, I'm going tell you how easy it is to do what you want.
Remember earlier, when I mentioned that you can mutate a list, and all the names for that list will see the new value? So, what if you did this:
a = [10]
b = a
a[0] += 1
Now b[0] is going to be 11.
Or you can create a class:
class Num:
pass
a = Num()
a.num = 10
b = a
a.num += 1
Now, b.num is 11.
Or you can even create a class that implements __add__ and __iadd__ and all the other numeric methods, so it can hold numbers (almost) transparently, but do so mutably.
class Num:
def __init__(self, num):
self.num = num
def __repr__(self):
return f'{type(self).__name__}({self.num})'
def __str__(self):
return str(self.num)
def __add__(self, other):
return type(self)(self.num + other)
def __radd__(self, other):
return type(self)(other + self.num)
def __iadd__(self, other):
self.num += other
return self
# etc.
And now:
a = Num(10)
b = a
a += 1
And b is a name for the same Num(11) as a.
If you really want to do this, though, you should consider making something specific like Integer rather than a generic Num that holds anything that acts like a number, and using the appropriate ABC in the numbers module to verify that you covered all the key methods, to get free implementations for lots of optional methods, and to be able to pass isinstance type checks. (And probably call num.__int__ in its constructor the way int does, or at least special-case isinstance(num, Integer) so you don't end up with a reference to a reference to a reference… unless that's what you want.)
1. Well, they live wherever the interpreter wants them to live, like Romanians under Ceaușescu. But if you're a builtin/extension type written in C and a paid-up member of the Party, you could override __new__ with a constructor that doesn't rely on super to allocate, but otherwise you have no choice.
2. But it's not completely unimportant. By convention (and of course in all builtin and stdlib types follow the convention), __add__ doesn't mutate, __iadd__ does. So, mutable types like list define both, meaning they get in-place behavior for a += b but copying behavior for a + b, while immutable types like tuple and int define only __add__, so they get copying behavior for both. Python doesn't force you to do things this way, but your type would be very strange if it didn't pick one of those two. If you're familiar with C++, it's the same—you usually implement operator+= by mutating in-place and returning a reference to this, and operator+ by copying and then returning += on the copy, but the language doesn't force you to, it's just confusing if you don't.
Variables don't have id, an id belongs to an object.
Integers e.g.: 10,9 are separate and immutable objects, 'a','b' variables are references only. id(a) really gives the id of the referenced object. The object '10' can not be mutated in place.
id(10), id(9)
Out[16]: (10943296, 10943264)
In [17]: a,b=10,10
In [18]: id(a),id(b)
Out[18]: (10943296, 10943296)
In [19]: b=a-1
In [20]: id(b)==id(9)
Out[20]: True
However, lists are mutable objects and can be changed in place with the [:] operator (and of course, with their methods,e.g.: append,extend):
l=[10,5]
In [29]: id(l),id(l[0]),id(l[1])
Out[29]: (139638954619720, 10943296, 10943136)
In [30]: id(l[0])==id(10)
Out[30]: True
In [31]: l=[10,5] # a new list object created, though the content is the same
In [32]: id(l)
Out[32]: 139638920490248
In [33]: l[:]=["a","b"] # in place changes
In [34]: id(l)
Out[34]: 139638920490248

Why is python treating lists like this when defining a function?

I was making a code, and variables started to behave strangely and get assigned to things which I thought they shouldn't. So, I decided to reduce the situation to minimal complexity in order to solve my doubts, and this is what happened:
The following code:
a = [2]
def changeA(c):
d = c
d[0] = 10
return True
changeA(a)
print(a)
prints '[10]'. This doesn't make sense to me, since I never assigned the list "a" to be anything after the first assignment. Inside the function changeA, the local variable d is assigned to be the input of the function, and it seems to me that this assignment is happening both ways, and even changing the "outside". If so, why? If not, why is this happening?
I've also noticed that the code
a = [2]
def changeA(c):
d = list(c)
d[0] = 10
return True
changeA(a)
print(a)
behaves normally (i.e., as I would expect).
EDIT: This question is being considered a duplicate of this one. I don't think this is true, since it is also relevant here that the locality character of procedures inside a function is being violated.
Python variables are references to objects, and some objects are mutable. Numbers are not, neither are strings nor tuples, but lists, sets and dicts are.
Let us look at the following Python code
a = [2] # ok a is a reference to a mutable list
b = a # b is a reference to the exact same list
b[0] = 12 # changes the value of first element of the unique list
print(a) # will display [12]
In the first example, you simply pass the reference of c(which is a) to d.
So whatever you do to d will happen on a.
In the second example, you copy the value of c(which is a) and give it to a new variable d.
So the d now has the same value as c(which is a) but different reference.
Note: you can see the reference or id of a variable using the id() function.
a = [2]
print id(a)
def changeA(c):
d = c
pirnt id(d)
d[0] = 10
return True
changeA(a)
print(a)
a = [2]
print id(a)
def changeA(c):
d = list(c)
print id(d)
d[0] = 10
return True
changeA(a)
print(a)
Its because when you do:
d = list(c)
that creates a new object. But when you do
d = c
You are making a reference to that object.
if you did
d.append(5)
to the first example you would get
[10,5]
Same operation to the second one and the list isn't modified.
Deeper explanation in the following link: http://henry.precheur.org/python/copy_list
In Python, names refer to values, so you have 2 names pointing to the same value.
In version 1 you create a list a, pass it in to the function under the pseudonym c, create another pseudonym d for the very same list, and then change the first value in that list. a, c, and d all refer to the same list.
In version 2, you're using list(c) which, to Python, means "take the contents of this iterable thing named c and make a new, different, list from it named d". Now, there are two copies of your list floating around. One is referred to as a or c, and the other is d. Therefore, when you update d[0] you're operating a second copy of the list. a remains unchanged.
'd=c' means reference copy as stated before. What it means, is that now d will reference the same object as c. As you are doing a direct manipulation on the referenced object, the value of the object a was referencing to is changed as well.
When you do 'd = list(c)' what it means that a new list object is created, with the baked of c. However, d is not referencing the same object as a anymore. Hence, the changes within the function doesn't impact a.

Changing the variable given in a function statement

My code so far is:
def errorCheckInt(n):
try:
n = int(n)
checkValue1 = True
except:
checkValue1 = False
It is supposed to take a variable input (which is n) and change it to an integer if possible. It will try to get a different input if it is a string.
However, it hasn't worked, and I think it's because you can't change the variable in the code.
This may not be very clear, so I'm giving an example:
testingNum = "2"
# def errorCheckInt here
errorCheckInt(testingNum)
# Should change testingNum to integer value 2, not string "2"
This would be followed by a while statement, checking whether it was successful (whether checkValue1 is True or False) then possibly asking for a new input and error checking that (depending on the input the user gives).
If this still isn't very clear, just comment a question and I may be able to tell you what I had in mind.
Thanks in advance!
It isn't clear to me what you mean when you say "you can't change the variable in the code". Honestly, I'm going to take a leap here (since I have no idea where you are at in terms of Python understanding).
There are two concepts I feel like you may be having trouble with. (1) is the idea of object mutability, and (2) how variables are used and defined in and out of scope inside of Python.
When you re-assign a value like this:
n = int(n)
You haven't actually modified the value held by the variable outside (testingNum). This is because variables in Python are just references to objects. This means that your variable will always point to the same object unless it is re-assigned explicitly. Where this gets confusing is when you're dealing with the concept of mutability. For example, lists, dictionaries and classes are mutable objects. If you do something like:
t = []
n = t
and then do:
n.extend([1,2,3,4])
you'll see that both n and t now look like:
[1,2,3,4]
This isn't really mysterious behavior when you finally understand what's going on. The integer 4 and the [1,2,3,4] are different objects. The only difference is that [1,2,3,4] was also the same object as the initial [] when it was assigned to t. On the other hand, when you re-assign an integer or a string to a variable, you've simply changed what object the variable is pointing to. That's all.
How does this apply to your example?
In essence, all you were missing was a return statement:
def errorCheckInt(n):
try:
n = int(n)
checkValue1 = True
except:
checkValue1 = False
return n
testingNum = "2"
newNum = errorCheckInt(testingNum) # You can catch it here with newNum.
Understanding the use of the return statement is tantamount. When you assign a reference to a variable inside a function, that variable falls out of scope as soon as you exit the function; in other words, if you try to call it afterwards, you'll get an error. To circumvent this, you need to catch the result of your function in a variable that's in the outer scope. This allows you to keep working with the data you've just calculated inside the function.
Note
A better way of doing what you're asking is to use isinstance. For example,
print isinstance(4, int)
# True
print isinstance('4', int)
# False
This will automatically check if the value is an instance of the int type. It's both clearer and cleaner.
Further clarification
An interesting fact of mutability is that when you pass references to mutable objects inside of functions, modifying the object with, say, [].append(4) will mean that any variable pointing to that object (even from outside) is getting the same update if you will. So in certain cases using the return statement isn't necessary, but being explicit is sometimes worth the effort.
When errorCheckInt is run, variable n is bound to the same value as testingNum. However with n =, you change the binding, not the value so n then points to a different value than testingNum. What you need is to pass a mutable type and change the binding inside it:
def errorCheckInt(n):
try:
n[0] = int(n[0])
checkValue1 = True
except:
checkValue1 = False
testingNum = [ "2" ]
errorCheckInt(testingNum)
# testingNum is now 2 and type(testingNum) is int

Why Use a=b in Python

If I define
foo = [[1,2],[2,3],[3,4]]
bar = foo
then foo and bar reference the same object, namely [[1,2],[2,3],[3,4]]. I can now use either of these "tags/namespaces/references" to make changes to the object [[1,2],[2,3],[3,4]], but how is this useful to anyone?
One reason it can be useful to rebind names to existing values is if you intend to reuse the original name for a different object. For instance, this function to calculate the nth number in the Fibonacci sequence reuses the names a, b and temp repeatedly in a loop, binding the value previously referenced by a to b (via temp) each time:
def fib(n):
a = 1
b = 0
for _ in range(n):
temp = a
a = a+b
b = temp
# A more Pythonic version of the last three lines would be: a, b = a+b, a
return b
Let's say I have an attribute of my class instance, frobnoz, a reference to an instance of a Frobnoz class which in turn an attribute marfoos which is a list of all the associated marfoos, and I want to perform several operations on the first one.
marfoo = self.frobnoz.marfoos[0]
marfoo.rotate(CLOCKWISE, degrees=90)
if marfoo.is_cloudy():
self.purge_clouds(marfoo)
If it were not possible to create an additional reference to the marfoo I wanted to perform actions on, I would have had to not only have lengthy references to it, but would also incur the expense of looking up both the frobnoz and marfoos references plus the first element of the list each time I wanted to use it.
It is useful (among other things) for function calls that modify a value:
def changeFoo(bar):
bar[0][0]=3.14
changeFoo(foo)
Note: This does not technically use assignment, but it is equivalent.
It can also be used when multiple objects need to have a reference to the same object (such as in linked lists).

Difference between mutation, rebinding, copying value, and assignment operator [duplicate]

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 6 months ago.
#!/usr/bin/env python3.2
def f1(a, l=[]):
l.append(a)
return(l)
print(f1(1))
print(f1(1))
print(f1(1))
def f2(a, b=1):
b = b + 1
return(a+b)
print(f2(1))
print(f2(1))
print(f2(1))
In f1 the argument l has a default value assignment, and it is only evaluated once, so the three print output 1, 2, and 3. Why f2 doesn't do the similar?
Conclusion:
To make what I learned easier to navigate for future readers of this thread, I summarize as the following:
I found this nice tutorial on the topic.
I made some simple example programs to compare the difference between mutation, rebinding, copying value, and assignment operator.
This is covered in detail in a relatively popular SO question, but I'll try to explain the issue in your particular context.
When your declare your function, the default parameters get evaluated at that moment. It does not refresh every time you call the function.
The reason why your functions behave differently is because you are treating them differently. In f1 you are mutating the object, while in f2 you are creating a new integer object and assigning it into b. You are not modifying b here, you are reassigning it. It is a different object now. In f1, you keep the same object around.
Consider an alternative function:
def f3(a, l= []):
l = l + [a]
return l
This behaves like f2 and doesn't keep appending to the default list. This is because it is creating a new l without ever modifying the object in the default parameter.
Common style in python is to assign the default parameter of None, then assign a new list. This gets around this whole ambiguity.
def f1(a, l = None):
if l is None:
l = []
l.append(a)
return l
Because in f2 the name b is rebound, whereas in f1 the object l is mutated.
This is a slightly tricky case. It makes sense when you have a good understanding of how Python treats names and objects. You should strive to develop this understanding as soon as possible if you're learning Python, because it is central to absolutely everything you do in Python.
Names in Python are things like a, f1, b. They exist only within certain scopes (i.e. you can't use b outside the function that uses it). At runtime a name refers to a value, but can at any time be rebound to a new value with assignment statements like:
a = 5
b = a
a = 7
Values are created at some point in your program, and can be referred to by names, but also by slots in lists or other data structures. In the above the name a is bound to the value 5, and later rebound to the value 7. This has no effect on the value 5, which is always the value 5 no matter how many names are currently bound to it.
The assignment to b on the other hand, makes binds the name b to the value referred to by a at that point in time. Rebinding the name a afterwards has no effect on the value 5, and so has no effect on the name b which is also bound to the value 5.
Assignment always works this way in Python. It never has any effect on values. (Except that some objects contain "names"; rebinding those names obviously effects the object containing the name, but it doesn't affect the values the name referred to before or after the change)
Whenever you see a name on the left side of an assignment statement, you're (re)binding the name. Whenever you see a name in any other context, you're retrieving the (current) value referred to by that name.
With that out of the way, we can see what's going on in your example.
When Python executes a function definition, it evaluates the expressions used for default arguments and remembers them somewhere sneaky off to the side. After this:
def f1(a, l=[]):
l.append(a)
return(l)
l is not anything, because l is only a name within the scope of the function f1, and we're not inside that function. However, the value [] is stored away somewhere.
When Python execution transfers into a call to f1, it binds all the argument names (a and l) to appropriate values - either the values passed in by the caller, or the default values created when the function was defined. So when Python beings executing the call f3(5), the name a will be bound to the value 5 and the name l will be bound to our default list.
When Python executes l.append(a), there's no assignment in sight, so we're referring to the current values of l and a. So if this is to have any effect on l at all, it can only do so by modifying the value that l refers to, and indeed it does. The append method of a list modifies the list by adding an item to the end. So after this our list value, which is still the same value stored to be the default argument of f1, has now had 5 (the current value of a) appended to it, and looks like [5].
Then we return l. But we've modified the default list, so it will affect any future calls. But also, we've returned the default list, so any other modifications to the value we returned will affect any future calls!
Now, consider f2:
def f2(a, b=1):
b = b + 1
return(a+b)
Here, as before, the value 1 is squirreled away somewhere to serve as the default value for b, and when we begin executing f2(5) call the name a will become bound to the argument 5, and the name b will become bound to the default value 1.
But then we execute the assignment statement. b appears on the left side of the assignment statement, so we're rebinding the name b. First Python works out b + 1, which is 6, then binds b to that value. Now b is bound to the value 6. But the default value for the function hasn't been affected: 1 is still 1!
Hopefully that's cleared things up. You really need to be able to think in terms of names which refer to values and can be rebound to point to different values, in order to understand Python.
It's probably also worth pointing out a tricky case. The rule I gave above (about assignment always binding names with no effect on the value, so if anything else affects a name it must do it by altering the value) are true of standard assignment, but not always of the "augmented" assignment operators like +=, -= and *=.
What these do unfortunately depends on what you use them on. In:
x += y
this normally behaves like:
x = x + y
i.e. it calculates a new value with and rebinds x to that value, with no effect on the old value. But if x is a list, then it actually modifies the value that x refers to! So be careful of that case.
In f1 you are storing the value in an array or better yet in Python a list where as in f2 your operating on the values passed. Thats my interpretation on it. I may be wrong
Other answers explain why this is happening, but I think there should be some discussion of what to do if you want to get new objects. Many classes have the method .copy() that allows you create copies. For instance, if we rewrite f1 as
def f1(a, l=[]):
new_l = l.copy()
new_l.append(a)
return(new_l)
then it will continue to return [1]no matter how many times we call it. There is also the library https://docs.python.org/3/library/copy.html for managing copies.
Also, if you're looping through the elements of a container and mutating them one by one, using comprehensions not only is more Pythonic, but can avoid the issue of mutating the original object. For instance, suppose we have the following code:
data = [1,2,3]
scaled_data = data
for i, value in enumerate(scaled_data):
scaled_data[i] = value/sum(data)
This will set scaled_data to [0.16666666666666666, 0.38709677419354843, 0.8441754916792739]; each time you set a value of scaled_data to the scaled version, you also change the value in data. If you instead have
data = [1,2,3]
scaled_data = [x/sum(data) for x in data]
this will set scaled_data to [0.16666666666666666, 0.3333333333333333, 0.5] because you're not mutating the original object but creating a new one.

Categories