Есть ли способ предотвратить побочные эффекты в python?

Есть ли способ предотвратить побочные эффекты в python? Например, следующая функция имеет побочный эффект, есть ли какое-либо ключевое слово или любой другой способ, чтобы жалоба python на него писала?

def func_with_side_affect(a):
    a.append('foo')

Ответ 1

Python действительно не настроен для обеспечения предотвращения побочных эффектов. Как уже говорили некоторые другие, вы можете попробовать deepcopy данные или использовать неизменяемые типы, но у них все еще есть угловые случаи, которые сложно поймать, и это всего лишь тонна усилий, чем это стоит.

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

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

Ответ 2

Извините, что очень поздно на вечеринку. Вы можете использовать effect библиотеку, чтобы изолировать побочные эффекты в вашем коде python. Как говорили другие в Python, вы должны явно писать код функционального стиля, но эта библиотека действительно поощряет его.

Ответ 3

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

UPD: Тем не менее, ваша функция может изменять объекты, на которые ссылается ваш неизменяемый объект (как указывалось в комментариях), писать в файлы и выполнять некоторые другие операции ввода-вывода.

Ответ 4

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

Таким образом, функция не имеет способа фактически изменить исходно переданные аргументы. Это, однако, имеет "побочный эффект" значительного замедления, поскольку операция с глубокой костью является довольно дорогостоящей с точки зрения использования памяти (и сбора мусора), а также потребления процессора.

Я бы скорее рекомендовал вам правильно протестировать ваш код, чтобы гарантировать, что не произойдет никаких случайных изменений, или используйте язык, который использует полную семантику по-одному (или имеет только неизменные переменные).

В качестве другого обходного пути вы можете сделать свои переданные объекты в основном неизменными, добавив это в свои классы:

 """An immutable class with a single attribute 'value'."""
 def __setattr__(self, *args):
     raise TypeError("can't modify immutable instance")
 __delattr__ = __setattr__
 def __init__(self, value):
     # we can no longer use self.value = value to store the instance data
     # so we must explicitly call the superclass
     super(Immutable, self).__setattr__('value', value)

(Код скопирован из статьи в Википедии о неизменяемом объекте)

Ответ 5

Поскольку любой код Python может выполнять IO, любой код Python может запускать межконтинентальные баллистические ракеты (и я бы подумал о том, чтобы запуск МБР был довольно катастрофическим побочным эффектом для большинства целей).

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

Ответ 6

Сначала вам нужно сделать копию списка. Что-то вроде этого:

def func_without_side_affect(a):
    b = a[:]
    b.append('foo')
    return b

Эта более короткая версия может работать и для вас:

def func_without_side_affect(a):
    return a[:] + ['foo']

Если у вас есть вложенные списки или другие подобные вещи, вы, вероятно, захотите посмотреть copy.deepcopy, чтобы сделать копию вместо Оператор среза [:].

Ответ 7

Это было бы очень сложно сделать для общего случая, но для некоторых практических случаев вы могли бы сделать что-то вроде этого:

def call_function_checking_for_modification(f, *args, **kwargs):
    myargs = [deepcopy(x) for x in args]
    mykwargs = dict((x, deepcopy(kwargs[x])) for x in kwargs)

    retval = f(*args, **kwargs)

    for arg, myarg in izip(args, myargs):
        if arg != myarg: 
            raise ValueError, 'Argument was modified during function call!'
    for kwkey in kwargs:
        if kwargs[kwkey] != mykwargs[kwkey]:
            raise ValueError, 'Argument was modified during function call!'

    return retval

Но, очевидно, есть несколько проблем с этим. Для тривиальных вещей (т.е. Все входные данные являются простыми типами), тогда это не очень полезно в любом случае - они, вероятно, будут неизменными, и в любом случае они легки (ну, относительно) для обнаружения, чем сложные типы.

Однако для сложных типов deepcopy будет дорогостоящим, и нет гарантии, что оператор == будет работать правильно. (и простая копия недостаточно хороша... представьте себе список, в котором один элемент меняет значение... простая копия просто сохранит ссылку, и поэтому исходное значение с изменением тоже).

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

Что-то вроде вышеупомянутого может быть обернуто в декораторе; с дорогостоящими частями, созданными глобальной переменной (if _debug == True:, что-то в этом роде), возможно, это может быть полезно в проектах, где многие люди редактируют один и тот же код, хотя, думаю...

Изменить: это работает только в средах, где ожидается более "строгая" форма "побочных эффектов". Во многих языках программирования вы можете сделать доступными побочные эффекты гораздо более явными - например, в С++, все по стоимости, если явно не указатель или ссылка, и даже тогда вы можете объявлять входящие ссылки как const, чтобы он мог "t быть измененным. Там" побочные эффекты" могут вызывать ошибки во время компиляции. (конечно, есть способ получить что-то в любом случае).

Приведенное выше означает, что любые измененные значения находятся в возвращаемом значении/кортеже. Если вы находитесь в python 3 (я еще не знаю), я думаю, что вы можете указать украшение в самом объявлении функции, чтобы указать атрибуты аргументов функции, в том числе, разрешено ли им изменять, и включить в эту функцию функцию, чтобы разрешить некоторые аргументы явно изменяемы.

Заметьте, что я думаю, что вы, вероятно, могли бы сделать что-то вроде этого:

class ImmutableObject(object):
    def __init__(self, inobj):
        self._inited = False
        self._inobj = inobj
        self._inited = True

    def __repr__(self):
        return self._inobj.__repr__()

    def __str__(self):
        return self._inobj.__str__()

    def __getitem__(self, key):
        return ImmutableObject(self._inobj.__getitem__(key))

    def __iter__(self):
        return self.__iter__()

    def __setitem__(self, key, value):
        raise AttributeError, 'Object is read-only'

    def __getattr__(self, key):
        x = getattr(self._inobj, key)
        if callable(x):
              return x
        else:
              return ImmutableObject(x)

    def __setattr__(self, attr, value):
        if attr not in  ['_inobj', '_inited'] and self._inited == True:
            raise AttributeError, 'Object is read-only'
        object.__setattr__(self, attr, value)

(Вероятно, не полная реализация, не много тестировалось, а начало). Работает следующим образом:

a = [1,2,3]
b = [a,3,4,5]
print c
[[1, 2, 3], 3, 4, 5]
c[0][1:] = [7,8]
AttributeError: Object is read-only

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