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

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

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

7 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

О единственном способе применения этого было бы переписать спецификацию функции на 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)

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

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

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:, что-то вроде этого), это может быть полезно в проектах, где много людей редактируют один и тот же код, хотя, я думаю...

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

Выше указано, что любые измененные значения находятся в возвращаемом значении / кортеже. Если вы находитесь в 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

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

Другие вопросы по тегам