\[ [Index](index.md) | [Exercise 3.8](ex3_8.md) | [Exercise 4.2](ex4_2.md) \]

# Exercise 4.1

*Objectives:*

- Learn more about how objects are represented.
- Learn how attribute assignment and lookup works.
- Better understand the role of a class definition

*Files Created:* None

*Files Modified:* None

Start this exercise, by going back to a simple version of the `Stock` class you created. 
At the interactive prompt, define a 
new class called `SimpleStock` that looks like this:

```python
>>> class SimpleStock:
        def __init__(self, name, shares, price):
            self.name = name
            self.shares = shares
            self.price = price
        def cost(self):
            return self.shares * self.price

>>>
```

Once you have defined this class, create a few instances.

```python
>>> goog = SimpleStock('GOOG',100,490.10)
>>> ibm  = SimpleStock('IBM',50, 91.23)
>>>
```

## (a) Representation of Instances

At the interactive shell, inspect the underlying dictionaries of the two instances you created:

```python
>>> goog.__dict__
... look at the output ...
>>> ibm.__dict__
... look at the output ...
>>>
```

## (b) Modification of Instance Data

Try setting a new attribute on one of the above instances:

```python
>>> goog.date = "6/11/2007"
>>> goog.__dict__
... look at output ...
>>> ibm.__dict__
... look at output ...
>>>
```

In the above output, you'll notice that the `goog` instance has
a attribute `date` whereas the `ibm` instance does not.
It is important to note that Python really doesn't place any
restrictions on attributes.  For example, the attributes of an
instance are not limited to those set up in the `__init__()`
method.

Instead of setting an attribute, try placing a new value directly into
the `__dict__` object:

```python
>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>>
```

Here, you really notice the fact that an instance is a layer on top of a dictionary.


## (c) The role of classes

The definitions that make up a class definition are shared by all
instances of that class.  Notice, that all instances have a link back
to their associated class:

```python
>>> goog.__class__
... look at output ...
>>> ibm.__class__
... look at output ...
>>> 
```

Try calling a method on the instances:

```python
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5
>>>
```

Notice that the name 'cost' is not defined in either `goog.__dict__` or `ibm.__dict__`.   Instead, it is being supplied by the
class dictionary.  Try this:

```python
>>> SimpleStock.__dict__['cost']
... look at output ...
>>>
```

Try calling the `cost()` method directly through the dictionary:

```python
>>> SimpleStock.__dict__['cost'](goog)
49010.00
>>> SimpleStock.__dict__['cost'](ibm)
4561.5
>>>
```

Notice how you are calling the function defined in the class definition and how the `self` argument
gets the instance.

If you add a new value to the class, it becomes a class variable that's visible to all instances.  Try it:

```python
>>> SimpleStock.spam = 42
>>> ibm.spam
42
>>> goog.spam
42
>>>
```

Observe that `spam` is not part of the instance dictionary.

```python
>>> ibm.__dict__
... look at the output ...
>>>
```

Instead, it's part of the class dictionary:

```python
>>> SimpleStock.__dict__['spam']
42
>>>
```

Essentially this is all a class really is--it's a collection of values shared by instances.


\[ [Solution](soln4_1.md) | [Index](index.md) | [Exercise 3.8](ex3_8.md) | [Exercise 4.2](ex4_2.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/)
