\[ [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 > >>> s.cost() # Looks up and calls the method 49010.0 >>> # Same operations using getattr() >>> getattr(s, 'cost') > >>> 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__ >>> 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/)