Эмуляция поведения пошагового значения в python
Я хотел бы эмулировать поведение pass-by-value в python. Другими словами, я хотел бы абсолютно убедиться, что функция, которую я пишу, не изменяет предоставленные пользователем данные.
Один из возможных способов - использовать глубокую копию:
from copy import deepcopy
def f(data):
data = deepcopy(data)
#do stuff
существует более эффективный или более питонический способ достижения этой цели, делая как можно меньше предположений относительно передаваемого объекта (например, метод .clone())
Изменить
Я знаю, что технически все в python передается по значению. Я был заинтересован в эмуляции поведения, то есть убедившись, что я не вмешиваюсь в данные, которые были переданы функции. Я предполагаю, что наиболее общий способ - клонировать рассматриваемый объект либо своим собственным механизмом клонирования, либо с помощью deepcopy.
Ответы
Ответ 1
Нет никакого питонического способа сделать это.
Python предоставляет очень мало возможностей для обеспечения безопасности таких вещей, как частные или доступные для чтения данные. Пифоническая философия заключается в том, что "мы все соглашаемся с взрослыми": в этом случае это означает, что "функция не должна изменять данные" является частью спецификации, но не применяется в коде.
Если вы хотите сделать копию данных, самое ближайшее, что вы можете получить, это ваше решение. Но copy.deepcopy
, помимо неэффективности, также имеет оговорки например:
Поскольку глубокая копия копирует все, что она может скопировать слишком много, например, структуры административных данных, которые должны быть разделены даже между копиями.
[...]
Этот модуль не копирует такие типы, как модуль, метод, трассировка стека, стек стека, файл, сокет, окно, массив или любые подобные типы.
Поэтому я бы рекомендовал это, только если вы знаете, что имеете дело со встроенными типами Python или с вашими собственными объектами (где вы можете настраивать поведение копирования, определяя специальные методы __copy__
/__deepcopy__
, там нет необходимо определить свой собственный метод clone()
).
Ответ 2
Вы можете сделать декоратор и поместить в него поведение клонирования.
>>> 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__
.
Ответ 3
Есть только несколько встроенных типов, которые работают как ссылки, например list
.
Итак, для меня питонический способ для выполнения pass-by-value для списка в этом примере будет:
list1 = [0,1,2,3,4]
list2 = list1[:]
list1[:]
создает новый экземпляр списка1, и вы можете назначить его новой переменной.
Может быть, вы могли бы написать функцию, которая могла бы получить один аргумент, а затем проверить его тип и, в соответствии с этим результатом, выполнить встроенную операцию, которая могла бы вернуть новый экземпляр переданного аргумента.
Как я уже говорил, существует только несколько встроенных типов, что их поведение похоже на ссылки, списки в этом примере.
В любом случае... надеюсь, что это поможет.
Ответ 4
обычно при передаче данных во внешний API вы можете обеспечить целостность своих данных, передав их как неизменяемый объект, например, оберните свои данные в кортеж. Это не может быть изменено, если это то, что вы пытались предотвратить своим кодом.
Ответ 5
Я не могу понять какой-либо другой вариант pythonic. Но лично я предпочел бы больше возможностей OO.
class TheData(object):
def clone(self): """return the cloned"""
def f(data):
#do stuff
def caller():
d = TheData()
f(d.clone())
Ответ 6
Хотя я уверен, что нет действительно путинского способа сделать это, я ожидаю, что модуль pickle
предоставит вам копии всего, что вы считаете бизнесом как ценность.
import pickle
def f(data):
data = pickle.loads(pickle.dumps((data)))
#do stuff
Ответ 7
В ответ на запрос 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]
Ответ 8
Существует еще один трюк
Числовая переменная
a = 0
functioncall (a + 0)
Строковая переменная
a = 'тестовые данные'
functioncall (a + '')
логическая переменная
a = True
functioncall (a и True)