Fall 2020
-->

# Defining objects

(These notes contain more than you need right away. We'll be working through his material over several assignments.)

We've already seen the notion of an "object" in programming, which is a way to combine some data values with some functions that manipulate them.

Object oriented programming is one important ways that code is organized.

If functions are analogous to verbs, then objects are analogous to nouns - "things" that can have properties (i.e. data values) as well as their own methods (i.e. functions).

One programming language known for its obsession with objects is Java - you cannot do anything in Java without tripping over them. Not everyone is convinced that objects are always the right approach - see for example this essay : Execution in the Kingdom of Nouns.

But objects can be a useful programming paradigm. For example we've used graphics objects.

center = Point(3, 4)            # Create an object
outline = Circle(center, 12)    # ... and another.
outline.setFill("red")          # Invoke a "method"


Now we are going to define our own objects. In python objects are defined with "class" definitions. Here's the simplest possible one.

class Person:
pass         # A class definition needs an indented block
# ... or at least an empty block, which is all "pass" does.


And now we can make one of these.

>>> jim = Person()                           # Create one.
>>> jim                                      # Show it.
<__main__.Person object at 0x7fb93803cd90>
>>> dir(jim)                                 # Let's look inside ...
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']


Remember that dir() looks inside something; we've used it with for example import math; dir(math) or from graphics import *; dir(Circle).

So even this currently "empty" object has some built-in stuff. All of those names with two underbars before and after are "private" methods or properties, implementing various aspects of the object's behavior.

We can create a new property within this object jim :

>>> jim.name = "Jim Mahoney"   # Create a ".name" property within an object.
>>> print(jim.name)            # Access that .name property.
'Jim Mahoney'


This "name" property is just like a variable - its just a variable contained within the "box" that is the object.

But instead of this modify-after-creation approach, usually we design the object class so that its properties are set up when it is created, with a special __init__ method which is called whenever an object is created.

## a Person example

Here's a more complete example, designed to make Java programmers think they know what's going on. There are several new syntax components here that need some explanation.

Run this in pythontutor.com to see what's going on.

class Person:
""" a Person has a name. """
def __init__(self, name):
self._name = name
def getName(self):
return self._name
def setName(self, name):
self._name = name

jim = Person('Jim')
print("Jim's name is ", jim.getName())
jim.setName('James')                          # invoke the .setName('string') method to alter the name
print("Jim's name is now ", jim.getName())    # invoke the .getName() method to find the name


Let's talk about the new syntax, piece by piece.

I'm going to go into some of the more subtle public/private concepts here, which you may want to skip on a first read.

First, the def parts are method definitions, almost the same as the function definitions we've already seen except that they are attached to a specific object.

Second, the variable self refers to the specific object that is running this method - for example jim in jim.setName.

Third, and a peculiarity of python, the first argument in the definition of a method is always "self".

Fourth, when invoking an object method, that "self" argument is omitted. (This is fairly strange, and a peculiarity of python.)

So even though in the code above we have def setName(self, name), with two arguments, later that is run as jim.setName('James'), with only one argument.

It turns out that in python , jim.setName('James') is actually a shorthand for Person.setName(jim, 'James')... and that's why selfwithin the method code is thatjim object.

By the way, the Java programming language is brimming with "getters and setters", getThis and setThat all over the place. The Java programming language strictly enforces the distinction between things that can be seen within a method (called "private") and things that can be seen by code that uses the objects (called "public").

So in Java, jim.name could only be seen - or worse yet, modified - from within a method of the Person class. And that's why Java programmers need all those setThing and getThing methods, to be the public access points for changing or finding the object's data.

The python programming language is less strict, and does not enforce these private vs public boundaries. Instead it "suggests" which data is private with the underbars, which is why in the example above I have put an underbar in self._name = name .

But in python it would be perfectly OK to avoid the getter/setter stuff and just write instead

class Hippy:
""" a Hippy has a name
... and a more casual interface
... just use .name to get or set """
def __init__(self, name):
self.name = name

print("Daisy's name is ", daisy.name)       # access daisy's name directly
daisy.name = 'Daisy Moonbeam'               # modify .name directly
print("Daisy's name is now ", daisy.name)


## a dice example

Here's another example. Put this too into pythontutor.com and see what happens in memory while it runs.

import random

class Dice:
def __init__(self, sides=6):
self.sides = sides
self.value = 1
def roll(self):
self.value = random.randint(1, self.sides)

def main():
fourD6 = []          # This will hold four six-sided dice.
for i in range(4):
d6 = Dice()        # Create a 6-sided die.
d6.roll()          # Randomize its .value .
fourD6.append(d6)
print("You roll four six-side dice and get ")
for die in fourD6:
print(die.value)
main()


## the __str__ method

Python has many possible "special" methods for objects that allow them to do the sorts of things that other python built-in types do.

One worth mentioning is the __str__ method, which determines how an object is converted into a string, for example when it is printed.

Here's an example.

class Hippy:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Hippy('{self.name}')"

daisy = Hippy('Daisy')
print(daisy)
# prints "Hippy('Daisy')" instead of not <__main__.Hippy object ... >


## other special methods

This material can be safely ignored unless you are already used to objects.

The notion is that you can create your own objects which have behaviors like Python's built-in data types, by defining methods with particular names. This means that a class that you define can behave like a number, or a string, or a list, or even a function. You can define for example a MyList class and then use python's this_list[i] notation with it. Or you can define your own MyFunction class, create one, and invoke it with this_function(x,y,z). Or your own numbers (like complex numbers) with their own notion of what == or + does.

In some languages this is called "overriding" a behavior or operator.

Here's an example ... without any docs or sample usage. Can you put in what the docs should be? Are there any things that this new object can't do ... but perhaps should?

class Triple:
def __init__(self, one=0, two=0, three=0):
self.one = one
self.two = two
self.three = three
def __len__(self):
return 3
def __repr__(self):
return "Triple(one={}, two={}, three={})".format(
self.one, self.two, self.three)
def __eq__(self, other):
return str(self) == str(other)
def __ne__(self, other):
return not (self == other)
return Triple(one = self.one + other.one,
two = self.two + other.two,
three = self.three + other.three)
`

## Further object topics

### inheritance

A technique often used when programming with objects is to have one object "inherit" behavior from another.

For example, in Zelle's graphics library there is a generic GraphicsObject class which defines the .setFill method , in such a way that all the specific shapes (Circle, Rectangle, ...) can use, by "inheriting" from that generic class.

The purpose of all this is to let different sorts of objects share the same code, while still allowing variations. For example, you might have a Person class, and then have Student and Faculty classes which inherit from it.

See for example the description at prgramiz.com/python-programming.inheritance

### containers

It's common to have a class that acts as a container, often by putting a list within it.

The components within the container may be any sort of data: strings, numbers, or other objects.

This is another way, in addition to inheritance, for one object to be "within" another.

Here's an example :

• Card : one card from 52 card deck
• Hand : a collection of cards
• BlackjackHand : a Hand that also has some Blackjack scoring

Hand is a container class, with individual Cards within it.

BlackjackHand inherits from Hand, with its behaviors and more.

We'll look at this example in more detail, working out how to implement this in python.

### class variables vs instance variables

It's also possible and sometimes useful to attach data and methods to an entire class, rather than to an instance of a class. So for example if you have a Person class, there might be a Person.population property of the entire class giving the total number of Person objects created.

### an example

Here's one more fairly silly example which illustrates many of these ideas including inheritance, class variables, and container classes.

https://cs.bennington.college /courses /fall2020 /introcs /notes /8_objects