Как проверить, является ли функция чистой в Python?

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

  • Распечатать или иным образом показать сообщение
  • Быть случайным
  • Зависит от системного времени
  • Изменить глобальные переменные
  • И другие

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

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

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

Прямо сейчас я просто просматриваю исходный код функции на наличие таких модных словечек, как global, random или print, и жалуюсь, если находит одно из них.

import inspect

def pure(function):
    source = inspect.getsource(function)
    for non_pure_indicator in ('random', 'time', 'input', 'print', 'global'):
        if non_pure_indicator in source:
            raise ValueError("The function {} is not pure as it uses `{}`".format(
                function.__name__, non_pure_indicator))
    return function

Однако это похоже на странный хак, который может сработать или не сработать в зависимости от вашей удачи, не могли бы вы помочь мне написать лучший декоратор?


person Caridorc    schedule 22.07.2015    source источник
comment
Вы можете inspect.getsource затем ast.parse и пройтись по узлам, проверяя разные вещи... но вы пойдете против причины, по которой существует язык - посмотрите на использование модуля abc, если хотите что-то, а затем isinstance проверьте, где это необходимо... - python строго типизирован, а не статически типизирован   -  person Jon Clements♦    schedule 22.07.2015
comment
Динамические языки @JonClements на самом деле выполняют меньшую проверку во время компиляции, но я думаю, что конкретная проверка значительно улучшит организацию программы и дважды проверит понимание программистами его собственной работы.   -  person Caridorc    schedule 22.07.2015
comment
Затем используйте статически типизированный язык... :) Вы можете рассматривать это как плохую вещь или как хорошую вещь... но это так, как есть   -  person Jon Clements♦    schedule 22.07.2015
comment
Возможно, вы могли бы исключить некоторые очевидные проблемы, но каждая нетривиальная функция вызывает десятки методов, __dunder__ методов и других функций. Каждый из этих вызовов может делать что угодно, от изменения практически любого объекта до изменения того, какие функции будут вызываться в следующей строке. Неполный черный список — это лучшее, что вы можете сделать, но это также можно сделать статически с помощью линтера, без необходимости проверки во время выполнения.   -  person    schedule 22.07.2015
comment
@JonClements Очень немногие языки разделяют чистые и нечистые функции, я думаю, что Haskell был бы хорошим выбором, но мне очень трудно учиться, так как я нахожу сообщения об ошибках типа очень загадочными.   -  person Caridorc    schedule 22.07.2015
comment
Вероятно, вы захотите выполнить проверку байт-кода, а не проверку исходного кода.   -  person kindall    schedule 22.07.2015
comment
Это невозможно. Сдаться.   -  person Veedrac    schedule 23.07.2015
comment
@Veedrac Опубликуйте это как ответ на вопрос, если не будет лучшего, я приму это.   -  person Caridorc    schedule 23.07.2015


Ответы (2)


Я как бы понимаю, откуда вы исходите, но я не думаю, что это может сработать. Возьмем простой пример:

def add(a,b):
    return a + b

Так что это, вероятно, выглядит "чистым" для вас. Но в Python + здесь — это произвольная функция, которая может делать что угодно, просто в зависимости от привязок, действующих при ее вызове. Так что a + b может иметь произвольные побочные эффекты.

Но это еще хуже. Даже если это просто стандартное целое число +, тогда происходит больше «нечистых» вещей.

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

Например:

class RegisteredNumber(int):

    numbers = []

    def __new__(cls,*args,**kwargs):
        self = int.__new__(cls,*args,**kwargs)
        self.numbers.append(self)
        return self

    def __add__(self,other):
        return RegisteredNumber(super().__add__(other))

c = RegisteredNumber(1) + 2

print(RegisteredNumber.numbers)

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

Понятие чистоты просто не имеет особого смысла в Python.

person strubbly    schedule 27.07.2015
comment
Я мог бы сформулировать ваше окончательное утверждение немного по-другому: понятие чистоты можно рассматривать как прекрасное в Python, просто в основном каждая нетривиальная функция является нечистой, потому что нет способа учесть все различные входные данные, которые она использует. может получить и среды, в которых он может работать. - person Ken Williams; 11.06.2021

(не ответ, но слишком длинный для комментария)

Итак, если функция может возвращать разные значения для одного и того же набора аргументов, она не является чистой?

Помните, что функции в Python являются объектами, поэтому вы хотите проверить чистоту объекта...

Возьмите этот пример:

def foo(x):
    ret, foo.x = x*x+foo.x, foo.x+1
    return ret
foo.x=0

повторный вызов foo(3) дает:

>>> foo(3)
9

>>> foo(3)
10

>>> foo(3)
11

...

Более того, чтение глобальных переменных не требует использования оператора global или встроенного оператора global() внутри вашей функции. Глобальные переменные могут измениться где-то еще, что повлияет на чистоту вашей функции.

Все описанные выше ситуации может быть трудно обнаружить во время выполнения.

person fferri    schedule 22.07.2015
comment
Интересная идея, но я могу вспомнить множество функций, которые не являются чистыми, что может показаться таковым в короткие промежутки времени, например, получение часа дня, номера версии операционной системы, текущей ветки git и т. д. - person wallyk; 22.07.2015