【问题标题】:Python: how to check if an argument's type is such that it will be passed by value or by reference?Python:如何检查参数的类型是按值传递还是按引用传递?
【发布时间】:2012-11-16 06:02:06
【问题描述】:

似乎原子类型(int、string、...)是通过值传递的,而所有其他类型(对象、指向函数的指针、指向方法的指针,...)都是通过引用传递的。

检查变量是按值传递还是按引用传递的最佳方法是什么?

isinstance(input_, float) or isinstance(input_, basestring) or <...>

似乎很不雅。

我需要它的原因如下:我有一个包装wx.Button的类,如果args/kwargs是按值传递的类型,则不会考虑在其他对象中更新它们的值。所以一些检查将是有益的

class PalpyButton(wx.Button):        
    def __init__(self, parent, btnLabel, handler, successMsg = None, args = (), kwargs = {}):
        super(PalpyButton, self).__init__(parent, -1, btnLabel)
        self.handler = handler
        self.successMsg = successMsg
        parent.Bind(wx.EVT_BUTTON, lambda event: self.onClick(event, *args, **kwargs), self)
    def onClick(self, event, *args, **kwargs):
        try:
            self.handler(*args, **kwargs)
            if self.successMsg != None:
                if hasattr(self.successMsg, '__call__'):
                    showInfoMessageBox(self.successMsg())
                else:
                    showInfoMessageBox(self.successMsg)
        except BaseException, detail:
            showErrorMessageBox(detail)

【问题讨论】:

  • 如果将相同的变量保留在 2 个“地方”,如果通过引用传递的变量在第一个“地方”发生更改,则它们不会在第二个“地方”更新,这将导致问题。通过引用传递的变量将被更新,并且可以正常工作。所以一些检查会是有益的......
  • 你必须检查传递的对象的类型,看看它是否是一个可变容器。当然,这可能是您的代码一无所知的事情……,但如果是这样,那会告诉您。
  • @Martijn Pieters :我需要它用于 ain 接口类,这个类使用 wx。我认为提到 wx 会使我的问题过于狭窄。

标签: python types pass-by-reference pass-by-value


【解决方案1】:

这个问题没有出现。 一切都以完全相同的方式传递,无论类型、值、操作系统、月相等如何。

是否最好将这种参数传递方式描述为按值传递、按引用传递或其他方式,还有待商榷(但请注意,使用“按值/引用传递”需要对“值”进行非标准定义/“引用”,这是其他术语(例如“通过对象”)的主要推理)。在这里没关系。它总是一样的。

编辑:就您的示例而言,handler 的语义不会根据参数类型而改变。要么分配给变量(AKA 名称):

def my_handler(x):
    x = <whatever>

... 但是my_handler(&lt;anything&gt;) 不会更改参数所指的对象(如果您传入局部变量、对象属性、更复杂表达式的结果或其他任何内容都没有关系别的)。顺便说一句,一切都是一个对象,你的人为区分(“原子”、“函数指针”)是没有意义的。

或者,该函数(尝试)更改对象的值(例如,向集合添加/删除项目,更改对象属性,调用更改某些可观察状态的方法间接)。如果对象不支持它(例如调用不存在的方法),这可能会引发异常,但任何访问都会发生这种情况,而不仅仅是突变。如果对象的值确实发生了变化,那么通过 每个 对该对象的引用,这总是可见。由于在许多情况下会创建对现有对象的新引用(例如参数传递、变量赋值、属性赋值等),您可以通过在一个地方更改其值并在另一个地方观察更改来观察对象不会被复制.这适用于所有对象(同样,前提是您可以更改对象的值)。

现在,有些对象是不可变的,这(根据定义)意味着您无法更改它们的值,因此无法以这种方式观察对象“共享”(尽管您可以通过其他方式观察它)。这并不意味着赋值根据可变性具有不同的语义(它没有),它只是意味着你可以用可变对象做你不能用不可变对象做的事情。您可以对两者执行的操作对两者的工作方式相同。

【讨论】:

  • 如果将相同的变量保留在 2 个“地方”,如果通过引用传递的变量在第一个“地方”发生更改,则它们不会在第二个“地方”更新,这将导致问题。通过引用传递的变量将被更新,并且可以正常工作。所以一些检查会是有益的......我可能应该在我最初的问题中提供更多细节......
  • @delnan:是的,我知道,我需要它的原因不同。
  • @YuccaV 那是错误的。没有这样的区别。如果您认为自己有证据,请出示,以便我们指出错误。
  • @delnan:不确定我的评论中有什么错误;我只是解释问我问题的原因,没有发表任何声明。我在最初的问题中提供了更多细节,希望现在更清楚。
  • @YuccaV 您认为存在差异的假设是错误的。我将进行编辑以解决您的具体示例。
【解决方案2】:

如果你确实需要改变传递给你的变量的值,你可以把它包装在一个数组中,实现过程中的引用传递:

def increaseInt(i_var):
  i_var[0] += 1

i_ref = [42]
increase(i_ref)
print i_ref[0]  # will print 43

在某些情况下,这可能是解决像您这样的问题的(有问题的)解决方案。

但一般来说,所有值在 Python 中都是通过引用传递的(在某种意义上,不会创建副本);有些值是不可变的(如整数、字符串、元组……)。您可能会对为参数变量分配一个新值感到困惑——这绝对不会改变之前在参数变量中的值,它只是用一个新值覆盖它(因此删除了对原始值的额外引用)。这就是为什么上面的例子没有给i_var分配任何东西,而是给i_var[0]分配了东西。

【讨论】:

  • 任何不赞成票都应附有说明反对票原因的评论(这应该与解决方案本身中已经命名的方面不同)。我知道并写道,这个解决方案有缺点,但它绝对可以在无法选择其他选项的情况下使用,即使它很丑陋并且有点 C-ish。
猜你喜欢
  • 2013-10-24
  • 2016-12-18
  • 2016-11-28
  • 2017-11-14
相关资源
最近更新 更多