\[ [Index](index.md) | [Exercise 5.1](ex5_1.md) | [Exercise 5.3](ex5_3.md) \] # Exercise 5.2 *Objectives:* - Returning values from functions In this exercise, we briefly look at problems related to returning values from functions. At first glance, it seems like this should be straightforward, but there are some subtle problems that arise. ## (a) Returning Multiple Values Suppose you were writing code to parse configuration files consisting of lines like this: name=value Write a function `parse_line(line)` that takes such a line and returns both the associated name and value. The common convention for returning multiple values is to return them in a tuple. For example: ```python >>> parse_line('email=guido@python.org') ('email', 'guido@python.org') >>> name, val = parse_line('email=guido@python.org') >>> name 'email' >>> val 'guido@python.org' >>> ``` ## (b) Returning Optional Values Sometimes a function might return an optional value--possibly as a mechanism for indicating success or failure. The most common convention is to use `None` as a representation for a missing value. Modify the `parse_line()` function above so that it either returns a tuple on success or `None` on bad data. For example: ```python >>> parse_line('email=guido@python.org') ('email', 'guido@python.org') >>> parse_line('spam') # Returns None >>> ``` Design discussion: Would it be better for the `parse_line()` function to raise an exception on malformed data? ## (c) Futures Sometimes Python code executes concurrently via threads or processes. To illustrate, try this example: ```python >>> import time >>> def worker(x, y): print('About to work') time.sleep(20) print('Done') return x + y >>> worker(2, 3) # Normal function call About to work Done 5 >>> ``` Now, launch `worker()` into a separate thread: ```python >>> import threading >>> t = threading.Thread(target=worker, args=(2, 3)) >>> t.start() About to work >>> Done ``` Carefully notice that the result of the calculation appears nowhere. Not only that, you don't even know when it's going to be completed. There is a certain coordination problem here. The convention for handling this case is to wrap the result of a function in a `Future`. A `Future` represents a future result. Here's how it works: ```python >>> from concurrent.futures import Future >>> # Wrapper around the function to use a future >>> def do_work(x, y, fut): fut.set_result(worker(x,y)) >>> fut = Future() >>> t = threading.Thread(target=do_work, args=(2, 3, fut)) >>> t.start() About to work >>> result = fut.result() Done >>> result 5 >>> ``` You'll see this kind of pattern a lot of if working with thread pools, processes, and other constructs. For example: ```python >>> from concurrent.futures import ThreadPoolExecutor >>> pool = ThreadPoolExecutor() >>> fut = pool.submit(worker, 2, 3) About to work >>> fut >>> fut.result() Done 5 >>> ``` \[ [Solution](soln5_2.md) | [Index](index.md) | [Exercise 5.1](ex5_1.md) | [Exercise 5.3](ex5_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)