Как вызвать функцию при изменении значения?

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

Я хочу, чтобы метод 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 ответов


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

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

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)

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

Что ты ищешь называется (Функциональное) Реактивное Программирование. для общего Lisp есть ячейки-см. клетки и клетки манифест и для python есть шпалера библиотека.

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

реактивное программирование подобно Модель наблюдателя, но с важным отличием:

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


вам понадобится свойства

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 и всякий раз, когда вы хотите получить х print MyClass().xвы будете использовать метод x_setter.


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

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 .55.
> Sally has .55.
> Sally has .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 .55.
> You have .12.
"""

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

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