72

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

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

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

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

编辑

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

4

8 回答 8

45

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

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


如果你想复制数据,你能得到的最接近的就是你的解决方案。但是copy.deepcopy,除了效率低下,还有一些注意事项,例如

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

[...]

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

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

于 2009-05-10T17:16:08.470 回答
37

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

>>> 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__重写的类来实现。

于 2009-05-10T11:57:47.867 回答
16

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

所以,对我来说,在这个例子中,对列表进行传值的pythonic方法是:

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

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

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

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

无论如何...希望它有所帮助。

于 2012-03-18T22:32:10.410 回答
4

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

于 2009-07-04T12:30:47.993 回答
3

我想不出任何其他pythonic选项。但就我个人而言,我更喜欢更面向对象的方式。

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

def f(data):
  #do stuff

def caller():
  d = TheData()
  f(d.clone())
于 2009-05-10T11:16:43.767 回答
1

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]
于 2015-01-23T11:25:36.847 回答
1

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

import pickle

def f(data):
    data = pickle.loads(pickle.dumps((data)))
    #do stuff
于 2009-07-04T21:11:20.150 回答
0

许多人使用标准库副本。我更喜欢在我的课程中定义__copy__或。__deepcopy__中的方法copy可能存在一些问题。

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

为避免这些失控行为,请通过覆盖__copy____deepcopy__. 亚历克斯的回答举了一个很好的例子。

于 2019-12-30T15:22:23.660 回答