0

Here is what I want to do:

class demo(object):
    def a(self):
        pass

    def b(self, param=self.a):  #I tried demo.a as well after making a static
        param()

The problem is apparently that one can't access the class in the function declaration line. Is there a way to add a prototype like in c(++)?

At the moment I use a ugly workarround:

def b(self, param=True): #my real function shall be able to use None, to skip the function call
    if param == True:
        param = self.a

    if param != None: #This explainds why I can't take None as default,
                      #for param, I jsut needed something as default which was 
                      #neither none or a callable function (don't want to force the user to create dummy lambdas)
        param()

So is it possible to achieve something like described in the top part without this ugly workarround? Note bene: I am bound to Jython which is approximately python 2.5 (I know there is 2.7 but I can't upgrade)

ted
  • 4,457
  • 5
  • 30
  • 76

6 Answers6

3

Short answer: No.

I think the best way to do it, if you want to be able to pass objects like None, True, etc., is to create a custom placeholder object like so:

default_value = object()

class demo(object):
    def a(self):
        pass

    def b(self, param=default_value):
        if param is default_value:
            self.a()
        else:
            param()

You can use the funciton a as the default value for b, like so:

    def b(self, param=a):

This will work long as a is defined before b. But the function a is not the same as the bound method self.a, so you'd need to bind it before you could call it, and you would need some way to distinguish a passed callable from the default method a, so that you could bind the latter but not the former. This would obviously be much messier than the comparatively short and readable code that I suggest.

Lauritz V. Thaulow
  • 41,893
  • 11
  • 64
  • 87
3

Don't tell anyone I showed you this.

class demo:
    def a(self): print(self, "called 'a'")
    def b(self, param): param(self)
demo.b.__defaults__ = (demo.a,)

demo().b()

(In 2.x, __defaults__ is spelled func_defaults.)

Karl Knechtel
  • 51,161
  • 7
  • 77
  • 117
  • while i like layzers solution, I guess I will stick with mine. I accept this as answer so, since it does exactly what I asked for, even though this is ugly because the default value is not obvious, I guess this is why you dont want me to tell anyone (If there are other resons, let me know). Additionally, thanks for adding the right name for python 2.5 and 3 (I guess). Nice to know how it should be done once Jython catches up. – ted Jun 13 '12 at 11:47
  • 1
    Upvote for teaching me something new. (By the way, 2.x equivalent to `__defaults__` is `im_func.func_defaults`.) However, there are complications with this solution. The `param` callable has changed signatures. In OP's question it's `param()`, now it's `param(self)`. This is because the default still isn't what OP wanted, which was `self.a`. It's now `demo.a`, an [unbound method](http://stackoverflow.com/a/3589335/566644). So in effect, this accomplishes the same thing as what I suggested at the bottom of my answer -- you bind the method by explicitly using `self` as an argument. – Lauritz V. Thaulow Jun 14 '12 at 07:38
1

You can put the method name in the function definition:

class Demo(object):

    def a(self):
        print 'a'

    def b(self, param='a'):
        if param:
            getattr(self, param)()

However, you'll still have to check whether param has a value of whether it is None. Note that this approach should not be used for untrusted input as it allows execution of any function of that class.

Simeon Visser
  • 106,727
  • 18
  • 159
  • 164
  • Apart from the `getattr`, this is a nice and straightforward solution. It allows client code to ask for the default behavior explicitly, which is a Good Thing™. – Fred Foo Jun 13 '12 at 11:31
1

I like lazyr's answer but maybe you will like this solution better:

class Demo(object):
    def a(self):
        pass

    def b(self, *args):
        if not args:
            param=self.a
        elif len(args)>1:
            raise TypeError("b() takes at most 1 positional argument")
        else:
            param=args[0]
        if param is not None:
            param()
Community
  • 1
  • 1
Oleh Prypin
  • 29,114
  • 9
  • 81
  • 92
1

I also prefer lazyr's answer (I usually use None as the default parameter), but you can also use keyword arguments to be more explicit about it:

def b(self, **kwargs):
    param = kwargs.get('param', self.a)
    if param: param()

You can still use None as a parameter, resulting in param not being executed. However, if you don't include the keyword argument param=, it will default to a().

demo.b() #demo.a() executed

demo.b(param=some_func) #some_func() executed

demo.b(param=None) #nothing executed.
Joel Cornett
  • 21,725
  • 6
  • 57
  • 82
  • I want to use None to indicate no function call and have another deafult, thats why i pass in some boolean. but usually I agree for None as the best option – ted Jun 13 '12 at 11:44
  • @ted: Ah, I see. Well, as as far as the above code goes, you should still be able to to pass `None` as a parameter. – Joel Cornett Jun 13 '12 at 11:48
1

I'll answer this question again, contradicting my earlier answer:

Short answer: YES! (sort of)

With the help of a method decorator, this is possible. The code is long and somewhat ugly, but the usage is short and simple.

The problem was that we can only use unbound methods as default arguments. Well, what if we create a wrapping function -- a decorator -- which binds the arguments before calling the real function?

First we create a helper class that can perform this task.

from inspect import getcallargs
from types import MethodType
from functools import wraps

class MethodBinder(object):
    def __init__(self, function):
        self.function = function

    def set_defaults(self, args, kwargs):
        kwargs = getcallargs(self.function, *args, **kwargs)
        # This is the self of the method we wish to call
        method_self = kwargs["self"]

        # First we build a list of the functions that are bound to self
        targets = set()
        for attr_name in dir(method_self):
            attr = getattr(method_self, attr_name)
            # For older python versions, replace __func__ with im_func
            if hasattr(attr, "__func__"):
                targets.add(attr.__func__)

        # Now we check whether any of the arguments are identical to the 
        # functions we found above. If so, we bind them to self.
        ret = {}
        for kw, val in kwargs.items():
            if val in targets:
                ret[kw] = MethodType(val, method_self)
            else:
                ret[kw] = val

        return ret

So instances of MethodBinder are associated with a method (or rather a function that will become a method). MethodBinders method set_defaults may be given the arguments used to call the associated method, and it will bind any unbound method of the self of the associated method and return a kwargs dict that may be used to call the associated method.

Now we can create a decorator using this class:

def bind_args(f):
    # f will be b in the below example
    binder = MethodBinder(f)

    @wraps(f)
    def wrapper(*args, **kwargs):
        # The wrapper function will get called instead of b, so args and kwargs
        # contains b's arguments. Let's bind any unbound function arguments:
        kwargs = binder.set_defaults(args, kwargs)

        # All arguments have been turned into keyword arguments. Now we
        # may call the real method with the modified arguments and return
        # the result.
        return f(**kwargs)
    return wrapper

Now that we've put the uglyness behind us, let's show the simple and pretty usage:

class demo(object):
    def a(self):
        print("{0}.a called!".format(self))

    @bind_args
    def b(self, param=a):
        param()

def other():
    print("other called")

demo().b()
demo().b(other)

This recipe uses a rather new addition to python, getcallargs from inspect. It's available only in newer versions of python2.7 and 3.1.

Lauritz V. Thaulow
  • 41,893
  • 11
  • 64
  • 87
  • awesome. I just learned a little bit more about python. I did not know that one could define own decorators. +1 for effort. Don't bother with the older python code it will be find, but i will take this to learn from once i get to use python 2.7 (its near jython 2.7 beta is out...) – ted Jun 19 '12 at 07:04
  • @ted I've looked at the source code for `getcallargs` from inspect.py, and it seems quite portable. It's possible it will work "out of the box" in python 2.3 if you just copy that funcion into your source code. – Lauritz V. Thaulow Jun 19 '12 at 09:51