How to pass a python function with parameters (a closure) into another function?

Viewed 421

I am a Python noob currently working with rabbitMQ. I receive a payload from rabbitMQ and process it. Here is the first function

def verify_user_id(ch, method, properties, body):
    print("[*] Received message for NIN verification")
    body = json.loads(body)  # convert JSON string to python object
    parameters = body.get('parameters')
    id_info = {
        'first_name': parameters.get('first_name'),
        'last_name': parameters.get('last_name'),
        'country': parameters.get('country'),
        'id_type': parameters.get('id_type'),
        'id_number': parameters.get('id_number'),
        'entered': parameters.get('entered')
    }
    partner_params = {
        'job_id': parameters.get('job_id'),
        'user_id': parameters.get('user_id'),
        'job_type': parameters.get('job_type')
    }
    return body, id_info, partner_params

Now, I want to use the function above to send a request to a server as shown below:

def smile_identity_func():
    body, id_info, partner_params = verify_user_id()
    try:
      
         connection = IdApi(str(partner_id), str(api_key), sid_server)
         response = connection.submit_job(partner_params, id_info)
         obj = response.json()
         return jsonify(obj)
        
        ch.basic_ack(delivery_tag=method.delivery_tag)
        print(" [x] Done")
    except ValueError as e:
        # some params are not valid
        return str(e)
    except ServerError as e:
        return str(e)

I know that body, id_info, partner_params = verify_user_id() is wrong, but I do not know how to properly pass verify_user_id() with its parameters ch, method, properties, body into the smile_identity_func().

I want to be able to do it in such a way that I do not have to specify values for `ch, method, properties,body'.

1 Answers

What your asking for is the classic use case for a Closure, or Python-specifically, A Python Closure.

Here's how that works:

# Create a closure on the `request` function that hard-codes the passed in
# parameters as the context variables to be used when executing the function
def get_request_function(username, password, email):

    # The standard function to call
    def request():
        print("Username {} | Password {} | Email {}".format(username, password, email))

    # return the closure on 'request'
    return request

# Another function that simply calls the function represented by the
# reference passed to it.
def used_passed_in_function(f):
    f()

# Get an instance of our closure, a call to "request", that bakes in
# the context variables we provide.
f = get_request_function("x3-", "f8dJsn9/sd", "x3-@gmail.com")

# Finally, call our sample function that calls whatever function is passed
# to it, passing in our closure to be the function that is called.
used_passed_in_function(f)

Result:

Username x3- | Password f8dJsn9/sd | Email x3-@gmail.com

This truly gives you what you want, the ability to pass some function into another function with its parameters already bound to the function.

Note that from the perspective of the used_passed_in_function, there is no difference between a closure and a reference to a regular function that takes no parameters. The two are, however, fundamentally very different.

I figured I'd answer the question generically first. Here's an example specific to the code you provide:

def smile_identity_func(verify_func):
    # This call to "verify_func" is a call to "verify_user_id" with a set of
    # parameters hard-coded into the call.
    body, id_info, partner_params = verify_func()
    ...

def create_verify_user_id_closure(ch, method, properties, body):
    def baked_verify_user_id():
        return verify_user_id(ch, method, properties, body)
    return baked_verify_user_id

verify_id = create_verify_user_id_closure("sample-ch", "sample-method", "sample-properties", "sample-body")
smile_identity_func(verify_id)
Related