0% found this document useful (0 votes)
9 views

Lecture 2 - Basic Python Programming Part 2

The document discusses creating classes in Python. It describes class definitions, instance variables, instance methods, and class variables. It provides an example Counter class that tracks the number of instances and allows incrementing and retrieving the stored value.

Uploaded by

jacobsabraw
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views

Lecture 2 - Basic Python Programming Part 2

The document discusses creating classes in Python. It describes class definitions, instance variables, instance methods, and class variables. It provides an example Counter class that tracks the number of instances and allows incrementing and retrieving the stored value.

Uploaded by

jacobsabraw
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 59

Lecture 2: Basic Python Programming Part 2

CIS-275
a collection of data (variables) and
Creating New Classes methods (functions) that act on
those data.
● When we define a class, we create a new data type.
○ A class can be thought of as a blueprint for a type of object.
○ We can use this blueprint to create multiple objects (or instances of the class).
● A class definition describes the data an instance of the class will hold as well as the
methods that can be performed on it.

Class is like a Build-A-Bear workshop. It gives you the blueprint to make unique Teddy Bears (that aren't
duplicates of each other but bear many similar attributes) or instances.

eg. Class is to Build-A-Bear as objects/instances are to stuffed animals.

Chapter 1 Basic Python Programming 2


Creating New Classes
A class definition is made up of the following parts:

class <class name>(<parent class name>):


<class variable assignments>
<instance method definitions>

Methods, in general are functions associated with a class.


● An instance method is a function that can be called on instances of the class.
Methods usually retrieve or modify instance variables in some way.
● A class variable is shared by all instances of its class (more on this soon)
○ Note: The parent class name is optional. For now, we will leave this out of our
class definitions, but later on we will start using it.

Chapter 1 Basic Python Programming 3


Creating New Classes
● A few key terms relating to classes:
○ Instance variable: Storage held by an individual object. Each object of the same
class might have different values in their instance variables.
○ Instance method: A method that is run on an object of a class. It can perform
operations on that object’s instance variables.
○ Class variable: Storage that is common to all instances of a class. i.e. if we
change a class variable for one object it is actually changed for all objects of the
same class.
Instance Variable: two stuffed animals (the instances) made at Build-A-Bear (the class) with clothes and hats,
but the stuffed animals are different (different instance variable).
Instance methond: the act of putting on clothes (instance methond) on a stuffed animal (instance variable) at
Build-A-Bear (class)
Class variable: All stuffed animals (instances) at Build-A-Bear (class) have stuffing in them (class variable)

Chapter 1 Basic Python Programming 4


Creating New Classes
● Today we will demonstrate the creation of a simple class named Counter.
● In this course, we will usually define each class in its own .py file.
● In PyCharm, you can create a new .py file by right-clicking the project name (Classes
in this example) and selecting new → Python File.
● Give the file the same name as the class you are defining.
○ In this example, we are creating a file named Counter.py.

Chapter 1 Basic Python Programming 5


Creating New Classes
● After creating a class on a different file, we need to import it into Main (or whatever
other file you want to use that class on).
● For now, we will give a file the same name as the class defined on it:

from Counter import Counter

● This statement tells Python that from the Counter module (i.e. Counter.py) we want
to import the Counter class.
● We can now create Counter objects on the file the class is imported to.

Chapter 1 Basic Python Programming 6


Creating New Classes
● A Counter object will store an integer value, initialized to 0.
● The Counter class will be used for counting how many times a particular event
occurred, so we will restrict the client to incrementing its value by 1 at a time.
● The class will:
○ Have methods which allow the client to increment by 1, decrement by 1,
retrieve, or reset the value to 0.
○ Override a some of the built-in, default methods and overload a few operators,
allowing us to compare counter objects and add them together, etc.
○ Have a class variable to track how many Counter objects have been created.

Chapter 1 Basic Python Programming 7


Creating a Counter Class
class Counter:
# Class variable instances variable outside of constructor, therefore class variable
instances = 0

# Constructor
def __init__ (self):
""" Creates a new Counter object and initializes its data """
Counter.instances += 1 because instances is a class variable, we have to call the class (in this
self._value = 0 case Counter) it's attached to in order to use it, even if the code is inside
the class it's referring to .
● The __init__ method is a class’s constructor.
● When we define this method, we override the default constructor provided by Python.
● When a Counter object is created, its __init__ method is immediately called.
● To create an object, the client writes the class name followed by parentheses:
Counter() # Calls the __init__ method

Chapter 1 Basic Python Programming 8


Creating a Counter Class
class Counter:
# Class variable
instances = 0

# Constructor
def __init__ (self):
""" Creates a new Counter object and initializes its data """
Counter.instances += 1
self._value = 0

● Python implicitly passes self to every class method. This parameter is a reference to
the calling object (the object the method was called on)
○ Note: in __init__, self references the new object Python created.
● In this method, we attach the attribute _value to the new object and initialize it to 0.
○ By convention, a leading underscore signifies that an attribute is private: code
outside of the class should not access it directly.

Chapter 1 Basic Python Programming 9


Creating a Counter Class
class Counter:
# Class variable
instances = 0

# Constructor
def __init__ (self):
""" Creates a new Counter object and initializes its data """
Counter.instances += 1
self._value = 0

● instances is a class variable. It is not attached to an individual Counter object but


rather associated with the Counter class as a whole.
● _value is an instance variable. Each Counter object will have its own _value
attribute and each may contain different numbers.

Chapter 1 Basic Python Programming 10


Creating a Counter Class
from Counter import Counter

def main():

c1 = Counter()
c2 = Counter()
print(c1.instances) # Prints 2

main()

● This code demonstrates how a class variable works.


● Each time the Counter constructor is called, Counter.instances is updated.
● We can access class variables from an object of that class or from the class itself.
● All three of the following lines work and all print the same value:
print(c1.instances) # Prints 2
print(c2.instances) # Prints 2
print(Counter.instances) # Prints 2

Chapter 1 Basic Python Programming 11


Property Methods
class Counter:

# Constructor and class variables go here


@property
def value(self):
return self._value method name should match attribute you're trying to retrieve

● self._value is private, so client code should not access it directly. also called a getter
● We can create a property method to allow the client to retrieve a private attribute.
● When the client calls a property method, they can leave off the parentheses. It appears
as if they are directly accessing an attribute:

my_counter.value # calls the property method 'value'. self is passed implicitly

Chapter 1 Basic Python Programming 12


Property Methods
class Counter:

# Constructor and class variables go here


@property method name should match attribute you're trying to retrieve
def value(self):
return self._value to call it would be <variablename>.value

@value.setter val gets updated


def value(self, val): to call it would be <variablename>.value = <number> to the new <number>
self._value = val

● A property setter allows the client to set the value of a private attribute.
● The decorator before the method header must include the name of the related property
and the method name must be the same.

my_counter = Counter()
my_counter.value = 5 # Calls the 'value' property setter, passes 5 for 'val'.
print(my_counter.value) # prints 5

Chapter 1 Basic Python Programming 13


Creating a Counter Class
● Suppose we have this code on main.py:

from Counter import Counter

def main():

Counter()
# More code goes here!

main()

● Is there anything wrong here?

Chapter 1 Basic Python Programming 14


Creating a Counter Class
● Suppose we have this code on main.py:

from Counter import Counter

def main():

Counter()
# More code goes here!

main()

● Is there anything wrong here? Yes!


● We create a Counter object but do not assign it to a variable.
○ This object is immediately garbage collected.

Chapter 1 Basic Python Programming 15


Creating a Counter Class
from Counter import Counter

def main():

my_counter = Counter()
print(my_counter.value)
# More code goes here!

main()

● This updated code assigns the newly created object to the variable my_counter.
● It will now remain in memory as long as at least one variable points to it.

Chapter 1 Basic Python Programming 16


Creating a Counter Class
class Counter:
# Constructor goes here

def increment( self):


""" adds 1 to the counter """
self._value += 1

● The increment method increases _value by one.


my_counter.increment() # Increment my_counter._value by 1

● Recall that we want to restrict the client from updating by a value other than one.
● For this reason, we want the client to only use this method to update their Counter.

Chapter 1 Basic Python Programming 17


Protecting Private Data
● One of the nice things about providing the client with methods to update internal data
is we can prevent them from accidentally using incorrect values.
● For example, what might the client do wrong when they create a Counter?
c = Counter()

Chapter 1 Basic Python Programming 18


Protecting Private Data
● One of the nice things about providing the client with methods to update internal data
is we can prevent them from accidentally using incorrect values.
● For example, what might the client do wrong when they create a Counter?
c = Counter()

● Suppose they set the value to a negative number:

c._value = -5

● Or they may try to increment the value by more than 1:


c._value += 10

Chapter 1 Basic Python Programming 19


Instance Variables
● Recall that _value is an instance variable.
● Each instance has its own unique instance variables:

my_counter1 = Counter()
my_counter2 = Counter()

my_counter2.increment() # only update my_counter2._value

print(my_counter1.value)
print(my_counter2.value)

_value: 0
● After this code executes, we have two reference my_counter1
variables pointing to two different Counter objects:
● Since we only called increment on my_counter2, _value: 1
its _value is the only one that is higher.
my_counter2
Chapter 1 Basic Python Programming 20
Class Variables
● What will the following code print?
my_counter1 = Counter()
my_counter2 = Counter()
print("They are equal!" if my_counter1 == my_counter2 else "They are not equal")

● Note: The third statement is a syntactic shortcut and is equivalent to the following:

if my_counter1 == my_counter2:
print("They are equal!")
else:
print("They are not equal!")

Chapter 1 Basic Python Programming 21


Class Variables
● What will the following code print? “They are not equal”
my_counter1 = Counter()
my_counter2 = Counter()
print("They are equal!" if my_counter1 == my_counter2 else "They are not equal")

● Although both Counter objects have the same _value of 0, Python currently does
consider them to be equal.
● By default, the == operator only evaluates to true if both its operands reference the
same object in memory.
● However, there are two Counter objects, each in different memory locations:
therefore they're not
my_counter1 _value: 0 equal

my_counter2 _value: 0

Chapter 1 Basic Python Programming 22


used to find if two instances are
The __eq__ method equal to each other

my_counter1 = Counter()
my_counter2 = Counter()
print("They are equal!" if my_counter1 == my_counter2 else "They are not equal")

● Every Python object comes with several built-in methods that are called when they
are operands of certain operators.
○ For example, the __eq__ method is called on the left operand of ==.
○ If a class does not define a body for __eq__, its default version is called.
■ This version returns True if both operands reference the same object.
○ However, we can override this method!

my_counter1 _value: 0

my_counter2 _value: 0

Chapter 1 Basic Python Programming 23


The __eq__ method
● Let’s override the default __eq__ method in Counter:
class Counter:

# Other methods here


def __eq__(self, other):
???

● Note: When the following statement executes, __eq__ is called on my_counter1:


my_counter1 == my_counter2

● Python passes my_counter1 to self and my_counter2 to other


● i.e. the left operand of the == operator is considered the calling object and the right
operand is the argument.

Chapter 1 Basic Python Programming 24


The __eq__ method
● Let’s override the default __eq__ method in Counter:
def __eq__(self, other):
if self is other: # Evaluates to True if self and other reference same object
return True
elif self._value == other.value:
return True
return False

● Is there anything missing from this method?


○ Hint: What will the following code do?
my_counter1 = Counter()
print(my_counter1 == "Hello")

Chapter 1 Basic Python Programming 25


The __eq__ method
● Let’s override the default __eq__ method in Counter:
def __eq__(self, other):
if self is other: # Evaluates to True if self and other reference same object
return True
elif self._value == other.value:
return True
return False

● Is there anything missing from this method?


○ Yes!
my_counter1 = Counter()
print(my_counter1 == "Hello") AttributeError: 'str' object has no attribute 'value'

Chapter 1 Basic Python Programming 26


The __eq__ method
● Let’s override the default __eq__ method in Counter:
class Counter:

# Other methods here


def __eq__(self, other):
if self is other: # Evaluates to True if self and other reference same object
return True
elif self._value == other.value:
return True
return False

● If the right operand of an == expression does not reference a Counter object, it won’t
have a value property method.
● When __eq__ attempts to call it on a str object (for example), an Error is raised!
● We would usually rather simply return False in this scenario.

Chapter 1 Basic Python Programming 27


The __eq__ method
class Counter:
def __eq__(self, other):
1 if self is other: # Evaluates to True if self and other reference same object
return True
2 if type(self) != type(other):
return False
3 elif self._value == other.value:
return True
4 return False

● Note: The type built-in function returns a variable’s type.


● This method now returns:
○ True if both operands point to the exact same object. 1
○ False if other does not reference a Counter object. 2
○ True if both objects contain the same _value. 3
○ False otherwise. 4

Chapter 1 Basic Python Programming 28


The __str__ Method
● The __str__ method should return a string representation of an object’s internal data.
○ By default, it prints out the object’s type and memory location:
● __str__ is called implicitly on an object when it is passed to the print function:

my_counter = Counter()
print(my_counter) # Calls __str__ on my_counter

● If Counter does not override __str__, this code will print something like this:

<Counter.Counter object at 0x0275D1C0>

Chapter 1 Basic Python Programming 29


The __str__ Method
● It is up to us to decide what string best represents an object’s data.
● The following is an example of doing so. Is there anything wrong with it?
def __str__(self):
print("Counter Object with a Value of ", self._value)

Chapter 1 Basic Python Programming 30


The __str__ Method
● It is up to us to decide what string best represents an object’s data.
● The following is an example of doing so. Is there anything wrong with it? Yes!
def __str__(self):
print("Counter Object with a Value of ", self._value)

● Recall that __str__ should return a string, not simply print it out.
○ A TypeError will be raised when the above method executes.
● Instead, we should do something like this:

def __str__(self):
return "Counter Object with a Value of " + str(self._value)

Chapter 1 Basic Python Programming 31


Comparing Counter Objects
● Consider the following code:
my_counter is 0
my_counter = Counter()
my_counter.increment()
my_counter is 1
my_counter2 = Counter() my_counter is 0
print("my_counter is larger" if my_counter > my_counter2 else "my_counter2 is larger")

● What prints?

● What should we want to print?

Chapter 1 Basic Python Programming 32


Comparing Counter Objects
● Consider the following code:

my_counter = Counter()
my_counter.increment()
my_counter2 = Counter()
print("my_counter is larger" if my_counter > my_counter2 else "my_counter2 is larger")

● What prints? Nothing! An Error is raised.


○ Unlike with ==, objects in Python do not have default behavior for the >
operator. This program will crash.

● What should we want to print? “my_counter is larger”

Chapter 1 Basic Python Programming 33


__lt__ is for less than, __gte__ is for

The __gt__ Method greater than or equal to, __lte__ is


for less than or equal to

● When Python encounters the > operator, it calls the __gt__ method on its left
operand, passing the right operand as an argument.
○ If the __gt__ method is not supported by the left operand’s class, an Error
occurs.
● This function can be defined fairly easily:

def __gt__(self, other):


return self._value > other.value

● Note: I didn’t check to see if self and other are the same type.
○ In this case, we probably still want an Error to be raised if the client tries to see if
a Counter object is greater than another type of object.
○ This would likely imply a mistake in the client’s code.

Chapter 1 Basic Python Programming 34


Adding Two Counter Objects
● Consider the following code:
my_counter = Counter()
my_counter.increment( 3)
my_counter2 = Counter()
my_counter2.increment()
my_counter3 = my_counter + my_counter2
print(my_counter3)

● What will occur?

● What should we want to occur?

Chapter 1 Basic Python Programming 35


Adding Two Counter Objects
● Consider the following code:
my_counter = Counter()
my_counter.increment( 3)
my_counter2 = Counter()
my_counter2.increment()
my_counter3 = my_counter + my_counter2
print(my_counter3)

● What will occur? The program will crash: The + operator is not defined for Counter
objects. As with >, no default method exists.

● What should we want to occur?


○ my_counter + my_counter2 should create a new Counter object
○ Its _value should be the addition of the _values of the other two Counters.

Chapter 1 Basic Python Programming 36


The __add__ Method
● When Python encounters the + operator, it calls the __add__ method on its left
operand, passing the right operand as an argument.
○ If the __add__ method is not supported by the left operand’s class, an Error
occurs.

def __add__(self, other):


???

● Note: The __add__ method should return a new object, not mutate either of its
arguments (self or other).

Chapter 1 Basic Python Programming 37


The __add__ Method
● When Python encounters the + operator, it calls the __add__ method on its left
operand, passing the right operand as an argument.
○ If the __add__ method is not supported by the left operand’s class, an Error
occurs.

def __add__(self, other):

# Create a new Counter object to return


return_counter = Counter()

# Set the new object's value to the sum of the other two's values
return_counter.value = self.value + other.value
return return_counter

Chapter 1 Basic Python Programming 38


The __add__ Method
● With the + operator now overridden, the following code prints “Counter object with a
value of 4”:

my_counter = Counter()
my_counter.increment( 3)
my_counter2 = Counter()
my_counter2.increment()
my_counter3 = my_counter + my_counter2
print(my_counter3)

Chapter 1 Basic Python Programming 39


Other Methods to Override
● When we build more complicated classes and data structures in this course, we will
override other operators!

Chapter 1 Basic Python Programming 40


The Date Class
● For the remaining portion of the lecture, let’s create a class for use in scheduling
applications.
● Each Date object will represent a particular calendar date and store a day, a month,
and a year value.

Chapter 1 Basic Python Programming 41


The Date Class
● In this simple constructor, the client is able to set the month, day, and year of a Date
when they create one.

class Date:
def __init__(self, day, month, year):
self._day = day
self._month = month
self._year = year

● In the client code, a Date object can be created in the following way:
new_years = Date(1, 1, 2022)

Chapter 1 Basic Python Programming 42


The Date Class
● The days_in_month method returns how many days are in the Date object’s month
(ignoring leap years for now):

Chapter 1 Basic Python Programming 43


The Date Class
● The days_in_month method returns how many days are in the Date object’s month
(ignoring leap years for now):
def days_in_month( self):
if self.month in (4, 6, 9, 11):
return 30
elif self.month == 2:
return 28
else:
return 31

Chapter 1 Basic Python Programming 44


The Date Class
● Why might we not want to allow the client to do something like this?

d1 = Date(5, 12, 22)


d1._day += 1

Chapter 1 Basic Python Programming 45


The Date Class
● Why might we not want to allow the client to do something like this?

d1 = Date(5, 12, 22)


d1._day += 1

● Two reasons:
○ If the client accesses the _day attribute directly, they could set an invalid value:
d1._day = 55

○ When the client wants to update the _day, suppose the Date is currently at the
end of the month.
○ The client must calculate if it’s the end of the month (or even year) and then
adjust the month as well.
■ This calculation can be a little complex.
Chapter 1 Basic Python Programming 46
The Date Class
● Let’s write a method named advance which updates a Date object to the next day.
● This method should take into account the possibility that we’re at the end of the
month and the end of the year.

Chapter 1 Basic Python Programming 47


The Date Class
● Let’s write a method named advance which updates a Date object to the next day.
● This method should take into account the possibility that we’re at the end of the
month and the end of the year.
def advance(self):
self.day += 1
if self.day > self.days_in_month():
# wrap to next month
self.day = 1
self.month +=1
if self.month > 12:
# wrap to next year
self.month = 1

● We have abstracted this complexity away from the client.


● They no longer need to worry about advancing to an invalid date!

Chapter 1 Basic Python Programming 48


The Date Class
● A simple override of the __str__ method will allow us to test our Date class as well
as the advance method.

def __str__(self):
return f'{self._month}/{self._day}/{self._year}'

Chapter 1 Basic Python Programming 49


The Date Class
● Consider the following property and setter:
@property
def day(self):
return self._day

@day.setter
def day(self, new_day):
self._day = new_day

● Do they allow the client to make mistakes?

Chapter 1 Basic Python Programming 50


The Date Class
● Consider the following property and setter:
@property
def day(self):
return self._day

@day.setter
def day(self, new_day):
self._day = new_day

● Do they allow the client to make mistakes? Yes!

d1 = Date(5, 12, 22)


d1.day = 500 # ERROR: Invalid date!

Chapter 1 Basic Python Programming 51


The Date Class
● We need to validate the day the client tries to assign to a Date object as well as the
month.
● Furthermore, we need to validate these values in both the setters and the constructor
(so the client doesn’t initialize a Date with invalid data).
● To help with this, let’s write two helper functions:
○ validate_day: Make sure the day the client is trying to assign is within the
correct range based on the object’s _month.
○ validate_month: Make sure the month the client is trying to assign is within
the range of 1-12.

Chapter 1 Basic Python Programming 52


The Date Class
● Note that both methods raise Errors.
● During class initialization, it’s almost always better to raise an Error rather than
simply printing a message.

def validate_month(self, month):


if month < 1 or month > 12:
raise ValueError(f'Invalid month: {month}')

def validate_day(self, day):


if day < 1 or day > self.days_in_month():
raise ValueError(f'Invalid day: {day}')

● Why?

Chapter 1 Basic Python Programming 53


The Date Class
● Note that both methods raise Errors.
● During class initialization, it’s almost always better to raise an Error rather than
simply printing a message.

def validate_month(self, month):


if month < 1 or month > 12:
raise ValueError(f'Invalid month: {month}')

def validate_day(self, day):


if day < 1 or day > self.days_in_month():
raise ValueError(f'Invalid day: {day}')

● Why? When the client initializes a Date object and they provide invalid data, we
don’t know what the default _day or _month should be.
● It’s better to crash the program and let the client know they made a mistake.
Chapter 1 Basic Python Programming 54
The Date Class
● In the constructor, we now call both.
● The month must be validated and set first, since validate_day checks its value.

def __init__(self, day, month, year):


self.validate_month(month)
self._month = month
self.validate_day(day)
self._day = day
self._year = year

● And we call the relevant method in the corresponding setter:

@day.setter
def day(self, new_month):
self.validate_month(new_month)
self._month = new_month

Chapter 1 Basic Python Programming 55


The Date Class
● Next, we can override the __eq__ method to allow two Date objects to be compared.
● Two dates are equal if:

Chapter 1 Basic Python Programming 56


The Date Class
● Next, we can override the __eq__ method to allow two Date objects to be compared.
● Two dates are equal if:
○ The day, month, and year are all the same!

def __eq__(self, other):


return self._day == other._day and self._month == other._month and self._year == other._year

Chapter 1 Basic Python Programming 57


The Date Class
● Finally, we might want to sort a list containing Date objects.
● To do this, we need to override the __gt__ method.

Chapter 1 Basic Python Programming 58


The Date Class
● Finally, we might want to sort a list containing Date objects.
● To do this, we need to override the __gt__ method.

def __gt__(self, other):


if self._year > other.year:
return True
elif self._year == other.year and self._month > other.month:
return True
elif self._year == other.year and self._month == other.month and self._day > other.day:
return True
else:
return False

Chapter 1 Basic Python Programming 59

You might also like