事情是这样的,Python里是传址还是传值令人疑惑,限于本人没有C基础,所以对大家的各类水平层次不一的解答难以确信。
第一个阶段:
在读《python基础教程第二版》的时候感到疑惑,然后群友解答(略敷衍),接着就是知乎上提问(感谢大家的热心回答,但我很晚才收到推送)
虽然是某天早晨睡不着,翻看公众号的时候看见一篇《不要再问 "Python 函数中,参数是传值,还是传引用?" 这种没有意义的问题了》的文章,初步释疑惑(但后来我觉得他的说法虽然形象,但是不准确)
第二个阶段:
在阅读《JavaScript高级程序设计(第2版)》一书的P71遇到问题,书中说JS都是值传递,但举的例子我觉得没有很好地论证他的观点,于是最终得到发现了JS中其实是“call by share”
第三个阶段:
在读《python学习手册》P159,书里提到了共享引用(Shared References)的概念,果然之前JS的官方的说法是正确的。

赋值:
无论对不变类型变量、可变对象赋值
    对变量赋新值都会断开对原值的引用,而成为新值的引用
不可变类型
例子1
a = 1
b = a
a = 3
print(a,b)
# 3 1
可变类型
例子2
L1 = [1,2,3]
L2 = L1
L1 = 13
print(L1,L2)
# 13 [1, 2, 3]
L1 = ['a','b','c']
print(L1,L2)
# ['a', 'b', 'c'] [1, 2, 3]
    对其赋值,就是在原处修改

调用方法修改属性:
L1 = [1,2,3]
L2 = L1
L1[1] = 13
print(L1,L2)
# [1, 13, 3] [1, 13, 3]
L1 = ['a','b','c']
print(L1,L2)
# ['a', 'b', 'c'] [1, 13, 3]
为什么说是调用方法修改属性呢?因为运算符重载中告诉我们,其实"[]"索引其实是自动调用了__getitem__方法(P713)。
因为Python中给函数传递实参即是“形参名=实参”的形式,跟例子1中b=a、例子2中L2=L1的意思是一样的。

记住以上两条,那么即可自如地应对python中参数传递的问题:

代码1:
def testImmutable(arg):
arg = 2
print(arg)

a = 1
testImmutable(a)
# 2
print(a)
# 1
这个用"对变量赋新值都会断开对原值的引用,而成为新值的引用"解释即可,所以这样的函数无法修改不可变对象。
当然,可变对象也是不可以修改
代码2:
def testMutable(arg):
arg = ['a','b','c']
print(arg)

L = [1,2,3]
testMutable(L)
# ['a', 'b', 'c']
print(L)
# [1, 2, 3]
(这一点可以跟后面补充内容部分的JS的例子做对比)

但是我们可以用方法修改可变对象的属性
代码3:
def testAttr(args):
args.append(1.3)

a = [1.1,1.2]
print(a)
# [1.1, 1.2]
testAttr(a)
print(a)
# [1.1, 1.2, 1.3]

******************************♣******************************
P530提到了一个陷阱
也就是说我如果想要让形参默认为一个空列表,如果写成这样是有问题的:
def saver(x=[]): # Saves away a list object
x.append(1) # Changes same object each time!
print(x)
saver([2]) # Default not used
# [2, 1]
saver() # Default used
# [1]
saver() # Grows on each call!
# [1, 1]
saver()
# [1, 1, 1]
这是因为,这个空列表对象只是在def语句执行时被创建的,不会每次函数调用时都得到一个新的列表,所以每次新的元素加入后,列表会在原来的基础上变大,因为这个空列表在每次函数调用的时候都没有被重置
正确写法如下:
def saver(x=None):
if x is None: # No argument passed?
x = [] # Run code to make a new list each time
x.append(1) # Changes new list object
print(x)
saver([2])
# [2, 1]
saver() # Doesn't grow here
# [1]
saver()
# [1]
    
    ******************************♣******************************
如下代码也会报错
x = 11
def selector():
print(X)
X = 88
selector()
    这个错误原因有点类似于JS的变量声明提升
    
R中的闭包可参看Python和JS的,而Python中的作用域问题,可以参看JS的(比如没有块作用域、局部变量声明提升等等的问题)


补充
****************************♣****************************
在阅读《JavaScript高级程序设计(第2版)》一书的P71遇到问题:
      为什么都是值传递,而不是传递引用?

******************************♣******************************
引用1:
引用是C++中的概念,其操作符是: & ,这跟C中是取地址操作符一样,
但是意义不一样,C中没有引用的。 (切记 !)

******************************♣******************************
引用2:
传值,
是把实参的值赋值给行参
那么对行参的修改,不会影响实参的值

传地址
是传值的一种特殊方式,只是他传递的是地址,不是普通的如int
那么传地址以后,实参和行参都指向同一个对象

传引用
真正的以地址的方式传递参数
传递以后,行参和实参都是同一个对象只是他们名字不同而已
 ✿例如有人名叫王小毛,他的绰号是“三毛”。说“三毛”怎么怎么的,其实就是对王小毛说三道四。

******************************♣******************************
♣引用3:
Java中只有传值
在JS和Java中,只有按值传递call by value,并没有按引用传递call by reference。引用类型的变量依然是按值传递的。这是因为引用类型也是一个变量,只是这个变量的值是另一块内存的地址。将引用类型变量传递给函数形参,函数同样会为该形参在栈上开辟新的空间,存放实参中的地址值因此这依然是按值传递,只不过这个值是地址值而已只有在C或C++里才有按引用传递。指的是用取地址符取到变量地址,作为实参传递给形参的指针变量。这里的实参是地址常量,不是指针变量。JS和Java里都没有取地址符,就只有按值传递。这一点在Core Java 第一卷里面有说明。不过,JS和Java里虽然没有对地址的直接操作,但是仍然有间接寻址的概念,即通过引用变量改变其地址所指向的内存区。

******************************♣******************************
引用4——在JAVA中:
基础类型变量存的是具体值,所以基础类型变量传值,传的是具体值的副本
引用类型变量存的就是地址值,所以引用类型变量传值,传的就是地址值了
——一切传引用其实本质上是传值
  1. int num = 10;
  2. String str = "hello";  //String是一个引用类型
  1. num = 20;
  2. str = "java";‘
 

第一个例子:基本类型
  1. void foo(int value) {
  2.     value = 100;
  3. }
  4. foo(num); // num 没有被改变

这很容易理解,因为我们传递的是值的副本

第二个例子:没有提供改变自身方法的引用类型
  1. void foo(String text) {
  2.     text = "windows";
  3. }
  4. foo(str); // str 也没有被改变

str存着地址0x11

而0x11地址被传递给形参text
而text = "windows"
是让text指向了一个新的对象,text中存了一个新地址0x12
第三个例子:提供了改变自身方法的引用类型
  1. StringBuilder sb = new StringBuilder("iphone");
  2. void foo(StringBuilder builder) {
  3.     builder.append("4");
  4. }
  5. foo(sb); // sb 被改变了,变成了"iphone4"。

sb是一个StringBuilder对象,他的地址0x21被传递给builder

此时,builder跟sb指向了同一个对象
所以使用append方法操作这个对象的时候,sb特被改变
第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
  1. StringBuilder sb = new StringBuilder("iphone");
  2. void foo(StringBuilder builder) {
  3.     builder = new StringBuilder("ipad");
  4. }
  5. foo(sb); // sb 没有被改变,还是 "iphone"。
builder尽管获得了builder的地址,但是没有使用,就被重新存了一个地址了
再看:
  1. public class TestMain {
  2. public static void main(String[] args) {
  3. List<Integer> list = new ArrayList<Integer>();
  4. for (int i = 0; i < 10; i++) {
  5. list.add(i);
  6. }
  7. add(list);
  8. for (Integer j : list) {
  9. System.err.print(j+",");;
  10. }
  11. System.err.println("");
  12. System.err.println("*********************");
  13. String a="A";
  14. append(a);
  15. System.err.println(a);
  16. int num = 5;
  17. addNum(num);
  18. System.err.println(num);
  19. }
  20. static void add(List<Integer> list){
  21. list.add(100);
  22. }
  23. static void append(String str){
  24. str+="is a";
  25. }
  26. static void addNum(int a){
  27. a=a+10;
  28. }
  29. }

打印出来的结果是:

0,1,2,3,4,5,6,7,8,9,100,
*********************
A                                        
5
尽管str是对象类型的,但是str+="is a";其实是str = str + "is a" 已经指向了一个新的对象了
而String类并没有提供改变自身的方法
他所谓的“改变”其实都是重新绑定到一个新的值

******************************♣******************************
引用5:
该策略的重点是:调用函数传参时,函数接受对象实参引用的副本(既不是按值传递的对象副本,也不是按引用传递的隐式引用)。 它和按引用传递的不同在于:在共享传递中对函数形参的赋值,不会影响实参的值。如下面例子中,不可以通过修改形参o的值,来修改obj的值。
  1. var obj = {x : 1};
  2. function foo(o) {
  3.     o = 100;
  4. }
  5. foo(obj);
  6. console.log(obj.x); // 仍然是1, obj并未被修改为100.

如果是按引用传递,修改形参o的值,应该影响到实参才对。但这里修改o的值并未影响obj。 因此JS中的对象并不是按引用传递。那么究竟对象的值在JS中如何传递的呢?

按共享传递 call by sharing
准确的说,JS中的基本类型按值传递,对象类型按共享传递(call by sharing,也叫按对象传递、按对象共享传递)。最早由Barbara Liskov. 在1974年的GLU语言中提出。该求值策略被用于Python、Java、Ruby、JS等多种语言。

******************************♣******************************
✤引用6:
ECMAScript中对call by sharing的定义:
The main point of this strategy is that function receives the copy of the reference to object. This reference copy is associated with the formal parameter and is its value.
 Regardless the fact that the concept of the reference in this case appears, this strategy should not be treated as call by reference (though, in this case the majority makes a mistake), because the value of the argument is not the direct alias(别名), but the copy of the address.
 The main difference consists that :
1 assignment of a new value to argument inside the function does not affect object outside (as it would be in case of call by reference). 
   给参数赋一个新值,不会影响到外面的对象
2 However, because formal parameter(形参), having an address copy, gets access to the same object that is outside (i.e. the object from the outside completely was not copied as would be in case of call by value), 
changes of properties of local argument object — are reflected in the external object.
   改变局部参数对象的属性,会影响到外部对象

******************************♣******************************
所以啊,个人的总结就是:
其实所谓的引用都是值传递,差异出现在:
     ❶基本类型(JavaScript、Java)/不可变类型(Python)
        传递值副本
     ❷引用类型(JavaScript、Java)/可变类型(Python)
        传递地址——传递地址区别于传递引用
        解释成共享传递
        :①在函数内给形参赋新值(无论是基本类型还是对象类型),不改变函数外的对象
        :②在函数内修改形参的属性,会影响到函数外的对象


他山之石,calling by share——python中既不是传址也不是传值的更多相关文章

  1. python中导入一个需要传参的模块

    最近跑实验,遇到了一个问题:由于实验数据集比较多,每次跑完一个数据集就需要手动更改文件路径,再将文件传到服务器,再运行实验,这样的话效率很低,必须要专门看着这个实验,啥时候跑完就手动修改运行下一个实验 ...

  2. python 中的metaclass和baseclasses

    提前说明: class object  指VM中的class 对象,因为python一切对象,class在VM也是一个对象,需要区分class对象和 class实例对象. class instance ...

  3. Python中的内置函数

    2.1 Built-in Functions The Python interpreter has a number of functions built into it that are alway ...

  4. Python中super()的用法

    参考链接:https://www.cnblogs.com/shengulong/p/7892266.html super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是 ...

  5. python中的__enter__ __exit__

    我们前面文章介绍了迭代器和可迭代对象,这次介绍python的上下文管理.在python中实现了__enter__和__exit__方法,即支持上下文管理器协议.上下文管理器就是支持上下文管理器协议的对 ...

  6. python中GIL和线程与进程

    线程与全局解释器锁(GIL) 一.线程概论 1.何为线程 每个进程有一个地址空间,而且默认就有一个控制线程.如果把一个进程比喻为一个车间的工作过程那么线程就是车间里的一个一个流水线. 进程只是用来把资 ...

  7. OpenCV-Python(1)在Python中使用OpenCV进行人脸检测

    OpenCV是如今最流行的计算机视觉库,而我们今天就是要学习如何安装使用OpenCV,以及如何去访问我们的摄像头.然后我们一起来看看写一个人脸检测程序是如何地简单,简单到只需要几行代码. 在开始之前, ...

  8. 如何将xml转为python中的字典

    如何将xml转为python中的字典 import cElementTree as ElementTree class XmlListConfig(list): def __init__(self, ...

  9. Python 中的属性访问与描述符

    在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作.例如,有一个类实例对象foo,它有一个name属性,那便可以使用foo.name对此属性进行访问.一般而言,点(. ...

随机推荐

  1. log4j日志输出框架

    什么是log4j框架呢? log4j是一个日志输出框架,用于输出日志的.比如MyBatis的日志就是通过log4j输出的,主流框架都是log4j输出的,Spring框架 也可以通过log4j输出日志! ...

  2. IDEA使用技巧

    1,导入原Eclipse Web项目 由于使用 PowerDesign连接MySql时只能用32位 Jdk,原Eclipse项目依赖于64位Jdk,导致在eclipse打不开工程,把工程导入IDEA后 ...

  3. 部署的docker image总是太大,怎么办?

    sudo docker images REPOSITORY                        TAG                 IMAGE ID            CREATED ...

  4. LeetCode-7-反转整数-c# 版本

    c# 版本 // 给定一个 32 位有符号整数,将整数中的数字进行反转. public class Solution { public int Reverse(int x) { / // 边界判断 / ...

  5. tensorflow(3)可视化,日志,调试

    可视化 添加变量 tf.summary.histogram( "weights1", weights1) # 可视化观看变量 还有添加图像和音频. 常量 tf.summary.sc ...

  6. C#设计模式(13)——代理模式(Proxy Pattern)(转)

    一.引言 在软件开发过程中,有些对象有时候会由于网络或其他的障碍,以至于不能够或者不能直接访问到这些对象,如果直接访问对象给系统带来不必要的复杂性,这时候可以在客户端和目标对象之间增加一层中间层,让代 ...

  7. python小程序--one

    #!/usr/bin/env python # _*_ coding:utf8 _*_ import sys user_lock_file="user_lock.txt" # 用户 ...

  8. Nodejs在Ubuntu的部署和配置 samba

    在Ubuntu上安装samba 在10.04上安装samba时,先把samba卸载,不然会影响后面的安装. 0.卸载samba sudo apt-get remove samba-common sud ...

  9. js左右大小变化

    点左边左边变大.点右边右边大左边小 <style type="text/css"> *{ margin:0px auto; padding:0px; } #wai{ w ...

  10. VC6中函数点go to definition报告the symbol XXX is undefined

    删除Debug中的bsc文件,再重建所有文件即可,在该函数处点击go to definition会提示重建.bsc文件,如果不行,多操作几次.