2

I'm creating a function that takes in a callback function as an argument. I want to be able to use it like this:

def callback1(result, found_index):
 # do stuffs
def callback2(result):
 # do same stuffs even though it's missing the found_index parameter
somefunct(callback1)
somefunct(callback2)
# somefunct calls the callback function like this:
def somefunct(callback):
 # do stuffs, and assign result and found_index
 callback(result, found_index) # should not throw error

For context, I am somewhat trying to replicate how javascript's callback functions work for the .forEach function on arrays. You can make a function that takes in only the array item on that specific iteration, or the array item and index, or even the array item, index, and original array:

let some_array = ["apple", "orange", "banana"];
function callback1(value, index) {
 console.log(`Item at index ${index}: ${value}`);
}
function callback2(value) {
 console.log(`Value: ${value}`);
}
some_array.forEach(callback1); // runs with no errors
some_array.forEach(callback2); // runs with no errors

Furthermore, I don't want the callback function to force the * operator, but also allow them to use it if needed. Thank you, wonderful people of python.

asked Mar 4, 2020 at 19:31
10
  • 1
    Just make found_index a keyword argument. As a more general note, it's not a great idea to try to replicate another language's patterns and idioms in Python. Write pythonic code. Commented Mar 4, 2020 at 19:35
  • @hitter The first snippet is python Commented Mar 4, 2020 at 19:35
  • @jordanm Thanks for pointing this out Commented Mar 4, 2020 at 19:37
  • 2
    Why not make callback2 accept two parameters, and simply ignore the second? You can either always call the callback in the same way, and let the callback decide what to do with its arguments, or you can add complicated introspection to somefunct which which determines how it should treat the callback. The former seems much more maintainable to me. Commented Mar 4, 2020 at 19:46
  • 1
    Brian's suggestion is the standard approach for Python. Commented Mar 4, 2020 at 19:54

5 Answers 5

1

(Posting this separately since it's fundamentally different to my other answer.)

If you need to pass a lot of values to some callbacks, without requiring other callbacks to declare a lot of unused parameters, a neat solution is to encapsulate all of those values in a single object. You can use collections.namedtuple to define a value type with named attributes, and then the callback can take one parameter and decide which attributes to use.

from collections import namedtuple
SomeFunctionResult = namedtuple('SomeFunctionResult', 'foo bar baz qux quz')
def some_function(callback):
 result = SomeFunctionResult('foo', 'bar', 'baz', 'qux', 'quz')
 callback(result)

Example:

>>> some_function(lambda r: print(r.foo, r.bar))
foo bar
>>> some_function(lambda r: print(r.baz, r.qux, r.quz))
baz qux quz

The downside is that this makes some_function less usable with existing functions which might expect to receive foo directly, rather than an object with a foo attribute. In that case, you have to write some_function(lambda r: blah(r.foo)) which is not as neat as some_function(blah).

answered Mar 4, 2020 at 20:52
Sign up to request clarification or add additional context in comments.

3 Comments

this would work well with only calling the callback once, but what about calling the callback multiple times from, for example, a for loop? How would you structure it?
@FluffyDoggo I don't see the problem with putting this directly in a loop. In principle you could compose them at the start of the function like new_callback = lambda *args: old_callback(SomeFunctionResult(*args)) and then call new_callback normally within the loop. But creating the SomeFunctionResult object in the loop directly seems like it should be fine anyway.
Ok, so this should work well then. Thank you very much!
1

The simplest approach would be to unify the signatures of your callbacks. Let's say you defined your forEach function as follows

def forEach(iterable, callback):
 for index, elem in enumerate(iterable):
 callback(elem, index)

You could then define Python analogs of the callack1 and callback2 Javascript functions as

def callback1(value, index):
 print(f"Item at index {index}: {value}")
def callback2(value, _index):
 print(f"Value: {value})

Rather than performing any complicated parameter-count-reasoning, exception handling, or dynamic dispatch within forEach, we delegate the decision of how to handle the value and index arguments to the callbacks themselves. If you need to adapt a single-parameter callback to work with forEach, you could simply use a wrapper lambda that discards the second argument:

forEach(some_iterable, lambda value, _index: callback(value))

However, at this point, you just have an obfuscated for loop, which would be much more cleanly expressed as

for elem in some_iterable:
 callback(elem)
answered Mar 4, 2020 at 19:59

3 Comments

I honestly don't understand what you did with the lambda value
Is it from a specific python doc chapter? If yes please link this. I would like to know
@hitter Could you elaborate on your confusion? The lambda simply acts as a callable which accepts two arguments, which then ignores the second and passes only the first to the callback. Regarding your second comment, are you looking for the section on lambda expressions?
0

In this case, it is easier to ask for forgiveness than permission.

def some_function(callback):
 result = 'foo'
 found_index = 5
 try:
 callback(result, found_index)
 except TypeError:
 callback(result)

Example:

>>> some_function(print)
foo 5
>>> some_function(lambda x: print(x))
foo
answered Mar 4, 2020 at 20:04

7 Comments

Exceptions should be the exception, not the rule. It's right there in the name. My experience with other languages (new to Python, old hand at C++, Java) leads me to expect exceptions to not be cheap, and that using them as part of the regular control flow of your program will utterly destroy its performance.
@MarkStorer That is usually my opinion too, but in this case the alternative (if you actually want the OP's behaviour, rather than an argument for why the OP shouldn't want it) is runtime inspection of the function's signature, which is a lot messier. It's also quite standard to use try/catch this way - the official docs call this style "common", "clean and fast", so this is a losing battle, I'm afraid. There are far less justifiable uses in the Python standard library and popular third-party libraries, e.g. numpy.
Here's the built-in json module using except KeyError instead of using if to test whether a key is in a dictionary: github.com/python/cpython/blob/master/Lib/json/encoder.py#L48
"All the cool kids are doing it" is not a valid justification for crappy behavior. Or in a more pop cultural way "I realize the council has made a decision, but because it's a stupid-ass decision, I have elected to ignore it!" (Nick Fury: The Avengers)
@MarkStorer Woah, hold on now - using a coding style you (and I) have a negative opinion of is not "crappy behaviour". I am on your side in this debate, but those on the other side include highly-respected experts such as core Python developer Raymond Hettinger who says, "In the Python world, using exceptions for flow control is common and normal", and the linked answer is not unsupported by arguments in favour of the style. I think it is better to use if/else for normal control flow, but also coding style rules are never universal.
|
0

this is the modified python code snippet you have provided that produces error , this works with no problem , you just have to unify the callback arguments number and type for each callback function called within the main function and define somefunc before calling it .

def callback1(result, found_index):
 # do stuffs 
 result="overridden result in callback 1"
 found_index ="overridden found_index in callback 1"
 print(result,found_index)
def callback2(result,found_index):
 # do same stuffs even though it's missing the found_index parameter
 result="overridden result in callback 2"
 print(result,found_index)
 # somefunct calls the callback function like this:
def somefunct(callback):
 # do stuffs, and assign result and found_index
 result = "overridden result in somefunct"
 found_index = "overridden index in somefunct"
 callback(result, found_index) # NOW it should not throw error as the callback is fed with the 2 arguments used in callback1 and ignored in callback2
somefunct(callback1)
somefunct(callback2)
answered Mar 4, 2020 at 20:12

Comments

-1

use optional arguments and check how much elemnts returned, sort of switch case: https://linux.die.net/diveintopython/html/power_of_introspection/optional_arguments.html

answered Mar 4, 2020 at 19:49

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.