按名调用 Algol
按值调用 Java
https://docs.python.org/3.6/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference
# 0-
# By returning a tuple of the results
def func2(a, b):
# a, b = 'new-value', b + 1
a = 'new-value'
b = b + 1 return a, b x, y = 'old-value', 99
x, y = func2(x, y) # 1-
# By using global variables. This isn’t thread-safe, and is not recommended. # 2-
# By passing a mutable (changeable in-place) object def func1(a):
a[0], a[1] = 'new-value', a[1] + 1 args = ['old-value', 99]
func1(args) # 3-
# By passing in a dictionary that gets mutated def func3(args):
args['a'], args['b'] = 'new-value', args['b'] + 1 args_b = {'a': 'old-value', 'b': 99}
func3(args_b) # 4-
# Or bundle up values in a class instance
class callByRef:
def __init__(self, **args):
for (key, value) in args.items():
setattr(self, key, value) def func4(args):
args.a, args.b = 'new-value', args.b + 1 args_c = callByRef(a='old-value', b=99)
func4(args_c)
Why did changing list ‘y’ also change list ‘x’?
If you wrote code like:
>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]
you might be wondering why appending an element to y
changed x
too.
There are two factors that produce this result:
Variables are simply names that refer to objects. Doing
y = x
doesn’t create a copy of the list – it creates a new variabley
that refers to the same objectx
refers to. This means that there is only one object (the list), and bothx
andy
refer to it.Lists are mutable, which means that you can change their content.
After the call to append()
, the content of the mutable object has changed from []
to [10]
. Since both the variables refer to the same object, using either name accesses the modified value [10]
.
If we instead assign an immutable object to x
:
>>> x = 5 # ints are immutable
>>> y = x
>>> x = x + 1 # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5
we can see that in this case x
and y
are not equal anymore. This is because integers are immutable, and when we do x = x + 1
we are not mutating the int 5
by incrementing its value; instead, we are creating a new object (the int 6
) and assigning it to x
(that is, changing which object x
refers to). After this assignment we have two objects (the ints 6
and 5
) and two variables that refer to them (x
now refers to 6
but y
still refers to 5
).
Some operations (for example y.append(10)
and y.sort()
) mutate the object, whereas superficially similar operations (for example y = y + [10]
and sorted(y)
) create a new object. In general in Python (and in all cases in the standard library) a method that mutates an object will return None
to help avoid getting the two types of operations confused. So if you mistakenly write y.sort()
thinking it will give you a sorted copy of y
, you’ll instead end up with None
, which will likely cause your program to generate an easily diagnosed error.
However, there is one class of operations where the same operation sometimes has different behaviors with different types: the augmented assignment operators. For example, +=
mutates lists but not tuples or ints (a_list += [1, 2, 3]
is equivalent to a_list.extend([1, 2, 3])
and mutates a_list
, whereas some_tuple += (1, 2, 3)
and some_int += 1
create new objects).
In other words:
If we have a mutable object (
list
,dict
,set
, etc.), we can use some specific operations to mutate it and all the variables that refer to it will see the change.If we have an immutable object (
str
,int
,tuple
, etc.), all the variables that refer to it will always see the same value, but operations that transform that value into a new value always return a new object.
If you want to know if two variables refer to the same object or not, you can use the is
operator, or the built-in function id()
.
How do I write a function with output parameters (call by reference)?
Remember that arguments are passed by assignment in Python. Since assignment just creates references to objects, there’s no alias between an argument name in the caller and callee, and so no call-by-reference per se. You can achieve the desired effect in a number of ways.
-
By returning a tuple of the results:
>>>>>> def func1(a, b):
... a = 'new-value' # a and b are local names
... b = b + 1 # assigned to new objects
... return a, b # return new values
...
>>> x, y = 'old-value', 99
>>> func1(x, y)
('new-value', 100)This is almost always the clearest solution.
By using global variables. This isn’t thread-safe, and is not recommended.
-
By passing a mutable (changeable in-place) object:
>>>>>> def func2(a):
... a[0] = 'new-value' # 'a' references a mutable list
... a[1] = a[1] + 1 # changes a shared object
...
>>> args = ['old-value', 99]
>>> func2(args)
>>> args
['new-value', 100] -
By passing in a dictionary that gets mutated:
>>>>>> def func3(args):
... args['a'] = 'new-value' # args is a mutable dictionary
... args['b'] = args['b'] + 1 # change it in-place
...
>>> args = {'a': 'old-value', 'b': 99}
>>> func3(args)
>>> args
{'a': 'new-value', 'b': 100} -
Or bundle up values in a class instance:
>>>>>> class Namespace:
... def __init__(self, /, **args):
... for key, value in args.items():
... setattr(self, key, value)
...
>>> def func4(args):
... args.a = 'new-value' # args is a mutable Namespace
... args.b = args.b + 1 # change object in-place
...
>>> args = Namespace(a='old-value', b=99)
>>> func4(args)
>>> vars(args)
{'a': 'new-value', 'b': 100}There’s almost never a good reason to get this complicated.
Your best choice is to return a tuple containing the multiple results.
为什么更改列表 'y' 也会更改列表 'x'?
如果你编写的代码就像下面一样:
>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]
你可能想知道为什么追加一个元素也改变了x。
产生这种结果有两个因素:
变量只是指向具体对象的名称。 执行
y = x
并不会为列表创建一个副本 —— 它只是创建了一个新变量y
指向x
所指向的同一对象。 这意味着只存在一个对象(列表),x
和y
都是对它的引用。列表属于 mutable 对象,这意味着你可以改变它的内容。
在调用 append()
之后,这个可变对象的内容由 []
变为 [10]
。 由于两个变量都指向同一对象,因此使用任何一个名称所访问到的都是修改后的值 [10]
。
如果我们改为将不可变对象赋值给 x
:
>>> x = 5 # ints are immutable
>>> y = x
>>> x = x + 1 # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5
我们可以看到在此情况下 x
和 y
就不再相等了。 这是因为整数是 immutable 对象,当我们执行 x = x + 1
时我们并不是改变了 5
这个对象的值;而是创建了一个新的对象 (整数 6
) 并将其赋值给 x
(也就是改变了 x
所指向的对象)。 在赋值之后我们就有了两个对象 (整数 6
和 5
) 以及分别指向它们的两个变量 (x
现在指向 6
而 y
仍然指向 5
)。
某些操作 (例如 y.append(10)
和 y.sort()
) 是改变原对象,而看上去相似的另一些操作 (例如 y = y + [10]
和 sorted(y)
) 则是创建新对象。 通常在 Python 中 (以及在标准库的所有代码中) 会改变原对象的方法将返回 None
以帮助避免混淆这两种不同类型的操作。 因此如果你错误地使用了 y.sort()
并期望它将返回一个经过排序的 y
的副本,你得到的结果将会是 None
,这将导致你的程序产生一个容易诊断的错误。
但是,还存在一类操作,不同的类型执行相同的操作会有不同的行为:那就是增强赋值运算符。 例如,+=
会原地改变列表,但不会改变元组或整数 (a_list += [1, 2, 3]
与 a_list.extend([1, 2, 3])
一样都会改变 a_list
,而 some_tuple += (1, 2, 3)
和 some_int += 1
则会创建新的对象)。
换而言之:
如何编写带输出参数的函数(通过引用调用)?
请记住在 Python 中参数是通过赋值来传递的。 由于赋值只是创建了对象的引用,因此在调用者和被调用者的参数名称之间没有别名,所以本身是没有按引用调用的。 你可以通过多种方式实现所需的效果。
-
通过返回一个结果元组:
>>>>>> def func1(a, b):
... a = 'new-value' # a and b are local names
... b = b + 1 # assigned to new objects
... return a, b # return new values
...
>>> x, y = 'old-value', 99
>>> func1(x, y)
('new-value', 100)这几乎总是最清晰明了的解决方案。
通过使用全局变量。 这种方式不是线程安全的,而且也不受推荐。
-
通过传递一个可变 (即可原地修改的) 对象:
>>>>>> def func2(a):
... a[0] = 'new-value' # 'a' references a mutable list
... a[1] = a[1] + 1 # changes a shared object
...
>>> args = ['old-value', 99]
>>> func2(args)
>>> args
['new-value', 100] -
通过传递一个会被改变的字典:
>>>>>> def func3(args):
... args['a'] = 'new-value' # args is a mutable dictionary
... args['b'] = args['b'] + 1 # change it in-place
...
>>> args = {'a': 'old-value', 'b': 99}
>>> func3(args)
>>> args
{'a': 'new-value', 'b': 100} -
或者在一个类实例中捆绑值:
>>>>>> class Namespace:
... def __init__(self, /, **args):
... for key, value in args.items():
... setattr(self, key, value)
...
>>> def func4(args):
... args.a = 'new-value' # args is a mutable Namespace
... args.b = args.b + 1 # change object in-place
...
>>> args = Namespace(a='old-value', b=99)
>>> func4(args)
>>> vars(args)
{'a': 'new-value', 'b': 100}几乎没有任何适当理由将问题如此复杂化。
你的最佳选择是返回一个包含多个结果的元组。
Is Python call-by-value or call-by-reference? Neither. https://jeffknupp.com/blog/2012/11/13/is-python-callbyvalue-or-callbyreference-neither/
Is Python call-by-value or call-by-reference? Neither.
One aspect of Python programming that trips up those coming from languages like C or Java is how arguments are passed to functions in Python. At a more fundamental level, the confusion arises from a misunderstanding about Python object-centric data model and its treatment of assignment. When asked whether Python function calling model is "call-by-value" or "call-by-reference", the correct answer is: neither. Indeed, to try to shoe-horn those terms into a conversation about Python's model is misguided. "call-by-object," or "call-by-object-reference" is a more accurate way of describing it. But what does "call-by-object" even mean?
In Python, (almost) everything is an object. What we commonly refer to as "variables" in Python are more properly called names. Likewise, "assignment" is really the binding of a name to an object. Each binding has a scope that defines its visibility, usually the block in which the name originates.
That's a lot of terminology all at once, but those basic terms form the cornerstone of Python's execution model. Compared to, say, C++, the differences are subtle yet important. A concrete example will highlight these differences. Think about what happens when the following C++ code is executed:
1 |
string some_guy = "Fred"; |
In the above, the variable some_guy
refers to a location in memory, and the value 'Fred' is inserted in that location (indeed, we can take the address of some_guy
to determine the portion of memory to which it refers). Later, the contents of the memory location referred to by some_guy
are changed to 'George'. The previous value no longer exists; it was overwritten. This likely matches your intuitive understanding (even if you don't program in C++).
Let's now look at a similar block of Python code:
1 |
some_guy = 'Fred' |
Binding Names to Objects
On line 1, we create a binding between a name, some_guy
, and a string object containing 'Fred'. In the context of program execution, the environment is altered; a binding of the name some_guy
' to a string object is created in the scope of the block where the statement occurred. When we later say some_guy = 'George'
, the string object containing 'Fred' is unaffected. We've just changed the binding of the name some_guy
. We haven't, however, changed either the 'Fred' or 'George' string objects. As far as we're concerned, they may live on indefinitely.
With only a single name binding, this may seem overly pedantic, but it becomes more important when bindings are shared and function calls are involved. Let's say we have the following bit of Python code:
1 |
some_guy = 'Fred' first_names = [] |
So what get's printed in the final line? Well, to start, the binding of some_guy
to the string object containing 'Fred' is added to the block's namespace. The name first_names
is bound to an empty list object. On line 4, a method is called on the list object first_names
is bound to, appending the object some_guy
is bound to. At this point, there are still only two objects that exist: the string object and the list object. some_guy
and first_names[0]
both refer to the same object (Indeed, print(some_guy is first_names[0])
shows this).
Let's continue to break things down. On line 6, a new name is bound: another_list_of_names
. Assignment between names does not create a new object. Rather, both names are simply bound to the same object. As a result, the string object and list object are still the only objects that have been created by the interpreter. On line 7, a member function is called on the object another_list_of_names
is bound to and it is mutated to contain a reference to a new object: 'George'. So to answer our original question, the output of the code is
Bill ['Fred', 'George'] ['Fred', 'George']
This brings us to an important point: there are actually two kinds of objects in Python. A mutable object exhibits time-varying behavior. Changes to a mutable object are visible through all names bound to it. Python's lists are an example of mutable objects. An immutable object does not exhibit time-varying behavior. The value of immutable objects can not be modified after they are created. They can be used to compute the values of new objects, which is how a function like string.join works. When you think about it, this dichotomy is necessary because, again, everything is an object in Python. If integers were not immutable I could change the meaning of the number '2' throughout my program.
It would be incorrect to say that "mutable objects can change and immutable ones can't", however. Consider the following:
1 |
first_names = ['Fred', 'George', 'Bill'] |
Tuples in Python are immutable. We can't change the tuple object name_tuple
is bound to. But immutable containers may contain references to mutable objects like lists. Therefore, even though name_tuple
is immutable, it "changes" when 'Igor' is appended to first_names
on the last line. It's a subtlety that can sometimes (though very infrequently) prove useful.
By now, you should almost be able to intuit how function calls work in Python. If I call foo(bar)
, I'm merely creating a binding within the scope of foo
to the object the argument bar
is bound to when the function is called. If bar
refers to a mutable object and foo
changes its value, then these changes will be visible outside of the scope of the function.
1 |
def foo(bar): |
On the other hand, if bar
refers to an immutable object, the most that foo
can do is create a name bar
in its local namespace and bind it to some other object.
1 |
def foo(bar): |
Hopefully by now it is clear why Python is neither "call-by-reference" nor "call-by-value". In Python a variable is not an alias for a location in memory. Rather, it is simply a binding to a Python object. While the notion of "everything is an object" is undoubtedly a cause of confusion for those new to the language, it allows for powerful and flexible language constructs, which I'll discuss in my next post.
Posted on Nov 13, 2012 by Jeff Knupp
实践
Python 3.7.7
>>> a=[]
>>> b=a
>>> b.append(1)
>>> id(a)
140139695586672
>>> id(b)
140139695586672
>>> c=a
>>> c=[1]
>>> id(c)
140139696409648
>>> c=[1,2]
>>> id(c)
140139695049536
>>> d=a
>>> id(d)
140139695586672
>>> id(a)
140139695586672
>>> d=[1]
>>> id(d)
140139696409648
>>>
d = {'a': {'sa': 'vsa'}, 'b': {'sb': 'vsb'}}
d_all = {'sm': 'smv', 'sn': 'snv'}
d_merged = d
print(d_merged == d)
for i in d:
for j in d_all:
d[i][j] = d_all[j]
print(d_merged == d)
import sys
print(sys.version_info)
True
True
sys.version_info(major=3, minor=8, micro=2, releaselevel='final', serial=0)