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

A pure function - это функция, аналогичная функции Математическая функция, где нет взаимодействия с "реальным миром" и побочными эффектами. С более практической точки зрения это означает, что чистая функция может не:

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

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

В языках с огромной системой типов, такой как 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

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

Ответ 1

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

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.

Ответ 2

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

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

Помните, что функции в 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() внутри вашей функции. Глобальные переменные могут меняться в другом месте, влияя на чистоту вашей функции.

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