web-dev-qa-db-fra.com

Comment déclencher une fonction lors d'un changement de valeur?

Je me rends compte que cette question a à voir avec la gestion des événements et j'ai lu Python gestionnaire d'événements un répartiteur, donc soit il n'a pas répondu à ma question, soit j'ai complètement raté les informations.

Je veux que la méthode m() de l'objet A soit déclenchée chaque fois que la valeur v change:

Par exemple (en supposant que l'argent rend heureux):

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

Donc, chaque fois que le global_wealth la valeur est modifiée, toutes les instances de la classe Person doivent changer leur happiness valeur en conséquence.

NB: J'ai dû éditer la question car la première version semblait suggérer que j'avais besoin de méthodes getter et setter. Désolé pour la confusion.

46
Benjamin

Vous devez utiliser le Observer Pattern . Dans le code suivant, une personne s'abonne pour recevoir des mises à jour de l'entité de richesse mondiale. Lorsqu'il y a un changement dans la richesse mondiale, cette entité alerte alors tous ses abonnés (observateurs) qu'un changement s'est produit. La personne se met ensuite à jour.

J'utilise des propriétés dans cet exemple, mais elles ne sont pas nécessaires. Un petit avertissement: les propriétés ne fonctionnent que sur les nouvelles classes de style, donc (l'objet) après les déclarations de classe est obligatoire pour que cela fonctionne.

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 __== '__main__':
    data = GlobalWealth()
    p = Person(data)
    print(p.happiness)
    data.global_wealth = 1.0
    print(p.happiness)
70
Thiago Chaves

Vous pouvez utiliser des propriétés si vous souhaitez exécuter du code lorsque les attributs sont modifiés. Méfiez-vous des effets secondaires importants ou des frais généraux importants qui se produisent lorsqu'un attribut est modifié est un peu surprenant pour quiconque utilise votre API, donc dans certains cas, vous voudrez probablement l'éviter en utilisant des méthodes à la place.

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)
13
Rosh Oxymoron

Ce que vous recherchez s'appelle (Functional) Reactive Programming . Pour LISP commun, il y a des cellules - voir Projet Cells et Manifeste Cells et pour python il y a bibliothèque Trellis .

Les feuilles de calcul utilisent également le même paradigme. Très utile pour garder une trace de plusieurs paramètres liés entre eux - comme dans la programmation GUI par exemple.

La programmation réactive est similaire au modèle Observer, mais avec une distinction importante:

Similitudes avec le modèle Observer Cependant, l'intégration des concepts de flux de données dans le langage de programmation faciliterait leur expression et pourrait donc augmenter la granularité des données graphique de flux. Par exemple, le modèle d'observateur décrit couramment les flux de données entre des objets/classes entiers, tandis que la programmation réactive orientée objet pourrait cibler les membres des objets/classes.

8
peterhil

Vous avez besoin d'une propriété

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)

Ici, chaque fois que vous voulez définir x MyClass().x = "foo", vous utiliserez la méthode x_getter et chaque fois que vous voulez récupérer x print MyClass().x, vous utiliserez la méthode x_setter.

3
Cédric Julien

Vous pouvez essayer quelque chose comme ceci:

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)

Si vous avez besoin d'arguments, utilisez simplement une fonction lambda. À la lumière de cela (et de la réponse acceptée que j'ai examinée plus récemment plus en détail), voici une version plus complexe avec des commentaires, plus de fonctionnalités et des exemples:

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.
"""

Alternative

Quoi qu'il en soit, vous pouvez également utiliser la fonctionnalité intégrée fournie avec Tkinter, avec par exemple DoubleVar.trace() ou someWidget.wait_variable().

La méthode trace() vous permet de lier une méthode à StringVar, IntVar, FloatVar, DoubleVar, BooleanVar ou à de telles variables. Voici un exemple complet 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.
"""

Vous voudrez peut-être détruire l'objet Tk à la fin du programme. Cependant, il semble bien sortir sans lui dans mon exemple.

wait_variable() est une autre alternative qui provoque l'arrêt de la fonction appelante sans arrêter votre interface graphique jusqu'à ce qu'une variable que vous avez spécifiée change. Il existe également d'autres méthodes similaires.

1
Shule