Python 的参数传递规则与 C# 的参数传递规则的主要区别是什么?
我对 Python 非常熟悉,只是开始学习 C#。我想知道我是否可以考虑关于何时通过引用或值传递对象的规则集,对于 C# 来说,它与 Python 中相同,或者是否有一些我需要牢记的关键差异。
Python 的参数传递规则与 C# 的参数传递规则的主要区别是什么?
我对 Python 非常熟悉,只是开始学习 C#。我想知道我是否可以考虑关于何时通过引用或值传递对象的规则集,对于 C# 来说,它与 Python 中相同,或者是否有一些我需要牢记的关键差异。
C# 按值传递参数,除非您指定不同的方式。如果参数类型是结构,则复制其值,否则复制对对象的引用。返回值也是如此。
ref
您可以使用or修饰符修改此行为out
,该修饰符必须在方法声明和方法调用中都指定。两者都将该参数的行为更改为按引用传递。这意味着您不能再传递更复杂的表达式。ref
和之间的区别在于out
,将变量传递给ref
参数时,它必须已经初始化,而传递给out
参数的变量不必初始化。在该方法中,out
参数被视为未初始化的变量,必须在返回前赋值。
Python 总是使用按引用传递的值。也不例外。任何变量赋值都意味着赋值参考值。没有例外。任何变量都是绑定到引用值的名称。总是。
您可以将引用视为目标对象的地址,该地址在使用时会自动取消引用。这样,您似乎可以直接使用目标对象。但中间总有一个参考,多一步跳转到目标。
更新——这是一个证明通过引用传递的通缉示例:
如果参数是按值传递的,则lst
无法修改外部。绿色是目标对象(黑色是里面存储的值,红色是对象类型),黄色是里面有引用值的内存——如箭头所示。蓝色实心箭头是传递给函数的参考值(通过蓝色虚线箭头路径)。丑陋的深黄色是内部字典。(它实际上也可以画成一个绿色的椭圆。颜色和形状只是说它是内部的。)
更新- 与 fgb 对通过引用传递示例swap(a, b)
的评论和 delnan 对不可能编写swap
.
在编译语言中,变量是能够捕获类型值的内存空间。在 Python 中,变量是绑定到引用变量的名称(内部捕获为字符串),该引用变量保存对目标对象的引用值。变量的名称是内部字典中的键,该字典项的值部分存储对目标的引用值。
在其他语言中的目的swap
是交换传递变量的内容,即交换内存空间的内容。这也适用于 Python,但仅限于可以修改的变量——这意味着可以修改其内存空间的内容。这仅适用于可修改的容器类型。从这个意义上说,一个简单的变量总是不变的,即使它的名字可以用于其他目的。
如果函数应该创建一些新对象,那么将其获取到外部的唯一方法是或通过容器类型参数,或通过 Pythonreturn
命令。但是,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
请注意,必须使用多重赋值like来表达列表内容的复制。a[:] = b[:]
将 Python 称为“按值传递”或“按引用传递”语言并与 C、C# 等进行比较的问题在于,Python 对数据的引用方式有不同的概念。Python 不容易适应传统的按值或按引用二分法,导致混淆和“它是按值调用!” “不,是引用式调用,我可以证明!” “不,你栗色,这显然是价值调用!” 上面见证了无限循环。
事实是,两者都不是。Python 使用共享调用(也称为对象调用)。有时这似乎是一种按值策略(例如,在处理像int
、float
和等标量值时str
),有时像按引用策略(例如在处理像list
、dict
、set
和等结构化值时object
)。David Goodger 的Code like a Pythonista 将这一点完美地总结为“其他语言有变量;Python 有名称”。作为奖励,他提供了清晰的图形来说明差异。
在幕后,call-by-sharing 的实现更像是 call-by-reference(正如Noctis Skytower提到的mutatefloat
示例所展示的那样。)但是如果你认为它是 call-by-reference,你就会偏离轨道很快,因为虽然引用是实现,但它们不是暴露的语义。
相比之下,C#使用按值调用或按引用调用——尽管有人可能会争辩说,该out
选项代表了一种超越纯按引用调用的调整,如在 C、Pascal 等中所见。
因此,Python 和 C# 确实非常不同——至少在架构级别上。在实践中,按值和按引用的组合将允许您创建操作非常类似于按共享调用的程序——尽管有一个棘手的小恶魔生活在细节和极端情况中。
如果您有兴趣在比较上下文中了解不同语言的参数传递策略, 维基百科的表达式评估策略页面值得一读。虽然它并不详尽(有很多方法可以给这只特殊的猫剥皮!),但它巧妙地涵盖了一系列最重要的方法,以及一些有趣的不常见变化。
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
都使用关键字,则也支持按引用传递:
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 之间的区别,因此您可以将所有内容都视为 pass-pointer-通过价值并大大简化您的心理模型。
C# 也支持out
参数。它们也是按引用传递的,但保证被调用者永远不会从它们中读取,只会写入,因此调用者不需要事先初始化它们。当您在 Python 中使用元组时,它们用于模拟多个返回值。它们有点像单向传递引用。
不太一样
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