A class is a “blueprint” for a set of related variables and functions.
These functions and variables “belong” together, and can be “bundled” into a self-contained object.
NOTE: Please run the cells in order. If you do not, you will get different results and the examples will not make sense to you.
class terms¶
property: a variable that is within a classmethod: a function that is within a class
Properties and methods act very much like variables and functions. There are a few differences that we will discuss.
Chapter 4.1.1 - Basic class example¶
class syntax¶
Much like defining a function, you define a class by using the class keyword, followed by a space, and then the class name. Just like with a function, you place a colon after the name, create a new line, and then start defining properties and methods that are indented to tell Python that they belong to the class definition.
In this example, we define a class named Observation that contains four properties that are commonly associated with a weather observation.
class Observation:
high_temperature = 85
low_temperature = 65
dewpoint = 64
pressure = 29.4This would be similar to creating a dict of these values
obs = {'high_temperature': 85,
'low_temperature': 65,
'dewpoint': 64,
'pressure': 29.4}
obs{'high_temperature': 85,
'low_temperature': 65,
'dewpoint': 64,
'pressure': 29.4}However, classes are generally “all-in-one” ways of working with the data (properties and often include methods that make it easier to work and access the data.
A very simple method example is printing out the high_temperature. We will discuss methods later, but I wanted to include this example to show how a class is different from the data types and approaches we have used thus far.
class Observation:
high_temperature = 85
low_temperature = 65
dewpoint = 64
pressure = 29.4
def high(self):
print(self.high_temperature)Chapter 4.1.2 - Accessing class properties¶
Like with other data types, we often want to access the data stored within a class. How do we do this?
We can try a few things that we know how to do.
Indexing
Observation[0]---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[5], line 1
----> 1 Observation[0]
TypeError: type 'Observation' is not subscriptableDictionary key
Observation['high_temperature']---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[6], line 1
----> 1 Observation['high_temperature']
TypeError: type 'Observation' is not subscriptableDARN, neither of these worked.
Chatper 4.1.3 - class instances¶
The correct way to access a property within a class
The first step is to create an instance of the class.
instance: a unique version of the class that maintains itsstatebased on the way you initialize the class.
Otherwise known as “instantiating a class”, this process uses the blueprint provided by the class definition to constrain what variables/values need to be defined by the programmer. Each instance can be unique, and reflects the variable nature of the data type and problem you are trying to address. For example, the morning weather observer students know that they need to fill-in-the-blanks on the weather observation form for very specific values. This form can be represented as an Observation blueprint. In other words, all observations require these values, but not all observations have the same values.
Here is how you create an instance of Observation named obs and how to access the property high_temperature associated with the instance named obs:
obs = Observation()
print(obs.high_temperature)85
Dot Notation
You can access the unique values associated with each instance of a class by using “dot notation”.
Dot notation is similar to how we used indexing in composite data types. Except, instead of a [], we simply add a . after the instance.
We can access any property within an instance directly using this approach (how would you do this with f-strings?):
print("The high temperature is", obs.high_temperature)
print("The low temperature is", obs.low_temperature)
print("The dewpoint is", obs.dewpoint)
print("The pressure is", obs.pressure)The high temperature is 85
The low temperature is 65
The dewpoint is 64
The pressure is 29.4
Each instance could have a different value, depending on how you define the class. If you modify one instance, it has no effect on a different instance.
The common way to think about this is as follows:
A class is a species. Each member of an animal species shares many similar traits. Humans, for example, have a name, an age, and other attributes that distinguish individuals from other humans.
Lets make a “Human” class:
class Human:
def __init__(self, age, name):
self.Age = age
self.Name = name
The class provides a template that all humans share (a name and an age), but do not define Age and Name until you create an instance.
Unlike the example above, we want to create a class that has unique values associated with the instance of Human (i.e., an individual) we create. We do this by defining a method within the class definition named __init__.
“init” is short for initialize. In other words, the purpose of this method is to set properties within a class instance. This method has three arguments (but it could be as few as one and as many as you’d like):
self- This is how you access properties and methods within the class. In other words, if you want to set or access a property from another part of the class, you must useselfto access that information with dot notation. This always has to be an argument in this method.age- this is the value we would like to assign to a property named ‘Age’ and assigns an age to an instance of Humanname - same as ‘age’, but to the property named ‘Name’.
The self argument does not need to be set by the programmer when creating an instance. This is implicitly defined when you create an instance.
If we want to create a “unique” human, we just need to provide specific values for Age and Name. Creating two instances of “Human” can be done as follows:
stacey = Human(name='Stacey', age=33)
bill = Human(name='Bill', age=30)We assign the variable named stacey to an instance of Human that has an Age of 33 and a Name of ‘Stacey’. Similarly, the variable bill is an instance of Human that has an Age of 30 and a Name of ‘Bill’
Here is the code in action:
class Human:
def __init__(self, age, name):
self.Age = age
self.Name = name
stacey = Human(name='Stacey', age=33)
bill = Human(name='Bill', age=30)
print(type(stacey), type(bill))<class '__main__.Human'> <class '__main__.Human'>
The variables stacey and bill will have the same type (Human), but each variable will be a unique instance of Human with unique values defined above.
You can access the unique values associated with each instance of a human by using “dot notation”.
Dot notation is similar to how we used indexing in composite data types. Except, instead of a [], we simply add a . after the instance:
print(stacey.Name)Stacey
Chapter 4.1.4 - class methods¶
We already defined a method named __init__ in the class definition for Human.
Say that we wanted to automatically print the properties (Age and Name) of an instance. We can add a function named __str__. This defines what happens when a class is “printed” using the print statement.
For this example, we will start with an f-string template using what we already know how to do:
name = 'Stacey'
age = 33
print(f"Hello, my name is {name} and I am {age} years old.")Hello, my name is Stacey and I am 33 years old.
We can use the __str__ method to insert this template into our class definition.
What happens if we just paste the above code into the method?
class Human:
def __init__(self, age, name):
self.Age = age
self.Name = name
def __str__(self):
print(f"Hello, my name is {name} and I am {age} years old.")
stacey = Human(name='Stacey', age=33)
print(stacey)Hello, my name is Stacey and I am 33 years old.
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[10], line 12
8 print(f"Hello, my name is {name} and I am {age} years old.")
10 stacey = Human(name='Stacey', age=33)
---> 12 print(stacey)
TypeError: __str__ returned non-string (type NoneType)Python is telling us that print was expecting a string, but it got something else. In this case, you tried to print within the method, which “returns” None, since all it needs to do is print and not give anything back after it is completed.
The key here is we need to return a string! So, remove the print statement and see what happens:
class Human:
def __init__(self, age, name):
self.Age = age
self.Name = name
def __str__(self):
return f"Hello, my name is {name} and I am {age} years old."
stacey = Human(name='Stacey', age=33)
print(stacey)Hello, my name is Stacey and I am 33 years old.
Great!! It worked!!! Now we should do one for “Bill”!
bill = Human(name='Bill', age=30)
print(bill)Hello, my name is Stacey and I am 33 years old.
Wait, WHAT?! Why did Bill end up with Stacey’s name and age?
It is because name and age are not defined in the class scope, so it defaults to the outer scope, which still has information from running our simple print example a few cells above this. You could confirm this by restarting and rerunning the notebook starting with the cell that defines the class named Human.
If we look at the class definition, we see that the variables within the class scope are Age and Name (upper-case).
So, just make that simple fix and it works, right?
class Human:
def __init__(self, age, name):
self.Age = age
self.Name = name
def __str__(self):
return f"Hello, my name is {Name} and I am {Age} years old."
stacey = Human(name='Stacey', age=33)
print(stacey)---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[13], line 12
8 return f"Hello, my name is {Name} and I am {Age} years old."
10 stacey = Human(name='Stacey', age=33)
---> 12 print(stacey)
Cell In[13], line 8, in Human.__str__(self)
7 def __str__(self):
----> 8 return f"Hello, my name is {Name} and I am {Age} years old."
NameError: name 'Name' is not definedThe problem is Python is expecting you to access ‘Age’ and ‘Name’ from a specific instance of Human.
This is why the self argument is so important. It allows other methods to access these properties:
class Human:
def __init__(self, age, name):
self.Age = age
self.Name = name
def __str__(self):
return f"Hello, my name is {self.Name} and I am {self.Age} years old."
stacey = Human(name='Stacey', age=33)
print(stacey)Hello, my name is Stacey and I am 33 years old.
and now Bill will get his correct name and age!
bill = Human(name='Bill', age=30)
print(bill)Hello, my name is Bill and I am 30 years old.
Chapter 4.1.5 - Practice¶
Define a class named Observation that has 4 properties inside of it:
high_temperature
low_temperature
dewpoint
pressure
You can set these values to any number. Create an instance and print out one of the values associated with the instance using dot notation.
Modify the definition of Observation so that it has an
__init__method that allows you to set values for each of the properties mentioned in #1
Create an instance with a high_temperature of 50, a low_temperature of 40, a dewpoint of 39, and a pressure of 1002. Print out one of the values associated with the instance using dot notation.
Modify the definition of Observation by adding a
__str__method that allows you to print the following message when Python tries to print aninstance.
“The high temperature is 50 F, the low temperature is 40 F, the dewpoint is 39 F, and the pressure is 1002 mb.”
Then, demonstrate this works by using the print statement to print an Observation instance. You can only use the following print statement to print your message, where obs is an instance of Observation.
print(obs)