Definitional Aspects of Functions

PythonPythonBeginner
Practice Now

This tutorial is from open-source community. Access the source code

Introduction

Objectives:

  • Explore a few definitional aspects of functions/methods
  • Making functions more flexible
  • Type hints

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("`Python`")) -.-> python/BasicConceptsGroup(["`Basic Concepts`"]) python(("`Python`")) -.-> python/FileHandlingGroup(["`File Handling`"]) python(("`Python`")) -.-> python/ControlFlowGroup(["`Control Flow`"]) python(("`Python`")) -.-> python/DataStructuresGroup(["`Data Structures`"]) python(("`Python`")) -.-> python/FunctionsGroup(["`Functions`"]) python(("`Python`")) -.-> python/ModulesandPackagesGroup(["`Modules and Packages`"]) python(("`Python`")) -.-> python/AdvancedTopicsGroup(["`Advanced Topics`"]) python(("`Python`")) -.-> python/PythonStandardLibraryGroup(["`Python Standard Library`"]) python/BasicConceptsGroup -.-> python/comments("`Comments`") python/FileHandlingGroup -.-> python/with_statement("`Using with Statement`") python/BasicConceptsGroup -.-> python/variables_data_types("`Variables and Data Types`") python/BasicConceptsGroup -.-> python/numeric_types("`Numeric Types`") python/BasicConceptsGroup -.-> python/strings("`Strings`") python/ControlFlowGroup -.-> python/conditional_statements("`Conditional Statements`") python/ControlFlowGroup -.-> python/for_loops("`For Loops`") python/DataStructuresGroup -.-> python/lists("`Lists`") python/DataStructuresGroup -.-> python/tuples("`Tuples`") python/DataStructuresGroup -.-> python/dictionaries("`Dictionaries`") python/FunctionsGroup -.-> python/function_definition("`Function Definition`") python/FunctionsGroup -.-> python/default_arguments("`Default Arguments`") python/ModulesandPackagesGroup -.-> python/importing_modules("`Importing Modules`") python/ModulesandPackagesGroup -.-> python/using_packages("`Using Packages`") python/ModulesandPackagesGroup -.-> python/standard_libraries("`Common Standard Libraries`") python/FileHandlingGroup -.-> python/file_opening_closing("`Opening and Closing Files`") python/FileHandlingGroup -.-> python/file_operations("`File Operations`") python/AdvancedTopicsGroup -.-> python/iterators("`Iterators`") python/PythonStandardLibraryGroup -.-> python/data_collections("`Data Collections`") python/BasicConceptsGroup -.-> python/python_shell("`Python Shell`") python/FunctionsGroup -.-> python/build_in_functions("`Build-in Functions`") subgraph Lab Skills python/comments -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/with_statement -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/variables_data_types -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/numeric_types -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/strings -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/conditional_statements -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/for_loops -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/lists -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/tuples -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/dictionaries -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/function_definition -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/default_arguments -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/importing_modules -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/using_packages -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/standard_libraries -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/file_opening_closing -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/file_operations -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/iterators -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/data_collections -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/python_shell -.-> lab-132503{{"`Definitional Aspects of Functions`"}} python/build_in_functions -.-> lab-132503{{"`Definitional Aspects of Functions`"}} end

Preparation

In Exercise 2.6 you wrote a reader.py module that had a function for reading a CSV into a list of dictionaries. For example:

>>> import reader
>>> port = reader.read_csv_as_dicts('portfolio.csv', [str,int,float])
>>>

We later expanded to that code to work with instances in Exercise 3.3:

>>> import reader
>>> from stock import Stock
>>> port = reader.read_csv_as_instances('portfolio.csv', Stock)
>>>

Eventually the code was refactored into a collection of classes involving inheritance in Exercise 3.7. However, the code has become rather complex and convoluted.

Back to Basics

Start by reverting the changes related to class definitions. Rewrite the reader.py file so that it contains the two basic functions that you had before you messed it up with classes:

## reader.py

import csv

def read_csv_as_dicts(filename, types):
    '''
    Read CSV data into a list of dictionaries with optional type conversion
    '''
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = { name: func(val)
                       for name, func, val in zip(headers, types, row) }
            records.append(record)
    return records

def read_csv_as_instances(filename, cls):
    '''
    Read CSV data into a list of instances
    '''
    records = []
    with open(filename) as file:
        rows = csv.reader(file)
        headers = next(rows)
        for row in rows:
            record = cls.from_row(row)
            records.append(record)
    return records

Make sure the code still works as it did before:

>>> import reader
>>> port = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
>>> port
[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1},
 {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23},
 {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1},
 {'name': 'IBM', 'shares': 100, 'price': 70.44}]
>>> import stock
>>> port = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> port
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44),
 Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1),
 Stock('IBM', 100, 70.44)]
>>>

Thinking about Flexibility

Right now, the two functions in reader.py are hard-wired to work with filenames that are passed directly to open(). Refactor the code so that it works with any iterable object that produces lines. To do this, create two new functions csv_as_dicts(lines, types) and csv_as_instances(lines, cls) that convert any iterable sequence of lines. For example:

>>> file = open('portfolio.csv')
>>> port = reader.csv_as_dicts(file, [str, int, float])
>>> port
[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1},
 {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23},
 {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1},
 {'name': 'IBM', 'shares': 100, 'price': 70.44}]
>>>

The whole point of doing this is to make it possible to work with different kinds of input sources. For example:

>>> import gzip
>>> import stock
>>> file = gzip.open('portfolio.csv.gz')
>>> port = reader.csv_as_instances(file, stock.Stock)
>>> port
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44),
 Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1),
 Stock('IBM', 100, 70.44)]
>>>

To maintain backwards compatibility with older code, write functions read_csv_as_dicts() and read_csv_as_instances() that take a filename as before. These functions should call open() on the supplied filename and use the new csv_as_dicts() or csv_as_instances() functions on the resulting file.

Design Challenge: CSV Headers

The code assumes that the first line of CSV data always contains column headers. However, this isn't always the case. For example, the file portfolio_noheader.csv contains data, but no column headers.

How would you refactor the code to accommodate missing column headers, having them supplied manually by the caller instead?

API Challenge: Type hints

Functions can have optional type-hints attached to arguments and return values. For example:

def add(x:int, y:int) -> int:
    return x + y

The typing module has additional classes for expressing more complex kinds of types including containers. For example:

from typing import List

def sum_squares(nums: List[int]) -> int:
    total = 0
    for n in nums:
        total += n*n
    return total

Your challenge: Modify the code in reader.py so that all functions have type hints. Try to make the type-hints as accurate as possible. To do this, you may need to consult the documentation for the typing module.

Summary

Congratulations! You have completed the Definitional Aspects of Functions lab. You can practice more labs in LabEx to improve your skills.

Other Python Tutorials you may like