Function Argument Passing Conventions

PythonPythonBeginner
Practice Now

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

Introduction

Objectives:

  • Learn more about function argument passing conventions

Files Created: structure.py, stock.py

IMPORTANT NOTE

This exercise is going to start a long road of rewriting the stock.py file in a more sane way. Before doing anything, copy your work in stock.py to a new file orig_stock.py.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("`Python`")) -.-> python/BasicConceptsGroup(["`Basic Concepts`"]) python(("`Python`")) -.-> python/FunctionsGroup(["`Functions`"]) python(("`Python`")) -.-> python/ControlFlowGroup(["`Control Flow`"]) python(("`Python`")) -.-> python/DataStructuresGroup(["`Data Structures`"]) python(("`Python`")) -.-> python/ModulesandPackagesGroup(["`Modules and Packages`"]) python(("`Python`")) -.-> python/ObjectOrientedProgrammingGroup(["`Object-Oriented Programming`"]) python(("`Python`")) -.-> python/ErrorandExceptionHandlingGroup(["`Error and Exception Handling`"]) python/BasicConceptsGroup -.-> python/comments("`Comments`") python/FunctionsGroup -.-> python/keyword_arguments("`Keyword Arguments`") python/ControlFlowGroup -.-> python/conditional_statements("`Conditional Statements`") python/ControlFlowGroup -.-> python/for_loops("`For Loops`") python/DataStructuresGroup -.-> python/tuples("`Tuples`") python/DataStructuresGroup -.-> python/dictionaries("`Dictionaries`") python/FunctionsGroup -.-> python/function_definition("`Function Definition`") python/ModulesandPackagesGroup -.-> python/importing_modules("`Importing Modules`") python/ModulesandPackagesGroup -.-> python/using_packages("`Using Packages`") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("`Classes and Objects`") python/ObjectOrientedProgrammingGroup -.-> python/constructor("`Constructor`") python/ObjectOrientedProgrammingGroup -.-> python/polymorphism("`Polymorphism`") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("`Encapsulation`") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("`Raising Exceptions`") python/BasicConceptsGroup -.-> python/python_shell("`Python Shell`") python/FunctionsGroup -.-> python/build_in_functions("`Build-in Functions`") subgraph Lab Skills python/comments -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/keyword_arguments -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/conditional_statements -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/for_loops -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/tuples -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/dictionaries -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/function_definition -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/importing_modules -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/using_packages -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/classes_objects -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/constructor -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/polymorphism -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/encapsulation -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/raising_exceptions -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/python_shell -.-> lab-132509{{"`Function Argument Passing Conventions`"}} python/build_in_functions -.-> lab-132509{{"`Function Argument Passing Conventions`"}} end

Preparation

We're going to recreate the Stock class from scratch using some new techniques. Make sure you have your unit tests from Exercise 5.4 handy. You'll want those.

If you define a function, you probably already know that it can be called using a mix of positional or keyword arguments. For example:

>>> def foo(x, y, z):
        return x + y + z

>>> foo(1, 2, 3)
6
>>> foo(1, z=3, y=2)
6
>>>

You may also know that you can pass sequences and dictionaries as function arguments using the * and ** syntax. For example:

>>> args = (1, 2, 3)
>>> foo(*args)
6
>>> kwargs = {'y':2, 'z':3 }
>>> foo(1,**kwargs)
6
>>>

In addition to that, you can write functions that accept any number of positional or keyword arguments using the * and ** syntax. For example:

>>> def foo(*args):
        print(args)

>>> foo(1,2)
(1, 2)
>>> foo(1,2,3,4,5)
(1, 2, 3, 4, 5)
>>> foo()
()
>>>
>>> def bar(**kwargs):
        print(kwargs)

>>> bar(x=1,y=2)
{'y': 2, 'x': 1}
>>> bar(x=1,y=2,z=3)
{'y': 2, 'x': 1, 'z': 3}
>>> bar()
{}
>>>

Variable argument functions are sometimes useful as a technique for reducing or simplifying the amount of code you need to type. In this exercise, we'll explore that idea for simple data structures.

Simplified Data Structures

In earlier exercises, you defined a class representing a stock like this:

class Stock:
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price

Focus on the __init__() method---isn't that a lot of code to type each time you want to populate a structure? What if you had to define dozens of such structures in your program?

In a file structure.py, define a base class Structure that allows the user to define simple data structures as follows:

class Stock(Structure):
    _fields = ('name','shares','price')

class Date(Structure):
    _fields = ('year', 'month', 'day')

The Structure class should define an __init__() method that takes any number of arguments and which looks for the presence of a _fields class variable. Have the method populate the instance from the attribute names in _fields and values passed to __init__().

Here is some sample code to test your implementation:

>>> s = Stock('GOOG',100,490.1)
>>> s.name
'GOOG'
>>> s.shares
100
>>> s.price
490.1
>>> s = Stock('AA',50)
Traceback (most recent call last):
...
TypeError: Expected 3 arguments
>>>

Making a Useful Representation

Modify the Structure class so that it produces a nice representation when repr() is used. For example:

>>> s = Stock('GOOG', 100, 490.1)
>>> s
Stock('GOOG',100,490.1)
>>>

Restricting Attribute Names

Give the Structure class a __setattr__() method that restricts the allowed set of attributes to those listed in the _fields variable. However, it should still allow any "private" attribute (e.g., name starting with _ to be set).

For example:

>>> s = Stock('GOOG',100,490.1)
>>> s.shares = 50
>>> s.share = 50
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "structure.py", line 13, in __setattr__
    raise AttributeError('No attribute %s' % name)
AttributeError: No attribute share
>>> s._shares = 100     ## Private attribute. OK
>>>

Starting Over

Create a new file stock.py (or delete all of your previous code). Start over by defining Stock as follows:

## stock.py

from structure import Structure

class Stock(Structure):
    _fields = ('name', 'shares', 'price')

    @property
    def cost(self):
        return self.shares * self.price

    def sell(self, nshares):
        self.shares -= nshares

Once you've done this, run your teststock.py unit tests. You should get a lot of failures, but at least a handful of the tests should pass.

Summary

Congratulations! You have completed the Function Argument Passing Conventions lab. You can practice more labs in LabEx to improve your skills.

Other Python Tutorials you may like