1

I'm having the problem of a button executing its' command when it's created. To stop this I have got a function, which can stop this behavior

This is the function which makes functions callable without being executed while creating my button. Usually it works fine but with some functions it seems to deny randomly any input! Here is the code:

class Callable(object):
    def __init__(self, func, *args, **kwds):
        self.func = func
        self.args = args
        self.kwds = kwds

    def __call__(self, *args, **kwds):
         return self.func(self.args)

    def __str__(self):
        return self.func.__name

It seems to be totally randomly which questions are accepted and which aren't. I'm really desperate, because it takes a lot of time to write a kind of synonym of this class, I adapt them with the number of args and kwds, then it works ok. But now I'm coming to a point where I don't know how many args I'm going to pass, so this won't work any more.

Question:

  • Why does this class doesn't accept every function?
  • How can I change this behaviour?
  • Provide an example of a function that works, and a function that doesn't. – Kevin Mar 24 '16 at 17:02
  • @Kevin: This will be hard, I'm using a module called swampy, it's a modulation for tkinter. It changes all the syntax and as I said it is random which functions work and which don't. I'm quite sure I would have seen a pattern –  Mar 24 '16 at 17:05
  • Can you explain exactly what this is supposed to do? e.g. you seem to be passing `*args` to both `__init__` and `__call__`. If I did: `Callable(func, 1, 2, 3)(4, 5)` -- What is the equivalent `func` call that you're expecting? e.g. is this the same as `func(1, 2, 3)`, `func((1, 2, 3))`, `func(1, 2, 3, 4 ,5)`, `func(4, 5)`, ... – mgilson Mar 24 '16 at 17:06
  • Incidentally, if you're just trying to construct callables that have arguments "preloaded", consider using [functools.partial](https://docs.python.org/2/library/functools.html#functools.partial) or lambda expressions. – Kevin Mar 24 '16 at 17:08
  • In fact I had the problem of my buttons to execute the commands stored in the function, which were executed directly, when I created the button. With this function,that problem was solved. Can this 'directly executed commands' be solved in an other way?!?!?!? –  Mar 24 '16 at 17:09
  • @ErichSchmidt -- Yep. You could use `lambda : func(a, b, c)`, or `functools.partial(func, a, b, c)` – mgilson Mar 24 '16 at 17:14
  • "I'm having a function which makes functions callable" - what? You don't need a special function to make functions callable. Functions are callable naturally. – user2357112 Mar 24 '16 at 17:15
  • @ErichSchmidt It sounds like your original problem was that you called the function while creating the buttons instead of passing just the function name to the button. – tdelaney Mar 24 '16 at 17:16
  • @user2357112: See the dialogue above –  Mar 24 '16 at 17:18
  • @tdelaney; Did I? I didn't know! But this problem is solved now anyway, I think. –  Mar 24 '16 at 17:19
  • @ErichSchmidt Not really, Can you post a snippet on how you create your buttons? – tdelaney Mar 24 '16 at 17:22
  • `bu = Button(master=frame, text='Press me', command=function(arg1, arg2, kwd1, kwd2)` like this –  Mar 24 '16 at 17:24
  • @mgilson I expect this: `func(1, 2, 3)` –  Mar 24 '16 at 17:30
  • I see, it looks like you want to provide context for your Button callback. Your class almost works, you just need to call `self.func(self.*args, self.**kw)`. – tdelaney Mar 24 '16 at 17:31
  • @ tdelaney Where do I need to implement this? –  Mar 24 '16 at 17:33
  • "makes functions callable" is a very odd thing to do. Aren't functions, by definition, already callable? – Bryan Oakley Mar 24 '16 at 20:11
  • @BryanOakley -- Yeah. I think OP is trying to learn how to curry function arguments. – mgilson Mar 24 '16 at 20:22

3 Answers3

3

I believe that's what you're looking for:

class Callable(object):
    def __init__(self, func, *args, **kwds):
        self.func = func
        self.args = args
        self.kwds = kwds

    def __call__(self, *args, **kwds):
         return self.func(*self.args, *args, **self.kwds, **kwds)

    def __str__(self):
        return self.func.__name

You need to unpack it with the * operator, and ** for keyword arguments. That way you pass your variables and the function's call variables.

UPDATE:

For python versions older than 3.5, this will work:

class Callable(object):
    def __init__(self, func, *args, **kwds):
        self.func = func
        self.args = args
        self.kwds = kwds

    def __call__(self, *args, **kwds):
        args = self.args + args
        kwargs = kwds.copy()
        kwargs.update(self.kwds)
        return self.func(*args, **kwargs)

    def __str__(self):
        return self.func.__name

Using this solution, you will first give the variables acquired by the __init__ then the variables passed to the __call__.

Bharel
  • 23,672
  • 5
  • 40
  • 80
  • Note, this syntax with mutiple splat operators isn't available until [python3.5](https://www.python.org/dev/peps/pep-0448/) IIRC. For earlier versions, you'll need to `*(self.args + args)` and do the analogous thing for the keyword argument unpacking... `kwds.update(self.kwds); func(*(self.args + args), **kwds)` – mgilson Mar 24 '16 at 17:10
  • Sorry I'm python 3.4. Can you pack it into your answer please? I'm not sure what to do now! –  Mar 24 '16 at 17:14
  • 1
    This answer places an odd requirement on positional arguments. Consider `fctn(a,b,c,d)` - if any args are given to the class initializer they will precede any args passed to `__call__`. If OP is trying to implement a partial class, then maybe that's Okay but it should be very explicitly stated. – tdelaney Mar 24 '16 at 17:20
  • @tdelaney Thanks, stated it in bottom line. – Bharel Mar 24 '16 at 17:33
  • this thing worked too, I tried it out. Nevertheless, I prefer `lambda`, it's shorter –  Mar 25 '16 at 17:25
  • @Erich this and the lambda solution is not the same thing. My solution is more like itertools.partial – Bharel Mar 27 '16 at 16:38
0

Your class is used to provide additional context to tkinter callbacks so the callback can actually do something useful. You almost have it right except that you need to unpack the original args and kwds when calling the function. Also, don't include any args in __call__ because you don't accept any.

class Callable(object):
    def __init__(self, func, *args, **kwds):
        self.func = func
        self.args = args
        self.kwds = kwds

    def __call__(self):
         return self.func(*self.args, **self.kwargs)

    def __str__(self):
        return self.func.__name


def some_callback(a, b, c=None):
    print(a,b,c)

Button(text="Do not press", 
    command=Callable(some_callback, 1, 2, 3))

This can also be done without a class, using lambdas instead

def some_callback(a, b, c=None):
    print(a,b,c)

Button(text="Do not press", 
    command=lambda: some_callback(1, 2, 3))
tdelaney
  • 73,364
  • 6
  • 83
  • 116
0

Please consider using the following class. It allows you to specify positional arguments and keyword arguments at the time of either instance creation or instance invocation (creating a new instance or calling it). Since it is ambiguous what would be meant if either types of arguments were specified at both times, the class refuses to guess what order or priorities were intended and raises a RuntimeError to prevent undefined behavior. Also, the __str__ method should still work if your function or other callable object does not have a __name__ attribute.

class Callable:

    def __init__(self, function, *args, **kwargs):
        self.__function, self.__args, self.__kwargs = function, args, kwargs

    def __call__(self, *args, **kwargs):
        if (args or kwargs) and (self.__args or self.__kwargs):
            raise RuntimeError('multiple args and kwargs are not supported')
        if args or kwargs:
            return self.__function(*args, **kwargs)
        return self.__function(*self.__args, **self.__kwargs)

    def __str__(self):
        return getattr(self.__function, '__name__', 'function')

You might also want to take a look at functools.partial for a well-defined object with very similar behavior. You may be able to avoid defining your own Callable class and use the functools module instead. That way, there is less code that you have to manage in you project.

Noctis Skytower
  • 21,433
  • 16
  • 79
  • 117