Как вызвать функцию при изменении значения? - PullRequest
45 голосов
/ 31 мая 2011

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

Я хочу, чтобы метод m() объекта A запускался при изменении значения v:

Например (при условии, что деньги приносят счастье):

global_wealth = 0

class Person()
    def __init__(self):
        self.wealth = 0
        global global_wealth
        # here is where attribute should be
        # bound to changes in 'global_wealth'
        self.happiness = bind_to(global_wealth, how_happy)

    def how_happy(self, global_wealth):
        return self.wealth / global_wealth

Таким образом, всякий раз, когда значение global_wealth изменяется, все экземпляры класса Person должны соответственно менять свое значение happiness.

NB: Мне пришлось редактировать вопрос, так как первая версия, казалось, предполагала, что мне нужны методы получения и установки. Извините за путаницу.

Ответы [ 5 ]

69 голосов
/ 31 мая 2011

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

Я использую свойства в этом примере, но они не обязательны. Небольшое предупреждение: свойства работают только для новых классов стилей, поэтому (объект) после объявления классов обязателен для этой работы.

class GlobalWealth(object):
    def __init__(self):
        self._global_wealth = 10.0
        self._observers = []

    @property
    def global_wealth(self):
        return self._global_wealth

    @global_wealth.setter
    def global_wealth(self, value):
        self._global_wealth = value
        for callback in self._observers:
            print('announcing change')
            callback(self._global_wealth)

    def bind_to(self, callback):
        print('bound')
        self._observers.append(callback)


class Person(object):
    def __init__(self, data):
        self.wealth = 1.0
        self.data = data
        self.data.bind_to(self.update_how_happy)
        self.happiness = self.wealth / self.data.global_wealth

    def update_how_happy(self, global_wealth):
        self.happiness = self.wealth / global_wealth


if __name__ == '__main__':
    data = GlobalWealth()
    p = Person(data)
    print(p.happiness)
    data.global_wealth = 1.0
    print(p.happiness)
13 голосов
/ 31 мая 2011

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

class A(object):

    def m(self, p_value):
         print p_value

    @property
    def p(self):
        return self._p 

    @p.setter
    def p(self, value)
        self._p = value
        self.m(value)
8 голосов
/ 03 июня 2011

То, что вы ищете, называется (Функциональное) Реактивное Программирование . Для Common Lisp есть Клетки - см. Проект Клеток и Манифест Клеток и для python есть библиотека Trellis .

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

Реактивное программирование аналогично шаблону Observer, но с важным отличием:

Сходство с шаблоном Observer Однако интеграция концепций потока данных в язык программирования облегчит их выражение и, следовательно, может повысить степень детализации графа потока данных.Например, шаблон наблюдателя обычно описывает потоки данных между целыми объектами / классами, тогда как объектно-ориентированное реактивное программирование может предназначаться для членов объектов / классов.

3 голосов
/ 31 мая 2011

Вам необходимо свойство

class MyClass(object):
    def __init__(self):
        self._x = None

    def x_setter(self, value):
        self._x = value

    def x_getter(self):
        return self._x

    x = property(x_getter, x_setter)

Здесь, когда вы хотите установить x MyClass().x = "foo", вы будете использовать метод x_getter и всякий раз, когда вы захотите получить x print MyClass().x, выбудет использовать метод x_setter.

1 голос
/ 29 ноября 2014

Вы можете попробовать что-то вроде этого:

class Variable:
    def __init__(self, v):
        self.v=v
        self.command=None
    def set(self, v):
        self.v=v
        if self.command!=None:
            self.command()
    def get(self):
        return self.v
    def trace(self, command):
        self.command=command

x=Variable(0)

def money():
    amount="{:.2f}".format(x.get())
    print("You have $"+amount+".")

x.trace(money)

x.set(5.55)
x.set(15.14)

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

class Variable: #This is a class for the variable you want to bind something to
    def __init__(self, v):
        self.v=v
        self.commands=[]
    def set(self, v): #Set the variable's value and call any bound functions
        self.v=v
        for x in self.commands:
            x()
    def get(self): #Get the variable's value
        return self.v
    def trace(self, *commands): #Bind one or more functions to the variable
        for x in commands:
            if x in self.commands:
                raise ValueError("You can’t add the same command object twice. If you need to, use another lambda function that calls the same function with the same parameters.")
        self.commands.extend(commands)
    def untrace(self, *commands): #Unbind one or more functions from the variable
        for x in commands:
            if x not in self.commands:
                raise ValueError(str(x)+" is not a traced command.")
        for x in commands:
            if x in self.commands:
                self.commands.remove(x)
    def clear_traces(self): #Removes all functions bound to the variable
        self.commands.clear()

x=Variable(0) #Make the variable, starting with a value of 0

def money(name): #Define the method to bind
    amount="{:.2f}".format(x.get())
    print(name+" has $"+amount+".")

sam=lambda : money("Sam") #We're making a new method to bind that calls the old one with the argument "Sam"
sally=lambda : money("Sally") #Another one (Sally and Sam will always have the same amount of money while they are both bound to the variable.)

#Bind them both to the value (not that this is practical, but we're doing both for demonstration)
x.trace(sam)
x.trace(sally)

#Set the value
x.set(5.55)
#Unbind the sam lambda function and set the value again
x.untrace(sam)
x.set(15.14)

"""
This prints the following:
> Sam has $5.55.
> Sally has $5.55.
> Sally has $15.14.
"""

Альтернатива

В любом случае, вы можететакже используйте встроенную функциональность, которая поставляется с Tkinter, например, DoubleVar.trace() или someWidget.wait_variable().

Метод trace() позволяет вам связать метод с StringVar, IntVar, FloatVar, DoubleVar,BooleanVar или такие переменные.Вот полный рабочий пример Python 3.x:

from tkinter import *

tk=Tk()
tk.withdraw()

d=DoubleVar(master=tk, value=0)

def my_event_handler(*args):
    amount="{:.2f}".format(d.get())
    print("$"+amount)

d.trace(mode="w", callback=my_event_handler)

d.set(5.55)
d.set(15.12)

"""
This prints the following:
> You have $5.55.
> You have $15.12.
"""

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...