Класс, подобный числу Python, который запоминает арифметические операции?

#python

#python

Вопрос:

Мне интересно, существует ли модуль Python, который позволил бы мне сделать что-то подобное:

 x = MagicNumber()
x.value = 3
y = 2 * (x   2) ** 2 - 8
print y   # 42
x.value = 2
print y   # 24
  

Таким образом, MagicNumber были бы реализованы все специальные операторные методы, и все они возвращали бы экземпляры MagicNumber, отслеживая при этом, какие операции выполняются. Существует ли такой класс?

РЕДАКТИРОВАТЬ: уточнение

Я хочу использовать это в модуле, который должен запоминать множество параметров некоторых произвольных вычислений, которые пользователь желает выполнить. Таким образом, пользователь установит параметры, а затем использует их для получения своего результата. Затем, если он решит, что хотел бы изменить параметр, изменение немедленно отражается в его результате. Таким образом, очень упрощенный сеанс использования только с одним экземпляром параметра будет выглядеть как:

 p = MyParams()
p.distance = 13.4           # I use __getattr__ and __setattr__ such that
p.speed = 3.14              # __getattr__ returns MagicNumber instances
time = p.distance / p.speed
  

РЕДАКТИРОВАТЬ 2: дополнительные разъяснения

Хорошо, я сделаю то, что должен был сделать с самого начала. Я предоставлю контекст.

Вы инженер, и вам нужно создать документ LaTeX, подробно описывающий работу и свойства какого-либо прототипа гаджета. Это задача, которую вы будете выполнять неоднократно для разных прототипов. Вы пишете небольшой интерфейс LaTeX python. Для каждого прототипа вы создаете модуль Python, который генерирует необходимый документ. В нем вы вводите код LaTeX при вычислении переменных по мере их необходимости, чтобы вычисления выполнялись в контексте. Через некоторое время вы замечаете две проблемы:

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

Из этой проблемы возникает исходный вопрос.

Ответ №1:

Что-то вроде этого?

 import operator

MAKE_BINARY  = lambda opfn : lambda self,other : BinaryOp(self, asMagicNumber(other), opfn)
MAKE_RBINARY = lambda opfn : lambda self,other : BinaryOp(asMagicNumber(other), self, opfn)

class MagicNumber(object):
    __add__  = MAKE_BINARY(operator.add)
    __sub__  = MAKE_BINARY(operator.sub)
    __mul__  = MAKE_BINARY(operator.mul)
    __radd__ = MAKE_RBINARY(operator.add)
    __rsub__ = MAKE_RBINARY(operator.sub)
    __rmul__ = MAKE_RBINARY(operator.mul)
    # __div__  = MAKE_BINARY(operator.div)
    # __rdiv__ = MAKE_RBINARY(operator.div)
    __truediv__ = MAKE_BINARY(operator.truediv)
    __rtruediv__ = MAKE_RBINARY(operator.truediv)
    __floordiv__ = MAKE_BINARY(operator.floordiv)
    __rfloordiv__ = MAKE_RBINARY(operator.floordiv)

    def __neg__(self, other):
        return UnaryOp(self, lambda x : -x)

    @property
    def value(self):
        return self.eval()

class Constant(MagicNumber):
    def __init__(self, value):
        self.value_ = value

    def eval(self):
        return self.value_

class Parameter(Constant):
    def __init__(self):
        super(Parameter, self).__init__(0.0)

    def setValue(self, v):
        self.value_ = v

    value = property(fset=setValue, fget=lambda self: self.value_)

class BinaryOp(MagicNumber):
    def __init__(self, op1, op2, operation):
        self.op1 = op1
        self.op2 = op2
        self.opn = operation

    def eval(self):
        return self.opn(self.op1.eval(), self.op2.eval())


class UnaryOp(MagicNumber):
    def __init__(self, op1, operation):
        self.op1 = op1
        self.operation = operation

    def eval(self):
        return self.opn(self.op1.eval())

asMagicNumber = lambda x : x if isinstance(x, MagicNumber) else Constant(x)
  

И вот он в действии:

 x = Parameter()
# integer division
y = 2*x*x   3*x - x//2

# or floating division
# y = 2*x*x   3*x - x/2

x.value = 10
print(y.value)
# prints 225

x.value = 20
print(y.value)
# prints 850

# compute a series of x-y values for the function
print([(x.value, y.value) for x.value in range(5)])
# prints [(0, 0), (1, 5), (2, 13), (3, 26), (4, 42)]
  

Комментарии:

1. Существует ли такой класс? Пол: «Теперь есть!». Хотелось бы, чтобы у меня было больше голосов.

2. Я стою на плечах гигантского, э-э, Питона? (Есть ли у питонов плечи?)

3. Отлично! Что касается символической алгебры, то у нее пока есть некоторые недостатки, но это не критика. Вы должны позаботиться о том, что вы назначаете x.value : с целыми числами вы получаете неожиданные результаты. Также неплохо было бы использовать некоторые символьные операции: что -y в приведенном выше примере. @PaulMcGuire: Я приму это как упражнение, потому что недавно я экспериментировал в аналогичном направлении, но у меня не получилось ничего даже близко такого краткого, как у вас.

4. Если «вы получаете неожиданные результаты» с целыми числами, возможно, вы имеете в виду сюрпризы целочисленного деления. Если вы хотите принудительно вычислять все выражения с использованием семантики с плавающей запятой, вы можете сохранять значения в параметре с помощью self.value_ = float(v) или позаботиться о том, чтобы в ваших выражениях использовались операторы целочисленного деления или деления с плавающей запятой.

Ответ №2:

Вы могли бы попробовать sympy, систему компьютерной алгебры, написанную на Python.

Например.

 >>> from sympy import Symbol
>>> x = Symbol('x')
>>> y = 2 * (x   2) ** 2 - 8
>>> y
2*(x   2)**2 - 8
>>> y.subs(x,3)
42
>>> y.subs(x,2)
24
  

Комментарии:

1. Не для того, чтобы преуменьшить ваш ответ, а из любопытства: в чем преимущество этой конструкции перед использованием функции? Я кажусь менее разборчивым, и вам все равно придется создавать его в коде… Я, честно говоря, заинтересован в дополнительной ценности библиотеки в этом случае.

2. @jro: OP фактически запросил библиотеку для алгебры, это библиотека, которая (среди прочего) занимается алгеброй. По функции это позволило бы решить уравнение из нескольких символов или выразить, где замещенные значения включают символы.

3. Это ближе всего к тому, что я имел в виду, поскольку я могу построить выражение в несколько шагов. Однако с помощью этого метода мне нужно вызвать специальную функцию, чтобы она была оценена — я хотел, чтобы она была бесшовной и давала x значение с самого начала. Но, может быть, я могу создать подкласс Symbol и у меня будет намного меньше работы. Спасибо!

Ответ №3:

Разве это не называется function ? Это может показаться простым ответом, но я говорю искренне.

 def y(x):
    return 2 * (x   2) ** 2 - 8
  

Не думаете ли вы в неправильном направлении с этим?

Чтобы обратиться к разъяснению:

 class MyParams():
    distance = 0.0
    speed = 0.0
    def __call__(self):
        return self.distance / self.speed

p = MyParams()
p.distance = 13.4            # These are properties
p.speed = 3.14               # where __get__ returns MagicNumber instances
time = p()  # 4.26
p.speed = 2.28
time = p()  # 5.88
  

Я думаю, что я больше склоняюсь к решению такого типа, хотя я вижу преимущество в модуле sympy. Я думаю, предпочтение.

Комментарии:

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

2. Может быть, тогда неплохо обновить ваш вопрос с некоторыми предполагаемыми вариантами использования. Как именно вы собираетесь создавать эти операции и почему вы не хотите сопоставлять символ (скажем, * ) с функцией (скажем, multiply )?

3. Повторное обновление. Снова вы привязываете меня к конкретной проблеме. Я не могу предвидеть, какие параметры или проблемы пользователь моего модуля желает выполнить. Он должен иметь право сам называть параметры и использовать их по своему усмотрению и вычислять с ними так, как если бы они были нормальными значениями. Я просто хочу дать ему дополнительную возможность изменять их задним числом.

4. Подождите, значит, конечный пользователь введет код, который вы ввели в первый блок кода вашего вопроса? В этом случае вы могли бы обернуть вызов в eval команду, заменив переменные при выполнении: например, eval('2 * (x 2) ** 2 - 8'.replace('x', str(3))) 3 является текущим значением x параметра?

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

Ответ №4:

 >>> magic = lambda x: eval('2 * (x   2) ** 2 - 8')
>>> magic(2)
24
>>> magic(3)
42
>>> magic = lambda x: eval('x ** 4')
>>> magic(2)
16
>>> magic(3)
81
  

Комментарии:

1. @eumiro, он не знает заранее, какие арифметические операции будут выполняться.

Ответ №5:

Я думаю, что трудность заключается в том, «как сохранить приоритет операторов», а не в реализации класса.

Я предлагаю взглянуть на другую нотацию (например, обратную польскую нотацию), которая может помочь избавиться от проблем с приоритетом…

Комментарии:

1. Я думаю, что интерпретатор Python позаботится о приоритетных проблемах — он не хочет анализировать выражение, он хочет захватить проанализированное выражение как оцениваемый (это слово?) объект.