【问题标题】:Pass by object reference good practices通过对象引用的良好做法
【发布时间】:2021-11-30 16:38:37
【问题描述】:

我来自 C++,在使用 Python 编程时我很难获得安全感(例如,拼写错误会导致极难发现错误,但这不是重点)。 在这里,我想了解如何通过坚持良好做法来避免做可怕的事情。

下面的简单函数在 c++ 中非常好,但在 Python 中创建了我只能称之为怪物的东西。

def fun(x): 
    x += 1
    x = x + 1
    return x

当我调用它时

var1 = 1;
print(fun(var1), var1)

var2 = np.array([1]);
print(fun(var2), var2)

我明白了

3 1
[3] [2]

除了缺乏同质行为(这已经很糟糕了),第二种情况特别可怕。外部变量仅由部分指令修改!

我很清楚为什么会这样。所以这不是我的问题。关键是,在构建复杂程序时,我不想对所有这些依赖于上下文和高度隐含的技术细节格外小心。

必须有一些我可以严格遵守的良好做法,以防止我无意中生成上面的代码。我可以想办法,但它们似乎使代码过于复杂,使 C++ 看起来像是一种更高级的语言。

我应该遵循什么好的做法来避免这种怪异现象?

谢谢!

[编辑] 一些澄清:我挣扎的是 Python 做出了一个依赖于类型和依赖于上下文的选择来创建一个临时的。再说一次,我知道规则。然而,在 C++ 中,选择是由程序员完成的,并且在整个函数中都是明确的,而在 Python 中并非如此。 Python 要求程序员了解对参数执行的操作的一些技术细节,以便确定此时 Python 是在处理临时的还是在原始的。

请注意,我构造了一个函数,它既返回值又具有副作用,只是为了说明我的观点。

关键是程序员可能希望编写该函数只是为了产生副作用(没有返回语句),并且在函数的中途 Python 决定构建一个临时函数,因此不应用一些副作用。 另一方面,程序员可能不想要副作用,而是得到一些(并且难以预测的)。

在 C++ 中,上面的处理简单明了。在 Python 中,它是相当技术性的,需要知道什么触发了临时对象的生成,什么不触发。由于我需要向我的学生解释这一点,我想给他们一个简单的规则,以防止他们落入这些陷阱。

【问题讨论】:

  • 由于+= 明确地是一个“就地”运算符,而+ 可以简单地映射到具有结果的函数,您不应该期望它们得到相同的结果。事实上,对于文字类型int,效果是相同的(因为“就地”操作对文字 int 没有真正意义)并不意味着同样适用于像 a 这样的对象类型numpy 数组。最好的做法是远离就地操作符,因为普通/函数式操作符会给你同样的结果。
  • 这能回答你的问题吗? How do I pass a variable by reference?
  • @tevemadar 谢谢,但不是真的。那篇文章解释了为什么会发生这种情况。我已经知道为什么会发生这种情况,并且遵循规则。我在这里要问的是:弄清楚这一点是相当技术性的。还要弄清楚是否以及何时生成临时(隐藏参数)需要对您执行的操作有一些了解。这使得非常简单的程序对于特定类别的程序员来说相当不安全(我正在考虑我的学生)。我可以给他们什么规则来确保他们不会犯这些错误?

标签: python pass-by-reference


【解决方案1】:

避免此类陷阱的良好做法:

  • 修改输入的函数不应返回任何内容(例如list.sort
  • 不修改输入的函数应该返回修改后的值(例如sorted

您的fun 两者兼而有之,这违背了大多数标准库代码和流行的第三方 Python 库所遵循的约定。打破这个“不成文的规则”,就是导致那里特别可怕的结果的原因。

一般来说,最好尽可能保留函数"pure"。纯粹的无状态函数更容易推理,也更容易测试。

使用 Python 编程时的“安全感”来自于拥有良好的测试套件。作为一种解释型和动态编程语言,Python 中的几乎所有事情都发生在运行时。在编译时几乎没有什么可以保护您的——几乎只会发现语法错误。这对灵活性非常有用,例如几乎任何东西都可以在运行时进行猴子补丁。 能力越大,责任越大。 Python 项目的测试代码是库代码的两倍并不罕见。

【讨论】:

    【解决方案2】:

    想到的一个好习惯是命令-查询分离

    一个函数或方法应该只要么计算和返回一些东西,做一些事情,至少在涉及到外部可观察的行为时。

    很少有例外是可以接受的(例如 pop 数据结构的 pop 方法:它返回一些东西,并且做一些事情)但那些往往是在它如此惯用的地方,你不会期望它有任何其他方式。

    当一个函数对其输入值做某事时,这应该是该函数的唯一目的。这样一来,就没有令人讨厌的惊喜了。

    现在对于“原始”类型和更复杂类型之间的不一致行为,最容易进行防御性编码并假定它是一个引用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-08
      • 1970-01-01
      • 2013-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-29
      • 2011-03-16
      相关资源
      最近更新 更多