他山之石,calling by share——python中既不是传址也不是传值

事情是这样的,Python里是传址还是传值令人疑惑,限于本人没有C基础,所以对大家的各类水平层次不一的解答难以确信。

第一个阶段:

在读《python基础教程第二版》的时候感到疑惑,然后群友解答(略敷衍),接着就是知乎上提问(感谢大家的热心回答,但我很晚才收到推送)

虽然是某天早晨睡不着,翻看公众号的时候看见一篇《不要再问 "Python 函数中,参数是传值,还是传引用?" 这种没有意义的问题了》的文章,初步释疑惑(但后来我觉得他的说法虽然形象,但是不准确)

第二个阶段:

在阅读《JavaScript高级程序设计(第2版)》一书的P71遇到问题,书中说JS都是值传递,但举的例子我觉得没有很好地论证他的观点,于是最终得到发现了JS中其实是“call by share”

第三个阶段:

在读《python学习手册》P159,书里提到了共享引用(Shared References)的概念,果然之前JS的官方的说法是正确的。

赋值:

无论对不变类型变量、可变对象赋值

对变量赋新值都会断开对原值的引用,而成为新值的引用

不可变类型

例子1

a = 1b = aa = 3print(a,b)# 3 1

可变类型

例子2

L1 = [1,2,3]L2 = L1L1 = 13print(L1,L2)# 13 [1, 2, 3]L1 = [‘a‘,‘b‘,‘c‘]print(L1,L2)# [‘a‘, ‘b‘, ‘c‘] [1, 2, 3]

对其赋值,就是在原处修改

调用方法修改属性:

L1 = [1,2,3]L2 = L1L1[1] = 13print(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 = 1testImmutable(a)  # 2print(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 = 11def 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里虽然没有对地址的直接操作,但是仍然有间接寻址的概念,即通过引用变量改变其地址所指向的内存区。

https://www.zhihu.com/question/37241658

******************************?******************************

引用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类并没有提供改变自身的方法

他所谓的“改变”其实都是重新绑定到一个新的值

https://www.zhihu.com/question/31203609

******************************?******************************

引用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等多种语言。

http://www.jb51.net/article/60568.htm

******************************?******************************

?引用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.

改变局部参数对象的属性,会影响到外部对象

https://segmentfault.com/a/1190000005177386

******************************?******************************

所以啊,个人的总结就是:

?其实所谓的引用都是值传递,差异出现在:

?基本类型(JavaScript、Java)/不可变类型(Python)

传递值副本

?引用类型(JavaScript、Java)/可变类型(Python)

传递地址——传递地址区别于传递引用

解释成共享传递

:①在函数内给形参赋新值(无论是基本类型还是对象类型),不改变函数外的对象

:②在函数内修改形参的属性,会影响到函数外的对象

null

时间: 2024-10-05 14:03:19

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

python中函数传值与传引用

python中函数整数,字符串,元组都是传值,函数中不会改变其值,其他的会在函数中改变其值 例如传列表: #-*-ecoding:UTF-8 -*- def fun(a):     a[0]="sss";     print a     print id(a)     return ; a=["a","b"]; fun(a); print a; print id(a) 列表中的值已经改变,但列表的地址没有改变

解决linux系统下python中的matplotlib模块内的pyplot输出图片不能显示中文的问题

问题: 我在ubuntu14.04下用python中的matplotlib模块内的pyplot输出图片不能显示中文,怎么解决呢? 解决: 1.指定默认编码为UTF-8: 在python代码开头加入如下代码 import sys reload(sys) sys.setdefaultencoding('utf-8') 2.确认你ubuntu系统环境下拥有的中文字体文件: 在终端运行命令"fc-list :lang=zh",得到自己系统的中文字体 命令输出如下: /usr/share/fon

python中property的使用

记得在cocoa中也有property的概念,python中的property与cocoa里的property好像是差不多的.下面是它的两种用法. 用法一: class test(object): def __init__(self): print "init is calling" self.a=100 def getX(self): print "call getX" return self.a def setX(self,value): print "

迭代器就是重复地做一些事情,可以简单的理解为循环,在python中实现了__iter__方法的对象是可迭代的,实现了next()方法的对象是迭代器,这样说起来有

迭代器就是重复地做一些事情,可以简单的理解为循环,在python中实现了__iter__方法的对象是可迭代的,实现了next()方法的对象是迭代器,这样说起来有点拗口,实际上要想让一个迭代器工作,至少要实现__iter__方法和next方法.很多时候使用迭代器完成的工作使用列表也可以完成,但是如果有很多值列表就会占用太多的内存,而且使用迭代器也让我们的程序更加通用.优雅.pythonic.下边是一个例子,从里边你会感受到不用列表而用迭代器的原因. #!/usr/bin/env python #c

转载:唐磊的个人博客《python中decorator详解》【转注:深入浅出清晰明了】

转载请注明来源:唐磊的个人博客<python中decorator详解> 前面写python的AOP解决方案时提到了decorator,这篇文章就详细的来整理下python的装饰器--decorator. python中的函数即objects 一步一步来,先了解下python中的函数. def shout(word='hello,world'):     return word.capitalize() + '!'print shout()#输出:Hello,world!#跟其他对象一样,你同样

python中的下划线及双下划线

一.Python 用下划线作为变量前缀和后缀指定特殊变量 1. 单下划线开头: _xxx:弱“内部使用”标识,如:”from Module import *”,将不导入所有以下划线开头的对象,包括包.模块.成员 2. 双下划线开头: __xxx:模块内的私有成员,外部无法直接调用. 即:私有类型的变量.只能是允许这个类本身进行访问了.连子类也不可以 3. 双下划线开头和结尾: __xxx__ :系统定义名字, 用户无法控制的命名空间中的“魔术”对象或属性, 如: __name__.__doc__

Python 中的属性访问与描述符

在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作.例如,有一个类实例对象foo,它有一个name属性,那便可以使用foo.name对此属性进行访问.一般而言,点(.)属性运算符比较直观,也是我们经常碰到的一种属性访问方式.然而,在点(.)属性运算符的背后却是别有洞天,值得我们对对象的属性访问进行探讨. 在进行对象属性访问的分析之前,我们需要先了解一下对象怎么表示其属性.为了便于说明,本文以新式类为例.有关新式类和旧式类的区别,大家可以查看Python官方文档.

python中的__enter__ __exit__

我们前面文章介绍了迭代器和可迭代对象,这次介绍python的上下文管理.在python中实现了__enter__和__exit__方法,即支持上下文管理器协议.上下文管理器就是支持上下文管理器协议的对象,它是为了with而生.当with语句在开始运行时,会在上下文管理器对象上调用 __enter__ 方法.with语句运行结束后,会在上下文管理器对象上调用 __exit__ 方法 with的语法: with EXPR as VAR: BLOCK 这是上面语法的伪代码: mgr = (EXPR)

走入计算机的第四十天(python中sockserver模块)

一.Python中的sockserver模块 1.该模块与sock模块不同之处是该模块自动帮我们分装好了一些功能,让我们在编程的时候直接调用这些功能就可以了,节省了编程步骤. 2.如图所示 注释:上图为服务端设置 该模块的操作方法比较死板,我们只要会熟悉的使用他就可以了.