\[ [Index](index.md) | [Exercise 3.1](ex3_1.md) | [Exercise 3.3](ex3_3.md) \]

# Exercise 3.2

*Objectives:*

- Learn about attribute access
- Learn how use getattr(), setattr(), and related functions.
- Experiment with bound methods.


*Files Created:* `tableformat.py`

## (a) The Three Operations

The entire Python object system consists of just three core operations: getting, setting, and deleting
of attributes.  Normally, these are accessed via the dot (.) like this:

```python
>>> s = Stock('GOOG', 100, 490.1)
>>> s.name    #  get
'GOOG'
>>> s.shares = 50    # set
>>> del s.shares     # delete
>>>
```

The three operations are also available as functions.  For example:

```python
>>> getattr(s, 'name')            # Same as s.name
'GOOG'
>>> setattr(s, 'shares', 50)      # Same as s.shares = 50
>>> delattr(s, 'shares')          # Same as del s.shares
>>>
```

An additional function `hasattr()` can be used to probe an object for the existence of an attribute:

```python
>>> hasattr(s, 'name')
True
>>> hasattr(s, 'blah')
False
>>>
```

## (b) Using getattr()

The `getattr()` function is extremely useful for
writing code that processes objects in an extremely generic way.  To
illustrate, consider this example which prints out a set of
user-defined attributes:

```python
>>> s= Stock('GOOG', 100, 490.1)
>>> fields = ['name','shares','price']
>>> for name in fields:
           print(name, getattr(s, name))

name GOOG
shares 100
price 490.1
>>>
```

## (c) Table Output

In [Exercise 3.1](ex3_1.md), you wrote a function `print_portfolio()`
that made a nicely formatted table.   That function was custom tailored
to a list of `Stock` objects.  However, it can be completely generalized
to work with any list of objects using the technique in part (b).

Create a new module called `tableformat.py`.  In that program,
write a function `print_table()` that takes a sequence (list) of objects,
a list of attribute names, and prints a nicely formatted table. For example:

```python
>>> import stock
>>> import tableformat
>>> portfolio = stock.read_portfolio('Data/portfolio.csv')
>>> tableformat.print_table(portfolio, ['name','shares','price'])
      name     shares      price
---------- ---------- ---------- 
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

>>> tableformat.print_table(portfolio,['shares','name'])
    shares       name
---------- ---------- 
       100         AA
        50        IBM
       150        CAT
       200       MSFT
        95         GE
        50       MSFT
       100        IBM
>>> 
```

For simplicity, just have the `print_table()` function print each field in
a 10-character wide column.

## (d) Bound Methods

It may be surprising, but method calls are layered onto the machinery used
for simple attributes.  Essentially, a method is an attribute that 
executes when you add the required parentheses () to call it like a function.  For
example:

```python
>>> s = Stock('GOOG',100,490.10)
>>> s.cost           # Looks up the method
<bound method Stock.cost of <__main__.Stock object at 0x409530>>
>>> s.cost()         # Looks up and calls the method
49010.0

>>> # Same operations using getattr()
>>> getattr(s, 'cost')
<bound method Stock.cost of <__main__.Stock object at 0x409530>>
>>> getattr(s, 'cost')()
49010.0
>>> 
```

A bound method is attached to the object where it came from.   If that
object is modified, the method will see the modifications.  You can
view the original object by inspecting the `__self__` attribute
of the method.

```python
>>> c = s.cost
>>> c()
49010.0
>>> s.shares = 75
>>> c()
36757.5
>>> c.__self__
<__main__.Stock object at 0x409530>
>>> c.__func__
<function cost at 0x37cc30>
>>> c.__func__(c.__self__)      # This is what happens behind the scenes of calling c()
36757.5
>>>
```

Try it with the `sell()` method just to make sure you
understand the mechanics:

```python
>>> f = s.sell
>>> f.__func__(f.__self__, 25)     # Same as s.sell(25)
>>> s.shares
50
>>>
```

\[ [Solution](soln3_2.md) | [Index](index.md) | [Exercise 3.1](ex3_1.md) | [Exercise 3.3](ex3_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/)
