Эмуляция поведения передачи по значению в Python - PullRequest
56 голосов
/ 10 мая 2009

Я хотел бы эмулировать поведение передачи по значению в python. Другими словами, я хотел бы быть абсолютно уверенным, что написанная мной функция не изменяет предоставленные пользователем данные.

Один из возможных способов - использовать глубокое копирование:

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

есть ли более эффективный или более питонический способ достижения этой цели с минимальным количеством предположений о передаваемом объекте (такой как метод .clone ())

Редактировать

Я знаю, что технически все в python передается по значению. Мне было интересно эмулировать поведение, т.е. убедиться, что я не связываюсь с данными, которые были переданы в функцию. Я предполагаю, что самый общий способ - клонировать рассматриваемый объект либо с помощью его собственного механизма клонирования, либо с помощью глубокой копии.

Ответы [ 7 ]

31 голосов
/ 10 мая 2009

Нет питонского способа сделать это.

Python предоставляет очень мало возможностей для принудительного применения таких вещей, как личные данные или данные только для чтения. Философия питона заключается в том, что «мы все взрослые»: в этом случае это означает, что «функция не должна изменять данные» является частью спецификации, но не применяется в коде.


Если вы хотите сделать копию данных, самое близкое, что вы можете получить, - это ваше решение. Но copy.deepcopy, помимо того, что он неэффективен, также имеет предостережения , такие как :

Поскольку глубокая копия копирует все, что может скопировать слишком много, например, административные структуры данных, которые должны совместно использоваться даже между копиями.

[...]

Этот модуль не копирует типы, такие как модуль, метод, трассировка стека, фрейм стека, файл, сокет, окно, массив или любые подобные типы.

Так что я бы порекомендовал его, только если вы знаете, что имеете дело со встроенными типами Python или своими собственными объектами (где вы можете настроить поведение копирования, задав специальные методы __copy__ / __deepcopy__, здесь нет необходимо определить собственный метод clone()).

30 голосов
/ 10 мая 2009

Вы можете создать декоратор и включить в него поведение клонирования.

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

8 голосов
/ 19 марта 2012

Существует только пара встроенных типов, которые работают как ссылки, например list.

Итак, для меня pythonic способ сделать передачу по значению для списка, в этом примере, будет:

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

list1[:] создает новый экземпляр списка list1, и вы можете назначить его новой переменной.

Возможно, вы могли бы написать функцию, которая могла бы получить один аргумент, затем проверить ее тип и в соответствии с этим выполнить встроенную операцию, которая могла бы вернуть новый экземпляр переданного аргумента.

Как я уже говорил ранее, есть только несколько встроенных типов, их поведение похоже на ссылки, списки в этом примере.

В любом случае ... надеюсь, это поможет.

3 голосов
/ 04 июля 2009

обычно при передаче данных во внешний API вы можете гарантировать целостность ваших данных, передавая их как неизменный объект, например, оборачивая ваши данные в кортеж. Это нельзя изменить, если это то, что вы пытались предотвратить с помощью своего кода.

2 голосов
/ 10 мая 2009

Я не могу найти никакой другой вариант pythonic . Но лично я предпочел бы более OO способ.

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

def f(data):
  #do stuff

def caller():
  d = TheData()
  f(d.clone())
1 голос
/ 05 июля 2009

Хотя я уверен, что нет действительно питонского способа сделать это, я ожидаю, что модуль pickle даст вам копии всего, что вы рассматриваете как ценность.

import pickle

def f(data):
    data = pickle.loads(pickle.dumps((data)))
    #do stuff
0 голосов
/ 23 января 2015

В дополнение к ответу 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]
...