Lecture 2 - Basic Python Programming Part 2
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.
● 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.
# 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
# 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.
# Constructor
def __init__ (self):
""" Creates a new Counter object and initializes its data """
Counter.instances += 1
self._value = 0
def main():
c1 = Counter()
c2 = Counter()
print(c1.instances) # Prints 2
main()
● 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:
● 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
def main():
Counter()
# More code goes here!
main()
def main():
Counter()
# More code goes here!
main()
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.
● 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.
c._value = -5
my_counter1 = Counter()
my_counter2 = Counter()
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!")
● 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
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
● 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.
my_counter = Counter()
print(my_counter) # Calls __str__ on my_counter
● If Counter does not override __str__, this code will print something like this:
● 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)
● What prints?
my_counter = Counter()
my_counter.increment()
my_counter2 = Counter()
print("my_counter is larger" if my_counter > my_counter2 else "my_counter2 is larger")
● 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:
● 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.
● What will occur? The program will crash: The + operator is not defined for Counter
objects. As with >, no default method exists.
● Note: The __add__ method should return a new object, not mutate either of its
arguments (self or other).
# 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
my_counter = Counter()
my_counter.increment( 3)
my_counter2 = Counter()
my_counter2.increment()
my_counter3 = my_counter + my_counter2
print(my_counter3)
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)
● 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.
def __str__(self):
return f'{self._month}/{self._day}/{self._year}'
@day.setter
def day(self, new_day):
self._day = new_day
@day.setter
def day(self, new_day):
self._day = new_day
● Why?
● 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.
@day.setter
def day(self, new_month):
self.validate_month(new_month)
self._month = new_month