C# vs Python 参数传递

Posted

技术标签:

【中文标题】C# vs Python 参数传递【英文标题】:C# vs Python argument passing 【发布时间】:2012-09-08 10:09:48 【问题描述】:

Python 的参数传递规则与 C# 的参数传递规则的主要区别是什么?

我对 Python 非常熟悉,只是开始学习 C#。我想知道我是否可以考虑关于何时通过引用或通过值传递对象的规则集,对于 C# 来说,它与在 Python 中相同,或者是否有一些我需要牢记的关键差异。

【问题讨论】:

在python中通过引用传递,从什么时候开始???? Python 按值传递对对象的引用:link 我知道你一定在谈论一些列表附加,我没有看到链接,但我想它一定是...... @Max:对。但是传递引用值仅仅意味着通过引用传递。简单来说就是传递了4个字节,字节就是引用传递的对象的地址。 @pepr No,这不是通过引用传递的。 “按引用传递”中的“引用”指的是不同的概念,即允许您操作存储位置(变量、对象属性、集合中的槽等)的东西,并且在 Python 中不存在。通过引用允许def f(x): x = whatever 生效。 【参考方案1】:

C# 按值传递参数,除非您指定不同的方式。如果参数类型是结构,则复制其值,否则复制对对象的引用。返回值也是如此。

您可以使用refout 修饰符修改此行为,该修饰符必须在方法声明和方法调用中都指定。两者都将该参数的行为更改为按引用传递。这意味着您不能再传递更复杂的表达式。 refout 之间的区别在于,将变量传递给ref 参数时,它必须已经初始化,而传递给out 参数的变量不必初始化。在该方法中,out参数被视为未初始化的变量,必须在返回前赋值。

【讨论】:

关于 ref,OP 可能会觉得 this question 很有趣。 之所以选为答案,是因为它是唯一一个告诉我如何在 C# 中传递参数的工作原理。【参考方案2】:

将 Python 称为“按值传递”或“按引用传递”语言并与 C、C# 等相比的问题在于,Python 对数据的引用方式有不同的概念。 Python 不容易适应传统的按值或按引用二分法,导致混淆和“它是按值调用!” “不,是引用式调用,我可以证明!” “不,你栗色,这显然是价值调用!”上面见证了无限循环。

事实是,两者都不是。 Python 使用共享调用(也称为对象调用)。有时这似乎是一种按值策略(例如,在处理像 intfloatstr 这样的标量值时),有时像按引用策略(例如,在处理像 @987654329 这样的结构化值时) @、dictsetobject)。 David Goodger 的Code like a Pythonista 将其完美地总结为“其他语言有变量;Python 有名称”。作为奖励,他提供了清晰的图形来说明差异。

在幕后,共享调用更像是引用调用(正如Noctis Skytower 提到的mutate a float example 所展示的那样。)但是如果你认为它是引用调用,你会迅速偏离轨道,因为虽然引用是实现,但它们不是暴露的语义。

相比之下,C# 是 uses either call-by-value or call-by-reference--尽管有人可能会争辩说 out 选项代表了一种超越纯引用调用的调整,如 C、Pascal 等中所见。

因此,Python 和 C# 确实非常不同——至少在架构级别上。在实践中,按值和按引用的组合将允许您创建运行方式与共享调用非常相似的程序——尽管有一个棘手的小恶魔生活在细节和极端情况中。

如果您有兴趣在比较上下文中了解不同语言的参数传递策略, Wikipedia's page on expression evaluation strategy 值得一读。虽然它并不详尽(有许多种方法可以给这只特殊的猫剥皮!),但它巧妙地涵盖了一系列最重要的方法,以及一些有趣的不常见变化。

【讨论】:

call-by-sharing 只是 Barbara Liskov 为特定类型的 pass-by-value 给出的名称(其中传递的值是 always 隐含的指针)在 CLU 中实现,后来在 Smalltalk、Objective-C、Java、C#、Python、Ruby、Self、Newspeak、Io、Ioke、Seph 等语言中实现。共享调用值传递。它也是 Java 和默认情况下 C# 使用的,至少对于引用类型。 我的 +1 @JörgWMittag:问题在于您考虑变量并在传递地址后为其命名。但地址不是参数值。此外,当传递占用大量内存的参数时,pass-by-value 意味着复制大量内存。从历史上看,通过引用传递是为了避免复制,或者通过修改传递的变量来允许输出。 当然,Python 是按值调用的。与 all 引用调用实际上是值调用完全相同的方式。在 CPU 寄存器级别,一切都是值。甚至参考。 “哈!按值调用!按值调用!QED!”接下来,让我们讨论一下,对象只不过是图灵机上的糖衣。转念一想——不。让我们严肃点。当我们为不同的意图和效果组织事物时,我们给它们不同的名称来表示不同的语义和结果。 Call-by-reference,sharing,copy-back,etc. 不能有效地简化为 call-by-value。 Python 和 C#(不使用 refout)表现出完全相同的语义。那么,为什么 C# 是按值传递而 Python 不是呢? @JonathanEunice:感谢您添加对 Python 实际工作原理的解释。意识到传递引用实际上是传递值的一种形式,这是一个很好的见解。正如您所指出的,它“不能有效地简化为按值调用。”【参考方案3】:

Python 总是使用传递引用值。也不例外。任何变量赋值都意味着赋值参考值。没有例外。任何变量都是绑定到引用值的名称。总是。

您可以将引用视为在使用时自动取消引用的目标对象的地址。这样,您似乎可以直接使用目标对象。但中间总有一个引用,多一步跳转到目标。

更新 -- 这是一个证明通过引用传递的通缉示例:

如果参数是按值传递的,则无法修改外部lst。绿色是目标对象(黑色是里面存储的值,红色是对象类型),黄色是里面有引用值的内存——如箭头所示。蓝色实心箭头是传递给函数的参考值(通过蓝色虚线箭头路径)。丑陋的深黄色是内部字典。 (实际上也可以画成绿色椭圆。颜色和形状只说它是内部的。)

更新 -- 与 fgb 关于通过引用传递示例 swap(a, b) 的评论和 delnan 关于不可能编写 swap 的评论有关。

在编译语言中,变量是能够捕获类型值的内存空间。在 Python 中,变量是绑定到引用变量的名称(内部捕获为字符串),该引用变量保存目标对象的引用值。变量的名称是内部字典中的键,该字典项的值部分存储对目标的引用值。

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

请注意,现在var1var2 模拟了经典语言中“正常”变量的外观。 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[:]必须用于表示复制列表的内容

【讨论】:

投反对票的人是恶意的或错误的,或者他/她不懂 Python。可以通过例子证明我说的是真的。但是,您不能证明 Python 是按值传递的。 @JonSkeet:尽管引用的问题另有说明,但它非常令人困惑。通过引用传递是否意味着传递引用值,这是一个文字游戏。当然。但是引用值只是对象的地址。通过引用传递意味着传递对象的地址。这种方式也在 Python 中使用。将参数传递给函数实际上意味着将函数的局部变量从外部分配给事物。这是与 Python 中的任何其他赋值相同的赋值。 如果它与任何其他赋值相同,那么它就是按值传递。引用传递意味着传递的/局部变量是相互引用。无论使用哪个变量,对变量的任何更改(包括赋值)都具有相同的效果。 @pepr:您写道:“Python 中的任何赋值都意味着复制对目标对象的引用。”嗯,这就是 pass-by-value 的定义 @pepr:因为引用会按值传递——这正是 C# 和 Java 的工作方式。老实说,我得出的结论是“按值传递”和“按引用传递”的传统术语可能对 Python 毫无帮助。很容易看出它们在 C# 和 Java 中是如何工作的(作为我熟悉的示例),因为变量的值是非常明确地定义的——而在 Python 中有绑定和变量,听起来它们是不同的东西,引入混乱......【参考方案4】:

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 的区别,所以你可以直接对待 一切 都是按值传递指针,大大简化了你的心智模型。

C# 也支持out 参数。它们也是按引用传递的,但保证被调用者永远不会从它们中读取,只会写入,因此调用者不需要事先初始化它们。当您在 Python 中使用元组时,它们用于模拟多个返回值。它们有点像单向传递引用。

【讨论】:

但请记住,即使 Python 使用按值传递,如果输入参数是复杂类型(如 dict),您也可以更改输入参数。在您的函数中更改它们将更改原始值。 @Gregor:嗯,这只是可变状态。如果你改变一个可变对象,它就会改变,这并不奇怪。 Python 不是纯粹的函数式语言,C# 也不是。 @JörgWMittag:Python 从不使用按值传递。反之亦然。 @Max:引用类型(类的实例)和值类型(结构的实例)之间的区别不是如何传递它们,而是什么 已传递:对于引用类型,传递的 是(副本)指向对象的 指针,对于值类型,传递的值是(副本)对象本身。这与按值传递与按引用传递完全正交:您可以按值传递引用类型(默认)或按引用传递值类型。 在python中,值是通过引用传入的。 Python assignment 不会替换引用的对象,它会创建一个新的引用并存储后者。 Python 值(对象)本身存在于堆中。【参考方案5】:

不一样

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

【讨论】:

以上是关于C# vs Python 参数传递的主要内容,如果未能解决你的问题,请参考以下文章

asp.net(vs2008 c# 中) 如何在跳转的页面传递参数?

将多个参数从 c# 传递给 python

值传递和引用传递

将零参数作为参数传递——行为是在哪里定义的?

c# 参数传递方式?

C#,Process.Start() 传递多参数问题。