Is there a way to do more work after a return statement?

Question:

I was a bit curious if I could do more work in a function after returning a result. Basically I’m making a site using the pyramid framework(which is simply coding in python) after I process the inputs I return variables to render the page but sometimes I want to do more work after I render the page.

For example, you come to my site and update your profile and all you care about is that its successful so I output a message saying ‘success!’ but after that done I want to take your update and update my activity logs of what your doing, update your friends activity streams, etc.. Right now I’m doing all that before I return the result status that you care about but I’m curious if I can do it after so users get their responses faster.

I have done multi-processing before and worst case I might just fork a thread to do this work but if there was a way to do work after a return statement then that would be simpler.

example:

def profile_update(inputs):
  #take updates and update the database 
  return "it worked"
  #do maintenance processing now.
Asked By: Lostsoul

||

Answers:

No, unfortunately, once you hit the return statement, you return from the function/method (either with or without a return value).

From the docs for return:

return leaves the current function call with the expression list (or None) as return value.

You may want to look into generator functions and the yield statement, this is a way to return a value from a function and continue processing and preparing another value to be returned when the function is called the next time.

Answered By: Levon

Why don’t you use a contextmanager? It basically does exactly what you want.

Here’s the canonical example from the Python docs.

from contextlib import contextmanager

@contextmanager
def tag(name):
    print "<%s>" % name
    yield
    print "</%s>" % name

So for your function, you’d just do:

@contextmanager
def profile_update(inputs):
  #take updates and update the database 
  yield "it worked"
  #do maintainence processing now..

And to call it, you’d just do:

with profile_update(inputs) as result: #pre-yield and yield here
    # do whatever while in scope
# as you move out of scope of with statement, post-yield is executed

EDIT: I was just testing things out, and it turns out that, with a yield statement, the function still executes to the end. Here’s a dumb example that illustrates the point and when things get executed.

def some_generator(lst):
    for elem in lst:
        yield elem
    lst[0] = "I WAS CHANGED POST-YIELD!!!!"

>>> q = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> gen = some_generator(q)
>>> for e in gen:
...    print e, q

0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
4 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
5 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
6 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
7 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
8 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
9 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print q
['I WAS CHANGED POST YIELD!!!', 1, 2, 3, 4, 5, 6, 7, 8, 9]

A contextmanager has the advantage of not requiring two next calls to get to the stop iteration (and cleaner syntax), but if you wanted to return multiple values or something, you could also do it this way, but you can see that the post yield statement doesn’t actually get called until the generator raises StopIteration on the next call (the for loop ends when it gets StopIteration)


If for some reason, you require a higher degree of control than @contextmanager offers, you can also define a class with __enter__ and __exit__ methods:

class MyContextClass(object):
    # ...

    def __enter__(self):
        # do some preprocessing
        return some_object

    def __exit__(self, exc_type, exc_value, traceback):
        # do some post processing
        # possibly do some processing of exceptions raised within the block
        if exc_type == MyCustomErrorType:
            return True #don't propagate the error
Answered By: Jeff Tratner

You could still do some work after return if you return from a try-block, the finally-block would still be executed, e.g.:

def fun(x):
    try:
        return x * 20
    finally:
        print("Yay! I still got executed, even though my function has already returned!")

print(fun(5))

Expected Output:

Yay! I still got executed, even though my function has already returned!
100

Quoting the docs:

When return passes control out of a try statement with a finally
clause, that finally clause is executed before really leaving the
function.

Answered By: Giel

No, a return gives the value back to the caller and stops.

If the caller(s) are also under your control (not part of the pyramid framework), you could change profile_updates to look like the following:

def profile_update(inputs):
    #take updates and update the database 
    def post_processing_task():
        #do maintainence processing now..
    return ("it worked", post_processing_task)

And then code the caller to expect a pair of (response, task), rather than just a response. It can do something immediately with the response part (communicate it to the user), then call task() to handle the post-processing.

This allows profile_update to determine what code needs to be executed afterwards (and keep those details hidden and encapsulated from the higher level), but allows the higher level to determine the flow of communicating a response to the user and then executing the post-processing in the background.

Answered By: Ben

It is possible to cheat with the try-except-finally structure. Example:

def function():
  try:
    #do some stuff here
    return x
  except Exception:
    #do something when an error occures
  finally:
    #do here whatever you wanna do after return

Note, that the finally statement will be executed even if an exception was caught.

Answered By: e. Tronics
import threading

def profile_update(inputs):

    # call function to take updates and update the database 
    update_function(inputs)

    # call the maintainence_function here
    t = threading.Thread(target=maintainence_function, args=[input1, input2])
    # setDaemon=False to stop the thread after complete
    t.setDaemon(False)
    # starting the thread
    t.start()

    # return response/anything-else you want to return
    return "it worked"



def update_function(inputs):
    # updating the database process here

def maintainence_function(input1, input2):
    #do maintainence processing now..

Here we use the threading functionality of python.

First we call the update function(you can also use this function in the thread if needed and if the response doesn’t depend on this function and if you need to give the response immediately).

Then we have create a thread which will complete the maintainence_function function and stop after finishing it. But the response will not be delayed until that function is finished.

ie: return “it worked” will be returned and then also the thread maintains the working of the function maintainence_function if ts a bit process.

Answered By: Shinto Joseph

i think it’s what are you searching about:

def foo():
# do stuff
try:
return something
finally:
# after return

Answered By: Gianla

You can use the Timer to schedule an event to occur asynchronously at some point in time. You can also give the interval after which the event shall occur. I hope the below code and output helps.

import time
from threading import Timer

def func():
    print("Inside func at", time.time())

def schedule_an_event():
    print(time.time())
    Timer(3, func).start()
    return "Done"

print(schedule_an_event())

Output:

1579682455.5378997
Done
Inside func at 1579682458.5382733
Answered By: Iket Agrawal

What you’re asking is not possible as when you give the return statement the function scope is terminated at that point but you can try a different approach

def show_status():
  return "it worked"

def profile_update(inputs):
  #take updates and update the database 
  show_status()
  #do maintainence processing now..
  return  # when you are done with the maintainence processing 
Answered By: Saurabh Mahra

I like Shinto Joseph’s answer the best where we use threading to split of another process. I, however, believe this example exemplifies his idea better:

import threading
from time import sleep

def math_fun(x):
    # The sleep here is simply to make it clear that this happens in the background
    sleep(1)
    print(x*20)


def fun(x):
    # Create thread to run math_fun for each argument in x 
    t = threading.Thread(target=math_fun, args=[x])
    t.setDaemon(False)
    t.start()

    print("Function has returned!")

fun(5)

Expected Output:

Function has returned!
100
Answered By: Ashtacore

Being aware that it’s an old topic and already has good answers, but maybe newer users find my idea attractive as well:

def master_profile_update(inputs):
    # since input parameters are global inside the upper scope
    # i omit them from the arguments of lower nested function:
    def profile_update()
        #take updates and update the database 
        return "it worked"

    profile_update()
    #do maintenance processing now..

I’ve found it more conventional than decorators and contextmanagers.

Answered By: S.Khajeh
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.