在python中模拟按值传递行为


70

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

一种可能的方法是使用深层复制:

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

有没有更有效或更Python的方法来实现此目标,对传递的对象进行尽可能少的假设(例如.clone()方法)

编辑

我知道技术上python中的所有内容都是按值传递的。我对模拟行为很感兴趣,即确保我不会弄乱传递给函数的数据。我猜最一般的方法是使用其自身的克隆机制或Deepcopy来克隆所讨论的对象。


1
您已经做对了。
ismail

3
只是一个术语注释:Python已经使用了值传递。许多值恰好是引用,因此您要按值传递引用。如果Python使用了传递引用,则def f(x):x = 5实际上会更改调用者为调用者传递的表达式的值,但不会。
劳伦斯·贡萨尔维斯

1
@ L.Gonsalves“按值传递”或“按引用传递”均未描述Python的语义。参见例如mail.python.org/pipermail/python-list/2007-July/621112.html
dF。

3
@dF:您链接到的页面是...“困惑”。Python使用按值传递。的确,Python中的所有变量都是对值的引用,但这并不会改变参数传递是按值进行的事实。该页面本身承认Java按值传递,并且Java中的非原始类型存储为引用。如果我们从Java中删除了原始类型,它将不再是按值传递?不会。同样,Python变量始终存储引用的事实与它的参数按值传递的事实正交。
劳伦斯·贡萨尔维斯

8
@劳伦斯,这是一个普遍的误解。实际上,Python都是通过引用传递的。变量赋值是一个指针绑定,它实际上并不更改变量指向的数据结构。像字符串和整数之类的东西似乎是例外,但这并不是真正的例外。它们是不可变的,因此,如果对它们产生任何不同的string / int的任何操作,实际上都会创建新的操作(如果它们尚不存在)。
未知

Answers:


41

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

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


如果要复制数据,则可以找到最接近的解决方案。但是copy.deepcopy,除了是低效的,也有注意事项,例如

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

[...]

该模块不复制诸如模块,方法,堆栈跟踪,堆栈框架,文件,套接字,窗口,数组或任何类似类型的类型。

因此,仅当您知道您正在处理内置的Python类型或您自己的对象时(如果您可以通过定义__copy__/__deepcopy__特殊方法自定义复制行为,而无需定义自己的clone()方法),我才建议这样做。


35

您可以制作一个装饰器,然后在其中放置克隆行为。

>>> 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

这不是最健壮的方法,并且不处理键值参数或类方法(缺少自变量),但是您可以理解。

请注意以下语句是相等的:

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

您甚至可以让装饰器执行某种功能来进行克隆,从而使装饰器的用户可以决定克隆策略。在这种情况下,装饰器可能最好作为具有__call__重写的类来实现。


2
我认为装饰器是解决此问题的一种非常优雅的方法。我确信您可以提出一些可以解决关键字args的问题,而不会带来太多麻烦。
jkp,2009年

是的,也差不多是给新方法一个** kwargs参数,然后复制它们。
Skurmedel

15

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

因此,对我而言,在此示例中,用于传递值(用于列表)的pythonic方法将是:

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

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

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

就像我之前说的,只有几个内置类型,它们的行为就像本示例中的引用列表一样。

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


5
python中的所有内容都按值传递和分配。Python中的所有值都是引用。没有其他类型可以与其他类型不同
newacct 2012年

您的方式天真地完成了我想要的操作。谢谢。
Brian Peterson

4

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


3

我不知道任何其他pythonic选项。但就我个人而言,我更喜欢采用面向对象的方法。

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

def f(data):
  #do stuff

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

谢谢。但是,克隆意味着进行深度复制的定制方法,可能是一种更有效的方法。我个人看到的库中,克隆的对象不如预期的那么深:)。更不用说根本没有克隆方法的类了。因此,基本上,深拷贝是更可移植的。
鲍里斯·哥列里克

(请参阅我对问题的澄清)
鲍里斯·戈里里克

深度复制只是克隆数据的另一种方法。我的代码的真正意义在于,“在调用方内部”确实是决定如何传递参数而不是在函数内部的更好的位置
Sake 2009年

而且,深拷贝绝对不是“更有效”的。当克隆数据的决定是在调用者内部,而您确切知道要传递的数据类型时,则可以使用最有效的方法来实现。(如list [:],dict(dic))。
清酒

我是否正确理解您,clone()/ deepcopy()应该与数据结构一起驻留。然后,您使用获取函数f传递的调用方方法?(在这种情况下,中f缺少参数caller(f)。)您是否不需要首先知道函数的签名?您可以将其设为多变量吗?还是将为每个功能实现新的数据结构?还是将每个函数定义为每个数据对象的成员函数?
gschenk

1

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

import pickle

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

0

根据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]

0

许多人使用标准库副本。我更喜欢在类中定义__copy____deepcopy__。中的方法copy可能有一些问题。

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

为避免这些失控行为,请通过覆盖__copy__和定义您自己的浅/深复制方法__deepcopy__。与Alex的答案给出一个很好的例子。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.