Эмуляция поведения передачи по значению в Python

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

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

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

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

Изменить

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


person Boris Gorelik    schedule 10.05.2009    source источник
comment
Вы уже делаете это правильно.   -  person ismail    schedule 10.05.2009
comment
Просто примечание по терминологии: Python уже использует передачу по значению. Многие значения просто являются ссылками, поэтому вы передаете ссылки по значению. Если Python использовал передачу по ссылке, тогда def f (x): x = 5 фактически изменил бы значение выражения, которое вызывающий объект передавал для вызывающего, но это не так.   -  person Laurence Gonsalves    schedule 10.05.2009
comment
@ L.Gonsalves Ни передача по значению, ни по ссылке не описывают семантику Python. См., Например, mail.python.org/pipermail/python-list /2007-July/621112.html   -  person dF.    schedule 10.05.2009
comment
@dF: страница, на которую вы ссылаетесь, ... запуталась. Python использует передачу по значению. Также верно, что все переменные в Python являются ссылками на значения, но это не меняет того факта, что передача параметров осуществляется по значению. Сама страница подтверждает, что Java передает по значению, а непримитивные типы в Java хранятся как ссылки. Если бы мы удалили из Java примитивные типы, разве они перестали бы передаваться по значению? Нет. Точно так же тот факт, что переменные Python всегда хранят ссылки, ортогонален тому факту, что его параметры передаются по значению.   -  person Laurence Gonsalves    schedule 10.05.2009
comment
@ L.Gonsalves Я понимаю, что вы имеете в виду, и согласен, что страница, на которую я указал, не очень ясна. Я предполагаю, что моя точка зрения заключается в том, что если вы пришли из такого языка, как C, передача по значению подразумевает, что создается копия данных, чего Python никогда не делает.   -  person dF.    schedule 11.05.2009
comment
@ Лоуренс, это распространенное заблуждение. На самом деле Python все передается по ссылке. Назначение переменной - это привязка указателя, она фактически не меняет структуру данных, на которую указывает переменная. Такие вещи, как строки и целые числа, которые кажутся исключением, на самом деле не являются исключениями. Они неизменяемы, и поэтому любые операции с ними, которые дают другую строку / int, фактически создают новые, если они еще не существуют.   -  person Unknown    schedule 13.05.2009


Ответы (8)


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

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


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

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

[...]

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

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

person dF.    schedule 10.05.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__.

person Skurmedel    schedule 10.05.2009
comment
Я думаю, что декоратор - действительно элегантный подход к решению этой проблемы. Я уверен, что вы могли бы придумать что-то, что могло бы справиться и с аргументами ключевого слова без особых проблем. - person jkp; 10.05.2009
comment
Да, это более или менее вопрос в том, чтобы дать новому методу аргумент ** kwargs и скопировать его. - person Skurmedel; 10.05.2009

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

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

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

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

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

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

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

person Community    schedule 18.03.2012
comment
все в python передается и присваивается по значению. все значения в Python являются ссылками. нет типов, которые работают иначе, чем другие - person newacct; 19.03.2012
comment
Ваш путь наивно сделал то, что я хотел. Спасибо. - person Brian Peterson; 18.04.2013

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

person Tom    schedule 04.07.2009

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

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

def f(data):
  #do stuff

def caller():
  d = TheData()
  f(d.clone())
person Sake    schedule 10.05.2009
comment
Спасибо. Однако клонирование подразумевает индивидуальный и, возможно, более эффективный способ выполнения глубокого копирования. Я лично видел библиотеки, в которых клонированные объекты оказались не такими глубокими, как ожидалось :). Не говоря уже о классах без метода клонирования. Итак, в основном deepcopy более портативен. - person Boris Gorelik; 10.05.2009
comment
(см. мое пояснение к вопросу) - person Boris Gorelik; 10.05.2009
comment
Deepcopy - это просто еще один способ клонировать данные. Реальный момент в моем коде заключается в том, что внутри вызывающего действительно лучшего места для принятия решения о том, как передать аргумент, а не внутри функции - person Sake; 10.05.2009
comment
Также deepcopy абсолютно не более эффективен. Когда решение о клонировании данных находится внутри вызывающей стороны, и вы точно знаете, какие данные вы передаете, вы можете использовать наиболее эффективный метод для этого; (например, list [:], dict (dic)). Если вы не совсем уверены, какие данные будут переданы, то сохранение решения об эффективном клонировании метода для самого объекта данных всегда будет давать лучшую производительность. - person Sake; 10.05.2009
comment
Правильно ли я понимаю, что clone () / deepcopy () должен находиться в структуре данных. Затем вы используете вызывающий метод, которому передается функция f? (В этом случае параметр f отсутствует в caller(f).) Разве вам не нужно сначала знать сигнатуру функции? Можете ли вы сделать его многомерным? Или вы будете реализовывать новую структуру данных для каждой функции? Или определить каждую функцию как функцию-член для каждого объекта данных? - person gschenk; 14.10.2020

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

import pickle

def f(data):
    data = pickle.loads(pickle.dumps((data)))
    #do stuff
person SingleNegationElimination    schedule 04.07.2009

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

Многие люди используют стандартную библиотеку copy. Я предпочитаю определять __copy__ или __deepcopy__ в своих классах. Методы в copy могут вызывать некоторые проблемы.

  1. Неглубокая копия сохранит ссылки на объекты в оригинале вместо создания новых.
  2. Глубокая копия будет выполняться рекурсивно, иногда может вызывать мертвую петлю. Если не уделять должного внимания, память может взорваться.

Чтобы избежать такого неконтролируемого поведения, определите свои собственные методы поверхностного / глубокого копирования путем перезаписи __copy__ и __deepcopy__. И ответ Алекса является хорошим примером.

person Qinsheng Zhang    schedule 30.12.2019