user3076411
user3076411

Reputation: 117

Convert a bound method in python to a function (and reduce arg count)

I am adding a URL handler to a Flask application using add_url_rule. It requires a function as a handler (not a method). However, for proper encapsulation, my intended function is a bound method that uses the self variable. Is there any way I can somehow make Flask see my function: def action(self, param) as def action(param) ?

class A():
    def __init__(self):
        self._localvar = 5
        self._flask = Flask("testapp")

    def add_rules(self):
        self._flask.add_url_rule("/","root",????)

    def action(self, value):
        print("The value is {}".format(self._localvar))

    def run(self):
        self._flask.run()

app = A()
app.add_rules()
app.run()

what I want to do is replace ???? with app.action, a bound method. However, add_url_rule expects a function. I tried app.action.__func__ and app.action.im_func which are function pointers. However, the function signature is still going to require self and will give me a runtime error.

My current solution is to have some loose functions in my module and have app just use them directly, but it just seems that there has to be a cleaner way since the handlers are really tightly coupled to the app object and I don't want them floating around outside of it.

Upvotes: 1

Views: 5839

Answers (4)

Matthias
Matthias

Reputation: 1

Try passing lambda x:A.action(app,x) to add_url_rule.

Upvotes: 0

CyanoKobalamyne
CyanoKobalamyne

Reputation: 237

This is an old question, but I thought it might be useful to know a possibly easier solution for someone (like me) visiting this page.

You can access the underlying function of a bound method as its __func__ attribute. Also, if you access it as an attribute of the class and not the instance, it will give you the function.

class C:
    def fn(self, param):
        return param

instance = C()
print(instance.fn("hello"))  # 'hello'
print(instance.fn.__func__(instance, "hello"))  # 'hello'
print(instance.fn.__func__(None, "hello"))  # 'hello'
print(C.fn(instance, "hello"))  # 'hello'
print(C.fn(None, "hello"))  # 'hello'

Upvotes: 4

user3076411
user3076411

Reputation: 117

I Think I finally found a way to do this (i.e. keep the action method bound inside my class and have a function-like reference to it when needed). I used a function inside a method:

class A():
    def __init__(self):
        self._localvar = 5
        self._flask = Flask("testapp")

    def add_rules(self):
        self._flask.add_url_rule("/","root",self.action_as_func())

    def action(self, value):
        print("The value is {}".format(self._localvar))

    def action_as_func(self):
        def inner_action(value):
            self.action(value)
        return inner_action

    def run(self):
        self._flask.run()

app = A()
app.add_rules()
app.run()

Any comments on whether there is something wrong with this ? The function can access self. Kinda sneaky :)

Upvotes: 1

jackdbd
jackdbd

Reputation: 5061

What I want to know is whether there is a python way to somehow "cast" a def action(value) to the def action(self, value) [...]

And what if you do it the other way around? You define your actions as functions (or staticmethods if you want to keep them in the class A), and bind them to your instance only when you need them.

You could use the types module to bind a function to an instance.

import types

class A():

    def bind_action_func_to_instance(self, func, value):
        self.action_method = types.MethodType(func, self)

    @staticmethod
    def action_func(value):
        print('action with value: {}'.format(value))

    def add_rules():
        self._flask.add_url_rule("/","root", self.action_func)

You could also implement a default action method and assign it to action_method in the __init__. It will then be reassigned when you call bind_action_func_to_instance.

I think you could also find useful this answer about the Strategy pattern.

Upvotes: 2

Related Questions