3
votes

Comment puis-je lier une fonction à une instance d'objet à l'aide de Decorator?

J'essaye de lier une fonction à une instance d'objet. Par exemple, j'ai un objet et j'essaye de lier une fonction à l'attribut condition. Le truc, c'est que je veux faire cela en utilisant un décorateur:

f = Transition()
f1 = Transition()

@bind(f)
def condition1():
    return True

@bind(f1)
def condition2():
    return False

Ensuite, je lie une fonction à l'instance de l'objet.

@bind
my_condition():
    return True

Après ceci, nous devrions avoir une référence à la fonction donnée si nous regardons l'instance de l'objet attribut de condition

Edit 1:

class Transition(object):
    def __init__(self, enter, exit, condition=None):
        if not isinstance(enter, State) or not isinstance(exit, State):
            raise TypeError("Parameters should be an instance of the State class")
        self.enter = enter
        self.exit = exit
        self.condition = None
        self.effect = None

    def __repr__(self):
        print("Going from {} to {} with condition {} and effect {}".format(self.enter, self.exit, self.condition.__name__, self.effect.__name__)) 

    def __eq__(self, other):
        return self.enter == other.enter and self.exit == other.exit

    def is_enpoint(self, endpoints):
        """
        :parameter --> 'endpoints' is a tuple indicating where the transition flows(from, to) 
        :return --> boolean
        :description --> check if the given endpoints are valid 
        """
        return self.enter == endpoints[0] and self.exit == endpoints[1]

L'instance f doit avoir une référence à la fonction condition1 et l'instance f2 doit avoir une référence à la condition2 pour l'attribut condition


4 commentaires

Votre question n'est pas claire ... vous voulez que tous les autres objets recherchent normalement leur attribut, mais un objet spécifique devrait utiliser une propriété / fonction pour cela? Pourquoi utiliseriez-vous la syntaxe du décorateur pour cela? Voulez-vous mettre cette fonction en tant que méthode dans cette classe? Comment définissez-vous quel attribut c'est?


Bienvenue à SO. J'ai bien peur que votre question ne soit pas vraiment une question - vous avez dit ce que vous vouliez faire, mais vous n'expliquez pas quel est votre problème réel. Notez que si la question est "comment faire cela", vous êtes censé d'abord essayer par vous-même.


@brunodesthuilliers je l'ai fait. J'ai également recherché la réponse en ligne mais je ne l'ai pas trouvée.


@AlexandreManeta "Vous avez fait" quoi? Expliquez votre problème réel? Désolé mais vous ne l'avez pas fait - votre message ne contient absolument aucune mention de ce que vous avez fait pour implémenter votre fonctionnalité ni du problème que vous avez rencontré. Vous voudrez peut-être lire la section d'aide, et spécialement celle-ci: stackoverflow.com/help/how-to-ask et ceci: stackoverflow.com/help/on-topic


3 Réponses :


0
votes

Je ne fais que deviner ici, mais vous pouvez lier des fonctions à des classes en utilisant des décorateurs, par exemple:

from functools import partial

def bind(clsname, first, second=None):
    if second is None:  # class binding
        cls = globals()[clsname]
        fn = first
        name = fn.fget.__name__ if isinstance(fn, property) else fn.__name__
        setattr(cls, name, fn)
    else:  # instance binding
        self = first
        fn = second
        name = fn.fget.__name__ if isinstance(fn, property) else fn.__name__
        setattr(self, name, partial(fn, self))

class BindableMeta(type):
    def __new__(cls, name, bases, dct):
        def inner(*args):
            return bind(name, *args)
        dct["bind"] = inner
        return type.__new__(cls, name, bases, dct)


class Bindable(metaclass=BindableMeta):
    pass

class Foo(Bindable):
    pass

f = Foo()
g = Foo()


@Foo.bind
def bar(self):
    return 5

@f.bind
def bat(self):
    return 5

@Foo.bind
@property
def prop(self):
    return 5


assert f.bar() == 5
assert f.bat() == 5
try:
    assert g.bat() == 5
except AttributeError:
    pass
else:
    raise RuntimeError("shouldn't work")
assert f.prop == 5

Utilisation:

@bind(Foo)
def bar(self):
    return 7

@bind(Foo)
@property
def bat(self):
    import random
    return random.randint(1, 6)

>>> f = Foo()
>>> f.bar()
7
>>> f.bat
4

Un exemple plus avancé pour prendre en charge les propriétés et la liaison à une classe personnalisée:

def bind(cls):
    def decorator(fn):
        name = fn.fget.__name__ if isinstance(fn, property) else fn.__name__
        setattr(cls, name, fn)
        return fn
    return decorator

Utilisation:

>>> f = Foo()
>>> f.bar()
7

Enfin, la solution que vous ne devriez certainement pas utiliser, mais je ne pouvais tout simplement pas m'en empêcher:

class Foo:
    pass

def bind(fn):
    setattr(Foo, fn.__name__, fn)
    return fn

@bind
def bar(self):
    return 7


8 commentaires

Cela s'applique-t-il à toutes les instances de la classe Foo ou est-ce spécifique à une seule instance de la classe Foo. Par exemple, si vous avez une variable f et un f1 vont-ils tous les deux avoir cette fonction comme attribut


@AlexandreManeta J'ai ajouté une version plus générale.


@AlexandreManeta Ceci s'applique à toutes les instances de la classe Foo. Si vous voulez qu'il ne s'applique qu'à une seule instance, vous devrez en quelque sorte indiquer au décorateur de quelle instance il s'agit (et les propriétés ne fonctionneront pas du tout).


Votre premier exemple fonctionnerait presque pour les instances également - on peut définir un attribut sur les instances, comme ça. Tout ce qui manque est un moyen de dire à la fonction, lorsqu'elle s'exécute, l'instance à laquelle elle est liée. (Ce que fait Python en insérant l'argument self )


@jsbueno setattr (instance, fn .__ nom__, functools.partial (fn, instance)) fonctionnerait en quelque sorte


@jsbueno Découvrez ma dernière modification en utilisant des métaclasses: laid, mais amusant.


Pourquoi ne pas avoir simplement bind comme méthode de classe dans Bindable ? Le nom de la classe est récupérable. Ensuite, alors que les métaclasses sont excessives, avoir un mixin avec une méthode de classe bind qui peut être utilisée comme décorateur peut être plutôt utilisable.


@jsbueno Je ne peux pas faire fonctionner cette version de telle sorte que vous puissiez attacher une "méthode" à une seule instance.



2
votes

Le décorateur, bien sûr, doit obtenir comme paramètre l'instance à laquelle il doit faire la liaison - et, le code sera beaucoup plus propre, si la fonction décorée elle-même a un paramètre à passer comme instance à laquelle elle est liée aussi: l'équivalent de self s'il était défini comme une méthode. Python ne l'insérera pas automatiquement, mais il peut s'appeler self pour qu'il soit facilement lisible;

import functools

...

def bind(func, instance=None):
    if instance is None:
         return functools.partial(bind, instance=func)
    @functools.wraps(func)
    def wrapper(*args, **kw):
         return func(instance, *args, **kw)
    setattr(instance, func.__name__, wrapper)
    return wrapper

Si vous voulez un décorateur plus plat, vous pouvez utiliser functools.partial - et j'utilise aussi functools.wraps , car les beaux décorateurs devraient l'utiliser de toute façon:

class Transition:
   ...

f = Transition

def bind(instance):
    def decorator(func):
        def wrapper (*args, **kwargs):
              return func(instance, *args, **kwargs)
        setattr(instance, func.__name__, wrapper)
        return wrapper
    return decorator

@bind(f)
def condition(self, ...):
    ...

Cela fonctionne car en Python, lorsqu'une fonction est attribuée directement à une instance, elle se comporte exactement comme n'importe quel attribut ordinaire: Python récupérera la fonction, puis l'appellera, sans modification - sans la changer en méthode, ni en insérant un self ). Nous faisons cela en tenant l'instance comme une variable non locale dans le code du décorateur.


0 commentaires

0
votes

Je pense avoir écrit un article de blog sur un problème similaire dans le passé. Il explique comment vous pouvez lier 2 attributs de classe python ensemble afin que la mise à jour de l'un mette automatiquement à jour l'autre. J'ai illustré 2 approches possibles de ce problème. Le premier est expliqué dans ceci article de blog avec un exemple pratique. Vous envisagez peut-être également d'utiliser les concepts illustrés dans l'article dans un autre but.


0 commentaires