2021年11月5日 星期五

[ Python 文章收集 ] Pydon't - Pass-by-value, reference, and assignment

 Source From Here

Preface
When you call a function in Python and give it some arguments... Are they passed by value? No! By reference? No! They're passed by assignment.

Many traditional programming languages employ either one of two models when passing arguments to functions:
* some languages use the pass-by-value model; and
* most of the others use the pass-by-reference model.

Having said that, it is important to know the model that Python uses, because that influences the way your code behaves. In this Pydon't, you will:
* See that Python doesn't use the pass-by-value nor the pass-by-reference models;
* Understand that Python uses a pass-by-assignment model;
* Learn about the built-in function id;
* Create a better understanding for the Python object model;
* Realise that every object has 3 very important properties that define it;
* Understand the difference between mutable and immutable objects;
* Learn the difference between shallow and deep copies; and
* Learn how to use the module copy to do both types of object copies.


Is Python pass-by-value?
In the pass-by-value model, when you call a function with a set of arguments, the data is copied into the function. This means that you can modify the arguments however you please and that you won't be able to alter the state of the program outside the function.

This is not what Python does, Python does not use the pass-by-value model. Looking at the snippet of code that follows, it might look like Python uses pass-by-value:
  1. def foo(x):  
  2.     x = 4  
  3.   
  4. a = 3  
  5. foo(a)  
  6. print(a)  # 3  
This looks like the pass-by-value model because we gave it a 3, changed it to a 4, and the change wasn't reflected on the outside (a is still 3).

But, in fact, Python is not copying the data into the function. To prove this, I'll show you a different function:
  1. def clearly_not_pass_by_value(my_list):  
  2.     my_list[0] = 42  
  3.   
  4. l = [123]  
  5. clearly_not_pass_by_value(l)  
  6. print(l)  # [4223]  
As we can see, the list l, that was defined outside of the function, changed after calling the function clearly_not_pass_by_value. Hence, Python does not use a pass-by-value model.

Is Python pass-by-reference?
In a true pass-by-reference model, the called function gets access to the variables of the callee! Sometimes, it can look like that's what Python does, but Python does not use the pass-by-reference model.

I'll do my best to explain why that's not what Python does:
  1. def not_pass_by_reference(my_list):  
  2.     my_list = [42730]  
  3.   
  4. l = [123]  
  5. not_pass_by_reference(l)  
  6. print(l)  # [123]  
If Python used a pass-by-reference model, the function would've managed to completely change the value of l outside the function, but that's not what happened, as we can see. Let me show you an actual pass-by-reference situation.

Here's some Pascal code:
  1. program callByReference;  
  2. var  
  3.     x: integer;  
  4.   
  5. procedure foo(var a: integer);  
  6. { create a procedure called `foo` }  
  7. begin  
  8.     a := 6            { assign 6 to `a` }  
  9. end;  
  10.   
  11. begin  
  12.     x := 2;           { assign 2 to `x` }  
  13.     writeln(x);       { print `x` }  
  14.     foo(x);           { call `foo` with `x` }  
  15.     writeln(x);       { print `x` }  
  16. end.  
Look at the last lines of that code:
* we assign 2 to x with x := 2;
* we print x;
* we call foo with x as argument; and
* we print x again.

What's the output of this program? If you run this, you'll see that the output is:
2
6

which can be rather surprising, if the majority of your programming experience is in Python!

Python object model
To really understand the way Python behaves when calling functions, it's best if we first understand what Python objects are, and how to characterise them.

The three characteristics of objects
In Python, everything is an object, and each object is characterised by three things:
* its identity (an integer that uniquely identifies the object, much like social security numbers identify people);
* a type (that identifies the operations you can do with your object); and
* the object's content.

Here is an object and its three characteristics:
>>> id(obj)
2698212637504 # the identity of `obj`
>>> type(obj)
<class 'list'> # the type of `obj`
>>> obj
[1, 2, 3] # the contents of `obj`

As we can see above, id is the built-in function you use to query the identity of an object, and type is the built-in function you use to query the type of an object.

(Im)mutability
The (im)mutability of an object depends on its type. In other words, (im)mutability is a characteristic of types, not of specific objects! But what exactly does it mean for an object to be mutable? Or for an object to be immutable?

Recall that an object is characterised by its identity, its type, and its contents. A type is mutable if you can change the contents of its objects without changing its identity and its type. Lists are a great example of a mutable data type. Why? Because lists are containers: you can put things inside lists and you can remove stuff from inside those same lists.

Below, you can see how the contents of the list obj change as we make method calls, but the identity of the list remains the same:
>>> obj = []
>>> id(obj)
2287844221184

>>> obj.append(0); obj.extend([1, 2, 3]); obj
[42, 0, 1, 2, 3]
>>> id(obj)
2287844221184

>>> obj.pop(0); obj.pop(0); obj.pop(); obj
42
0
3
[1, 2]

>>> id(obj)
2287844221184

However, when dealing with immutable objects, it's a completely different story. If we check an English dictionary, this is what we get for the definition of “immutable”:
adjective: immutable – unchanging over time or unable to be changed.

Immutable objects' contents never change. Take a string as an example:
>>> obj = "Hello, world!"

Strings are a good example for this discussion because, sometimes, they can look mutable. But they are not!

A very good indicator that an object is immutable is when all its methods return something. This is unlike list's .append method, for example! If you use .append on a list, you get no return value. On the other hand, whatever method you use on a string, the result is returned to you:
>>> [].append(0) # No return.
>>> obj.upper() # A string is returned.
'HELLO, WORLD!"

Notice how obj wasn't updated automatically to "HELLO, WORLD!". Instead, the new string was created and returned to you.

Another great hint at the fact that strings are immutable is that you cannot assign to its indices:
>>> obj[0]
'H'
>>> obj[0] = "h"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

This shows that, when a string is created, it remains the same. It can be used to build other strings, but the string itself always. stays. unchanged.

Variable names as labels
Another important thing to understand is that a variable name has very little to do with the object itself.

In fact, the name obj was just a label that I decided to attach to the object that has identity 2698212637504, has the list type, and contents 1, 2, 3. Just like I attached the label obj to that object, I can attach many more names to it:
>>> foo = bar = baz = obj

Again, these names are just labels. Labels that I decided to stick to the same object. How can we know it's the same object? Well, all their “social security numbers” (the ids) match, so they must be the same object:
>>> id(foo)
2698212637504
>>> id(bar)
2698212637504
>>> id(baz)
2698212637504
>>> id(obj)
2698212637504

Therefore, we conclude that foobarbaz, and obj, are variable names that all refer to the same object.

The operator is
This is exactly what the operator is does: it checks if the two objects are the same.

For two objects to be the same, they must have the same identity:
>>> foo is obj
True
>>> bar is foo
True
>>> obj is foo
True

It is not enough to have the same type and contents:
>>> obj1 = [1, 2, 3]; obj2 = [1, 2, 3]
>>> obj1 is obj2
False

Think of it in terms of perfect twins. When two siblings are perfect twins, they look identical. However, they are different people!

Assignment as nicknaming
If we keep pushing this metaphor forward, assigning variables is just like giving a new nickname to someone.

My friends from middle school call me “Rojer”. My friends from college call me “Girão”. People I am not close to call me by my first name – “Rodrigo”. However, regardless of what they call me, I am still me, right?

If one day I decide to change my haircut, everyone will see the new haircut, regardless of what they call me!

In a similar fashion, if I modify the contents of an object, I can use whatever nickname I prefer to see that those changes happened. For example, we can change the middle element of the list we have been playing around with:
>>> john = ken = bob = [1, 2, 3]
>>> john[1] = 42
>>> ken
[1, 42, 3]
>>> bob
[1, 42, 3]

Because they all pointed at the same list object.

Python is pass-by-assignment
Having laid out all of this, we are now ready to understand how Python passes arguments to functions. When we call a function, each of the parameters of the function is assigned to the object they were passed in. In essence, each parameter now becomes a new nickname to the objects that were given in.

Immutable arguments
If we pass in immutable arguments, then we have no way of modifying the arguments themselves. After all, that's what immutable means: “doesn't change”.

That is why it can look like Python uses the pass-by-value model. Because the only way in which we can have the parameter hold something else is by assigning it to a completely different thing. When we do that, we are reusing the same nickname for a different object:
>>> mydict = dict.fromkeys(['a', 'b'], 1)
>>> mydict
{'a': 1, 'b': 1}
>>> mydict['a'] = 0
>>> mydict # Now key 'a' and 'b' points to different object
{'a': 0, 'b': 1}

Mutable arguments
On the other hand, mutable arguments can be changed. We can modify their internal contents. A prime example of a mutable object is a list: its elements can change (and so can its length).

That is why it can look like Python uses a pass-by-reference model. However, when we change the contents of an object, we didn't change the identity of the object itself. Similarly, when you change your haircut or your clothes, your social security number does not change:
>>> mydict = dict.fromkeys(['a', 'b'], [])
>>> mydict
{'a': [], 'b': []}
>>> mydict['a'].append(0)
>>> mydict # Both key 'a' and 'b' points to same list
{'a': [0], 'b': [0]}


More topics (Making copies, Examples in code etc) can refer to original post here.

[ Python 文章收集 ] Pydon't - Pass-by-value, reference, and assignment

  Source From  Here Preface When you call a function in Python and give it some arguments... Are they passed by value? No! By reference? No!...