在 python 中模拟传值行为

Posted

技术标签:

【中文标题】在 python 中模拟传值行为【英文标题】:Emulating pass-by-value behaviour in python 【发布时间】:2010-10-25 02:41:09 【问题描述】:

我想模拟 python 中的按值传递行为。换句话说,我想绝对确保我编写的函数不会修改用户提供的数据。

一种可能的方法是使用深拷贝:

from copy import deepcopy
def f(data):
    data = deepcopy(data)
    #do stuff

有没有更有效或更pythonic的方式来实现这个目标,对传递的对象做尽可能少的假设(比如.clone()方法)

编辑

我知道从技术上讲,python 中的所有内容都是按值传递的。我对模拟行为很感兴趣,即确保我不会弄乱传递给函数的数据。我想最通用的方法是使用自己的克隆机制或使用 deepcopy 克隆有问题的对象。

【问题讨论】:

你已经做对了。 只是一个术语说明:Python 已经使用按值传递。许多值恰好是引用,因此您按值传递引用。如果 Python 使用传递引用,那么 def f(x): x = 5 实际上会改变调用者为调用者传递的表达式的值,但它不会。 @L.Gonsalves “pass-by-value”或“pass-by-reference”都没有描述 Python 的语义。参见例如mail.python.org/pipermail/python-list/2007-July/621112.html @dF:您链接到的页面是......“困惑”。 Python 使用按值传递。 Python 中的所有变量都是对值的引用,这也是事实,但这并不能改变参数传递是按值传递的事实。页面本身承认 Java 按值传递,Java 中的非原始类型存储为引用。如果我们从 Java 中删除原始类型,它会不再是按值传递吗?不。同样,Python 变量总是存储引用的事实与它的参数传递是按值传递的事实是正交的。 @Laurence,这是一个常见的误解。 Python实际上都是通过引用传递的。变量赋值是一种指针绑定,它实际上并没有改变变量指向的数据结构。似乎是例外的字符串和整数之类的东西并不是真正的例外。它们是不可变的,因此对它们产生不同字符串/整数的任何操作实际上都会创建新的,如果它们不存在的话。 【参考方案1】:

很多人使用标准库copy。我更喜欢在我的课程中定义__copy____deepcopy__copy 中的方法可能存在一些问题。

    浅拷贝将保留对原始对象的引用,而不是创建新对象。 deepcopy 会递归运行,有时可能会导致死循环。如果没有足够的注意力,内存可能会爆炸。

为避免这些失控行为,请通过覆盖__copy____deepcopy__ 来定义您自己的浅/深复制方法。而Alex's answer 就是一个很好的例子。

【讨论】:

【参考方案2】:

user695800 的回答的基础上,通过值传递可能使用 [:] 运算符的列表

def listCopy(l):
    l[1] = 5
    for i in l:
        print i

调用

In [12]: list1 = [1,2,3,4]

In [13]: listCopy(list1[:])
1
5
3
4

list1
Out[14]: [1, 2, 3, 4]

【讨论】:

【参考方案3】:

只有几个内置类型可以用作引用,例如 list

所以,对我来说,在这个例子中,对于列表,按值传递的 Pythonic 方式是:

list1 = [0,1,2,3,4]
list2 = list1[:]

list1[:] 创建 list1 的新实例,您可以将其分配给新变量。

也许您可以编写一个可以接收一个参数的函数,然后检查其类型,并根据该结果执行一个内置操作,该操作可以返回所传递参数的新实例。

正如我之前所说,只有少数内置类型,它们的行为类似于引用,在这个例子中是列表。

任何方式...希望它有所帮助。

【讨论】:

python 中的一切都是按值传递和赋值的。 Python 中的所有值都是引用。没有任何类型的工作方式与其他类型不同 你的方式天真地做了我想做的事。谢谢。【参考方案4】:

虽然我确信没有真正的 Pythonic 方法可以做到这一点,但我希望 pickle 模块会为您提供任何业务视为价值的所有内容的副本。

import pickle

def f(data):
    data = pickle.loads(pickle.dumps((data)))
    #do stuff

【讨论】:

【参考方案5】:

通常在将数据传递给外部 API 时,您可以通过将数据作为不可变对象传递来确保数据的完整性,例如将数据包装到元组中。如果这是您试图通过代码阻止的,则无法修改。

【讨论】:

【参考方案6】:

没有pythonic的方法可以做到这一点。

Python 为强制诸如私有或只读数据之类的事物提供了非常少的工具。 pythonic 的哲学是“我们都是同意的成年人”:在这种情况下,这意味着“函数不应更改数据”是规范的一部分,但并未在代码中强制执行。


如果您想复制数据,您可以获得的最接近的解决方案就是您的解决方案。但是copy.deepcopy,除了效率低下,也有注意事项such as:

因为深拷贝复制了它可能复制过多的所有内容,例如,即使在副本之间也应该共享的管理数据结构。

[...]

此模块不会复制模块、方法、堆栈跟踪、堆栈帧、文件、套接字、窗口、数组等类型或任何类似类型。

所以我只推荐它,如果你知道你正在处理内置的 Python 类型或你自己的对象(你可以通过定义 __copy__ / __deepcopy__ 特殊方法来自定义复制行为,没有需要定义自己的clone() 方法)。

【讨论】:

如果您必须使用deepcopy,实现您自己的__deepcopy__ 通常也会通过多种因素加快对deepcopy 的调用。【参考方案7】:

您可以制作一个装饰器并将克隆行为放入其中。

>>> def passbyval(func):
def new(*args):
    cargs = [deepcopy(arg) for arg in args]
    return func(*cargs)
return new

>>> @passbyval
def myfunc(a):
    print a

>>> myfunc(20)
20

这不是最健壮的方式,并且不处理键值参数或类方法(缺少 self 参数),但你明白了。

注意以下语句是相等的:

@somedecorator
def func1(): pass
# ... same as ...
def func2(): pass
func2 = somedecorator(func2)

您甚至可以让装饰器采用某种功能来进行克隆,从而允许装饰器的用户决定克隆策略。在这种情况下,装饰器可能最好实现为覆盖 __call__ 的类。

【讨论】:

我认为装饰器是解决这个问题的一种非常优雅的方法。我相信您也可以想出一些可以轻松应对关键字 args 的东西。 是的,这或多或少是给新方法一个 **kwargs 参数并复制它们的问题。【参考方案8】:

我想不出任何其他 pythonic 选项。但我个人更喜欢 OO 方式。

class TheData(object):
  def clone(self):  """return the cloned"""

def f(data):
  #do stuff

def caller():
  d = TheData()
  f(d.clone())

【讨论】:

谢谢。然而,克隆意味着一种定制的、可能更有效的深度复制方式。我个人看到克隆对象没有预期的那么深的库:)。更不用说完全没有克隆方法的类了。所以,基本上 deepcopy 更便携。 (见我对问题的澄清) 深拷贝只是克隆数据的另一种方式。我的代码中真正的要点是“在调用者内部”确实是决定如何传递参数的更好地方,而不是在函数内部 另外,deepcopy 绝对不是“更高效”。当克隆数据的决定是在您确切知道要传递的数据类型的调用者内部时,您可以使用最有效的方法来执行此操作; (如 list[:], dict(dic)).. 如果您不确定将传递哪些数据,那么将有效克隆方法的决定保留给数据对象本身将始终产生更好的性能。 我是否理解正确,clone()/deepcopy() 应该与数据结构一起存在。然后你使用一个调用者方法来获取一个函数 f 传递? (在这种情况下,caller(f) 中缺少 f 参数。)您不需要先知道函数的签名吗?你能把它做成通用的多元变量吗?或者你会为每个函数实现一个新的数据结构?还是将每个函数定义为每个数据对象的成员函数?

以上是关于在 python 中模拟传值行为的主要内容,如果未能解决你的问题,请参考以下文章

前端模拟后台Format方法,便于前后台交互时间传值转换问题

在Python函数中如何多类型传值与递归调用

Python传值与传址

Python传值与传址

python函数传参是传值还是传引用?

Python函数中多类型传值和冗余参数及函数的递归调用