3

I'm trying to bind a function to an object instance. For example, I have an object and I'm trying to bind a function to the condition attribute. The thing is that I want to do that using a decorator:

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]

Then I bind a function to the object's instance.

@bind
my_condition():
    return True

After this, we should have a reference to the given function if we look at the object's instance condition attribute

Edit 1:

f = Transition()
f1 = Transition()

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

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

The f instance should have a reference to the condition1 function and the f2 instance should have a reference to the condition2 for the condition attribute

4
  • 2
    It's not clear what your question is... you want that all other objects look up their attribute normally, but one specific object should use a property/function for it? Why would you use the decorator syntax for that? Do you want to put this function as a method in that class? How do you define which attribute that is? Commented Jan 4, 2019 at 14:03
  • Welcome to SO. I'm afraid your question is not really a question - you stated what you wanted to do, but you do not explain what your actual problem is. Note that if the question is "how to do that", you're supposed to first try by yourself. Commented Jan 4, 2019 at 14:52
  • @brunodesthuilliers I did. I also searched for the answer online but couldn't find it. Commented Jan 4, 2019 at 14:59
  • @AlexandreManeta "You did" what ? Explain your actual problem ? Sorry but you didn't - your post contains absolutely no mention of what you did to implement your feature nor what problem you had. You may want to read the help section, and specially this: stackoverflow.com/help/how-to-ask and this : stackoverflow.com/help/on-topic Commented Jan 4, 2019 at 15:05

3 Answers 3

3

The decorator, of course, must get as a parameter the instance it is to make the bind to - and, the code will be much cleaner, if the decorated function itself have one parameter to be passed as the instance it is bound too: the equivalent to self if it were defined as a method. Python won't insert it automatically, but it may be called self so it is easily readable;

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, ...):
    ...

If you want a flatter decorator, you can use functools.partial - and then I also use functools.wraps, as nice decorators should use it anyway:

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

This works because in Python, when a function is attributed directly to an instance, it behaves exactly like any ordinary attribute: Python will retrieve the function, and then call it, with no modifications - not changing it to method, neither inserting a self argument). We do that by holding the instance as a nonlocal variable inside the decorator code.

Sign up to request clarification or add additional context in comments.

Comments

0

I think I wrote a blog post about a similar problem in the past. It explains how you can bind 2 python class attributes together so that updating one automatically updates the other. I illustrated 2 possible approaches to this problem. The first one is explained in this blog post with a practical example. You might also be planning to use the concepts illustrated in the post for another purpose.

Comments

0

I'm just guessing here, but you can bind functions to classes using decorators, e.g.:

class Foo:
    pass

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

@bind
def bar(self):
    return 7

Usage:

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

A more advanced example to support properties and binding to a custom class:

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

Usage:

@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

Finally, the solution you should definitely not use, but I just couldn't stop myself:

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

8 Comments

Does this apply to every instances of the Foo class or is it specfic to only one instance of the Foo class. For example, if you have a f variable and a f1 are they both going to have this function as an attribute
@AlexandreManeta I added a more general version.
@AlexandreManeta This applies to every instance of the Foo class. If you want it to apply only to one instance, you'll need to somehow tell the decorator which instance that is (and properties won't work at all).
Your first example would almost work for instances as well - one can set an attribute on the instances, just like that. All that is missing is a way to tell the function, when it runs, the instance it is bound too. (What Python does by inserting the self argument)
@jsbueno setattr(instance, fn.__name__, functools.partial(fn, instance)) would kind of work
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.