Python的参数传递规则与C#的参数传递规则有什么主要区别(如果有的话)?
我对Python非常熟悉,只是开始学习C#。我想知道我是否能够想到规则集是关于何时通过引用传递对象或者通过值传递C#与Python中的相同,或者如果存在一些关键差异我需要牢记。
答案 0 :(得分:6)
C#按值传递参数,除非您指定不同的方式。如果参数类型是结构,则复制其值,否则将复制对该对象的引用。返回值也是如此。
您可以使用ref
或out
修饰符修改此行为,必须在方法声明和方法调用中指定。两者都将该参数的行为更改为pass-by-reference。这意味着您无法再传递更复杂的表达式。 ref
和out
之间的区别在于,当将变量传递给ref
参数时,它必须已经初始化,而传递给out
参数的变量不能必须初始化。在该方法中,out
参数被视为未初始化的变量,并且必须在返回之前为其分配值。
答案 1 :(得分:3)
Python总是使用传递引用值。没有例外。任何变量赋值意味着分配参考值。没有例外。任何变量都是绑定到参考值的名称。总是
您可以考虑引用关于使用时自动解除引用的目标对象的地址。这样,您似乎可以直接使用目标对象。但总会有一个参考,更多的是跳到目标。
已更新 - 这是一个通过引用传递的通缉示例:
如果参数是按值传递的,则无法修改外部lst
。绿色是目标对象(黑色是存储在内部的值,红色是对象类型),黄色是内部具有参考值的内存 - 绘制为箭头。蓝色实线箭头是传递给函数的参考值(通过虚线蓝色箭头路径)。 uggly dark yellow是内部词典。 (它实际上也可以画成绿色椭圆。颜色和形状只表示它是内部的。)
更新 - 与fgb关于通过引用示例swap(a, b)
传递的评论以及delnan关于编写swap
的不可能性的评论相关。
在编译语言中,variable是一个能够捕获类型值的内存空间。在Python中,variable是绑定到引用变量的名称(在内部捕获为字符串),该引用变量保存目标对象的引用值。变量的名称是内部字典中的键,该字典项的值部分将参考值存储到目标。
其他语言中swap
的目的是交换传递的变量的内容,即交换内存空间的内容。这也可以用于Python,但仅适用于可以修改的变量 - 这意味着可以修改其内存空间的内容。这仅适用于可修改的容器类型。在这个意义上,一个简单的变量总是不变的,即使它的名字可以重复用于其他目的。
如果函数应该创建一个新对象,那么将它传递到外面的唯一方法是或通过容器类型参数,或通过Python return
命令。但是,Python return
在语法上看起来好像能够传递多个参数。实际上,传递给外部的多个值形成一个元组,但元组可以在语法上分配给更多外部Python变量。
更新。存储空间由单元素列表模拟 - 即一个更多级别的间接。然后swap(a, b)
可以用其他语言编写。唯一奇怪的是我们必须使用列表元素作为模拟变量值的引用。以这种方式模拟其他语言变量的必要性的原因是只有容器(它们的一个子集)是Python中唯一可以修改的对象:
>>> def swap(a, b):
... x = a[0]
... a[0] = b[0]
... b[0] = x
...
>>> var1 = ['content1']
>>> var2 = ['content2']
>>> var1
['content1']
>>> var2
['content2']
>>> id(var1)
35956296L
>>> id(var2)
35957064L
>>> swap(var1, var2)
>>> var1
['content2']
>>> var2
['content1']
>>> id(var1)
35956296L
>>> id(var2)
35957064L
请注意,现在var1
和var2
模拟了经典语言中“普通”变量的外观。 swap
更改了内容,但地址保持不变。
对于可修改的对象 - 例如列表 - 您可以编写与其他语言完全相同的swap(a, b)
:
>>> def swap(a, b):
... x = a[:]
... a[:] = b[:]
... b[:] = x[:]
...
>>> lst1 = ['a1', 'b1', 'c1']
>>> lst2 = ['a2', 'b2', 'c2']
>>> lst1
['a1', 'b1', 'c1']
>>> lst2
['a2', 'b2', 'c2']
>>> id(lst1)
35957320L
>>> id(lst2)
35873160L
>>> swap(lst1, lst2)
>>> lst1
['a2', 'b2', 'c2']
>>> lst2
['a1', 'b1', 'c1']
>>> id(lst1)
35957320L
>>> id(lst2)
35873160L
请注意,必须使用多次分配(如a[:] = b[:]
)来表示复制列表的内容。
答案 2 :(得分:3)
将Python称为“按值传递”或“传递引用”语言并与C,C#等进行比较的问题在于,Python对数据的引用方式有不同的概念。 Python不容易融入传统的按值或参考二分法,导致混淆和“它是按值调用!” “不,这是参考号,我可以证明这一点!” “不,你是MAROON,它显然是按价值呼叫!”上面见证了无穷无尽的循环。
事实是,它既不是。 Python使用call-by-sharing(也称为call-by-object)。有时,这似乎是一种按价值策略(例如,在处理标量值时,如int
,float
和str
),有时候像是通过参考策略(例如,在处理时)具有结构化值,例如list
,dict
,set
和object
)。 David Goodger的Code like a Pythonista总结得很漂亮,因为“其他语言有变量; Python有名称。”作为奖励,他提供了清晰的图形来说明差异。
在幕后,分享呼叫的实施更像是按引用引用(正如mutate a float
example所提到的Noctis Skytower所示。)但如果您将其视为呼叫 - 请 - 参考,你将快速走出轨道,因为虽然引用是实现,但它们不是暴露的语义。
out
选项代表了一种超越纯粹的引用调整的调整,如C,Pascal等所示。
所以Python和C#确实非常不同 - 无论如何在架构层面上都是如此。在实践中,按值和按引用的组合将允许您创建与分享呼叫非常相似的程序 - 尽管有一个棘手的小恶魔生活在细节和角落案件中。
如果您有兴趣在比较背景下理解不同语言的参数传递策略, Wikipedia's page on expression evaluation strategy值得一读。虽然它并非详尽无遗(有很多方法可以让这只特别的猫皮肤!),它可以很好地涵盖一系列最重要的猫咪,以及一些有趣的罕见变化。
答案 3 :(得分:2)
Python始终是按值传递:
def is_python_pass_by_value(foo):
foo[0] = 'More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.'
foo = ['Python is not pass-by-reference.']
quux = ['Yes, of course, Python *is* pass-by-value!']
is_python_pass_by_value(quux)
print(quux[0])
# More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.
默认情况下,C#是按值传递,但如果在方法声明站点和呼叫站点使用ref
关键字,则C#也支持传递引用:
struct MutableCell
{
public string value;
}
class Program
{
static void IsCSharpPassByValue(string[] foo, MutableCell bar, ref string baz, ref MutableCell qux)
{
foo[0] = "More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
foo = new string[] { "C# is not pass-by-reference." };
bar.value = "For value types, it is *not* call-by-sharing.";
bar = new MutableCell { value = "And also not pass-by-reference." };
baz = "It also supports pass-by-reference if explicitly requested.";
qux = new MutableCell { value = "Pass-by-reference is supported for value types as well." };
}
static void Main(string[] args)
{
var quux = new string[] { "Yes, of course, C# *is* pass-by-value!" };
var corge = new MutableCell { value = "For value types it is pure pass-by-value." };
var grault = "This string will vanish because of pass-by-reference.";
var garply = new MutableCell { value = "This string will vanish because of pass-by-reference." };
IsCSharpPassByValue(quux, corge, ref grault, ref garply);
Console.WriteLine(quux[0]);
// More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.
Console.WriteLine(corge.value);
// For value types it is pure pass-by-value.
Console.WriteLine(grault);
// It also supports pass-by-reference if explicitly requested.
Console.WriteLine(garply.value);
// Pass-by-reference is supported for value types as well.
}
}
正如您所看到的,如果没有使用ref
关键字的显式注释,C#就像Python一样完全 。值类型是值传递,其中传递的值是对象本身,引用类型是值传递,其中传递的值是指向对象的指针(也称为逐个对象共享)。
Python不支持可变值类型(可能是一件好事),因此无法观察pass-value-by-value和pass-pointer-by-value之间的区别,因此您只需要处理一切作为按值传递指针,大大简化了你的心理模型。
C#还支持out
个参数。它们也是传递引用,但保证被调用者永远不会从它们读取,只能写入,因此调用者不需要事先初始化它们。当您在Python中使用元组时,它们用于模拟多个返回值。它们有点像单向传递。
答案 4 :(得分:0)
没有那么不同
def func(a,b):
a[0]=5 #Python
b=30
public int func( ref int a,out int b,int d)
{
a++;b--; //C#
}
x=[10]
y=20
func(20,30) #python
print x,y #Outputs x=[5],y=20 Note:I have used mutable objects.Not possible with int.
int x=10,y=20;
func(ref x,out y,18); //C#
Console.Writeline("x={0} y={1}",x,y);//Outputs x=11,y=19