0% found this document useful (0 votes)
22 views17 pages

Classes, Constructors, and Attributes - Arcade 2023 Documentation

Chistes constructores y atributos Python
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
22 views17 pages

Classes, Constructors, and Attributes - Arcade 2023 Documentation

Chistes constructores y atributos Python
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

 / Chapters / 16.

Classes, Constructors, and Attributes

16. Classes, Constructors, and Attributes


Video: https://youtu.be/7BfXwcapLFQ
Slides: https://slides.com/paulcraven/14-classes-constructors-and-attributes/

Throughout this course we’ve been using variables to store a


value. We just learned how to store multiple values using a list.
The next step is object-oriented programming. This type of
programming has three advantages. One, we can group multiple
variables together in a single record. Two, we can associate
functions with that group of data. Three, we can use something
called inheritance which allows us to take a base set of code and extend it, without needing to
rewrite it from scratch.

16.1. Using Classes and Objects to Group Data


Grouping related data together using object-oriented programming can simplify our
code. For example, think of an adventure game. Each character in an adventure
game needs data, such as a name, what they look like, how many hit points they
have, their armor, and how fast they can move.

Without classes, our Python code to store the information might look like this:

1 name = "Link"
2 outfit = "Green"
3 max_hit_points = 50
4 current_hit_points = 50
5 armor_amount = 6
6 max_speed = 10

In order to do anything with this character, we’ll need to pass all that data to a function. With so
many parameters, that function gets complex and hard to manage.

1 def display_character(name, outfit, max_hit_points, current_hit_points, armor,


2 print(name, outfit, max_hit_points, current_hit_points)
As our game expands, we might start adding more character attributes, such as weapons, magic,
special abilities, and more. To do that we’d have to go through each function in our program that
works with the player character and redo the parameters.

Keeping all these data points organized becomes difficult very quickly. How do we keep a monster’s
hit points separated from the player’s hit points? Because when we add monsters to the game, they’ll
have their own attributes. In fact, just about every item in an adventure game has attributes.

There needs to be a better way. Somehow our program needs to package up all those data fields for
easy management.

16.2. Defining Classes


A better way to manage multiple data attributes is to define a structure to hold the information. We
can give that “grouping” of information a name, like Character or Address. This can be easily done in
Python and any other modern language by using a class. Each data item we group into the class is
called a field, attribute, or instance variable. These terms may be used interchangeably, as they
mean the same thing.

16.2.1. Defining the Class

Let’s code an example using our adventure character. First, we tell the computer we are defining a
class with the class keyword, and then we give the class a name that starts with a capital letter. Just
like with functions and loops, we end the statement with a colon, and everything associated with the
class will be indented below it:

1 class Character:

Unlike variables, all class names should start with a capital letter. While you can use a lower-case
variable, you never should. Following this pattern of lower-case for variables and upper-case for
classes makes it easy to tell which is which.

Next, we normally put into triple-quote comments a description of the class.

1 class Character:
2 """
3 This is a class that represents the player character.
4 """
Yes, the code will run fine without any comments. It is optional. However good documentation is
important to maintainable code, even if you are the only person using the code.

The cool feature about creating comments this way, is the text can be pulled out automatically to
form a website for your API documentation. All the classes and functions in the Arcade library’s API
are created with these comments. You can see the result here:

https://api.arcade.academy/en/latest/arcade.html

For each of those examples, you can click on the “source” link and quickly go to the source code for
that function or class.

16.2.2. Defining the Init Function

Any time we create a new instance of a class, we need code that will create our attributes (variables)
and set them to default values. In Python, this is the __init__ method.

This strangely named method needs a bit of explanation.

First, any function in a class is called a method, rather than a function. This helps us keep straight
what is in a class, and what isn’t.

Second, the initialization method is a magic method that is called automatically. Yes, Python
programmers actually call methods that are automatically invoked “magic methods.”

Third, to signify a method is magic, Python surrounds the method name with double underscores.
Two underscores in the front, and two underscores in the back. The short-name for double-
underline is dunder, and these magic methods are also known as dunder methods.

1 class Character:
2 """
3 This is a class that represents the player character.
4 """
5 def __init__(self):
6 """ This is a method that sets up the variables in the object. """

The most common mistakes people make when typing this in is to use only one underscore before
and after the init, and to forget that there is a space between def and the first underscore.
All methods in a class have at least one parameter, and the first parameter is always self. We’ll
explain about self in the next section.

16.2.3. Defining Class Attributes

Remember back to our chapter on functions, that any variable created


inside a function is forgotten about after the function is done running? If
you want to keep anything, you need to return it as a value.

Methods follow this rule too, with one exception. The self parameter refers to memory associated
with each instance of the class. We can use that self to create variables that keep their value for as
long as the object exists. We call variables that exist as part of the class either attributes, fields, or
instance variables. The terms mean the same thing. Attributes must be set to a default value. That
value is often 0, an empty string, or the special value None.

1 class Character:
2 """
3 This is a class that represents the player character.
4 """
5 def __init__(self):
6 """ This is a method that sets up the variables in the object. """
7 self.name = ""
8 self.outfit = ""
9 self.max_hit_points = 0
10 self.current_hit_points = 0
11 self.armor_amount = 0
12 self.max_speed = 0

In the example above, if we had failed to put self. in front, the computer would completely forget
about the variables once the __init__ function was done.

Here’s another example, we are defining a class called Address which has attributes for each field of
a US mailing address.
1 class Address:
2 """ Hold all the fields for a mailing address. """
3 def __init__(self):
4 """ Set up the address fields. """
5 self.name = ""
6 self.line1 = ""
7 self.line2 = ""
8 self.city = ""
9 self.state = ""
10 self.zip = ""

In the code above, Address is the class name. The variables in the class are the attributes.

The __init__ is a special method that you may also hear


Constructor?
referred to as a constructor. If you are programming in
other languages, the term constructor is a generic term
There is some debate about
used to refer to whatever that language’s equivalent to
calling __init__ a constructor.
the __init__ method is.
In some languages a
constructor is called before the
The self. is kind of like the pronoun my. When inside
computer sets aside memory
the class Address we are talking about my name, my city,
for the object. In Python the
etc. We don’t want to use self. outside the class. Why?
__init__ method is actually
Because just like the pronoun “my,” it means someone
called after this happens. For
totally different when said by a different person!
our purposes, the distinction is
16.3. Creating Objects not important.

The class code defines a class


but it does not actually create
an instance of one. The code
told the computer what fields
an address has, but we don’t
actually have an address yet.
We can define a class without
creating one just like we can
define a function without calling it.

To create an instance of the Address class, we use the following code:


1 def main():
2 # Create an address
3 home_address = Address()

We need a variable that will point to our address. In this case, we’ve called it home_address. We’ll set
that variable equal to the new instance of the class we create. We create an new instance by using
the name of the class (Address), followed by parentheses. This will “magically” call the __init__
method which will set up fields/attributes for the class.

In this case, Address is a class. It defines what an address looks like. The home_address variable
points to an object. An object is an instance of a class. It is the actual address. As another example,
“Human” is a class, while “Samantha” and “Pete” are instances of the class.

You can set the object’s attributes using the dot operator. First, use the variable that points to our
object, immediately follow that with a period, then the attribute name.

1 def main():
2 # Create an address
3 home_address = Address()
4
5 # Set the fields in the address
6 home_address.name = "John Smith"
7 home_address.line1 = "701 N. C Street"
8 home_address.line2 = "Carver Science Building"
9 home_address.city = "Indianola"
10 home_address.state = "IA"
11 home_address.zip = "50125"

A second variable can be created that points to a completely different instance of the Address class:
1 # Create another address
2 vacation_home_address = Address()
3
4 # Set the fields in the address
5 vacation_home_address.name = "John Smith"
6 vacation_home_address.line1 = "1122 Main Street"
7 vacation_home_address.line2 = ""
8 vacation_home_address.city = "Panama City Beach"
9 vacation_home_address.state = "FL"
10 vacation_home_address.zip = "32407"
11
12 print("The client's main home is in " + home_address.city)
13 print("His vacation home is in " + vacation_home_address.city)

Attributes are not limited to being simple strings and numbers! If you have a class that represents a
graph, you can store all the data points in an attribute that is a list. Attributes can even be other
objects. An object that represents a player character in an adventure could have an attribute with
another object that represents a magical hat.

16.3.1. Common Mistakes Creating Objects

The first common mistake when creating an object is to forget the parentheses:

1 # ERROR - Forgot the parentheses after Address


2 home_address = Address

The terrible thing about this mistake is that the program won’t stop or give you an error. Try
running the example we just created with the two different addresses. Take out the parentheses. The
program runs without error, but both the vacation home and the home address say we are in
Panama City! That’s because without the parentheses we don’t create a new address, we just use the
same block of memory and write the new information over the old, so everything points to the same
address.

Another very common mistake when working with classes is to forget to specify which instance of
the class you want to work with. If only one address is created, it is natural to assume the computer
will know to use that address you are talking about. This is not the case.

Take a look at this code:


1 class Address:
2 def __init__(self):
3 self.name = ""
4 self.line1 = ""
5 self.line2 = ""
6 self.city = ""
7 self.state = ""
8 self.zip = ""
9
10 def main():
11 # Create an address
12 my_address = Address()
13
14 # Alert! This does not set the address's name!
15 name = "Dr. Smith"
16
17 # This doesn't set the name for the address either
18 Address.name = "Dr. Smith"
19
20 # This runs, creates a new attribute but with the wrong name.
21 my_address.naem = "Dr. Smith"
22
23 # This does work:
24 my_address.name = "Dr. Smith"
25
26 main()

This code will run without generating an exception, but it still isn’t correct. Line 15 creates a variable
called name, but it is completely different than the name that is part of Address. So we think we’ve set
the name, but we haven’t.

Line 18 does refer to Address, but not my_address. Frustratingly it runs without alerting us to an
error, but the code isn’t modifying my_address. Instead it sets something called a static variable,
which we’ll talk about later.

Think of it this way. If you are in a room of people, saying “Age is 18” is confusing. Saying “Human’s
age is 18” is also confusing. Saying “Sally’s age is 18” is ideal, because you are saying which instance
of human you are referring to. You have to do this with programming, even if there is only one
human in the room.

Another mistake is on line 22. That line also runs fine, but it creates a new attribute called naem
instead of setting the desired attribute name.
16.4. Using Objects in Functions
Putting lots of data fields into a class makes it easy to pass data in and out of a function. In this
example, the function takes in an address as a parameter and prints it out on the screen. It is not
necessary to pass parameters for each field of the address.

Listing 1: Passing in an object as a function parameter

1 def print_address(address):
2 """ Print an address to the screen """
3
4 print(address.name)
5 # If there is a line1 in the address, print it
6 if len(address.line1) > 0:
7 print(address.line1)
8 # If there is a line2 in the address, print it
9 if len(address.line2) > 0:
10 print( address.line2 )
11 print(address.city + ", " + address.state + " " + address.zip)
12
13
14 def main():
15 # ... code for creating home_address and vacation_home_address
16 # goes here.
17 print_address(home_address)
18 print()
19 print_address(vacation_home_address)
20
21
22 main()

16.5. Customizing the Constructor


Take a look at this code, where we represent a dog using a class. Unfortunately,
there’s a terrible problem with the code. When we create a dog, the dog has no
name. Dogs should have names! Only horses in the desert can have no name.
1 class Dog():
2 def __init__(self):
3 """ Constructor """
4 self.name = ""
5
6
7 def main():
8 # This creates the dog
9 my_dog = Dog()
10 print(f"The dog's name is: {my_dog.name}")
11
12
13 main()

We can modify the code in our constructor to keep this from happening. First, let’s add a print
statement to our __init__ just to demonstrate that it is really being called.

1 class Dog():
2 def __init__(self):
3 """ Constructor """
4 self.name = ""
5 print("A new dog is born!")
6
7
8 def main():
9 # This creates the dog
10 my_dog = Dog()
11 print(f"The dog's name is: {my_dog.name}")

When the program is run, it will print this:

A new dog is born!

When a Dog object is created on line 10, the __init__ function is “magically” called and the message
is printed to the screen.

We can add a parameter to our constructor, so that it requires us to pass in a name for the dog. Try
running this code.
1 class Dog():
2 def __init__(self, new_name):
3 """ Constructor """
4 self.name = new_name
5 print("A new dog is born!")
6
7
8 def main():
9 # This creates the dog
10 my_dog = Dog()
11 print(f"The dog's name is: {my_dog.name}")
12
13
14 main()

You should get an error that looks like:

File "c:/my_project/test.py", line 10, in main


my_dog = Dog()
TypeError: __init__() missing 1 required positional argument: 'new_name'

The computer is saying it is missing a value for the new_name parameter. It won’t let the dog be
created without a name. We can fix that up by adding a name when we create the dog.

1 class Dog():
2 def __init__(self, new_name):
3 """ Constructor """
4 self.name = new_name
5 print("A new dog is born!")
6
7
8 def main():
9 # This creates the dog
10 my_dog = Dog("Fluffy")

Notice in line 4 we take the value that was passed in as a parameter and assign self.name to have
that same value. Without this line, the dog’s name won’t get set.

As programmers sometimes get tired of making up variable names, it is completely normal to see
code like this:
1 class Dog():
2 def __init__(self, name):
3 """ Constructor """
4 self.name = name
5 print("A new dog is born!")
6
7
8 def main():
9 # This creates the dog
10 my_dog = Dog("Fluffy")
11
12
13 main()

Though it may seem strange at first, we have two variables at work, not one. The first variable is
name, and that variable is assigned as a parameter when we call the Dog constructor. It goes away as

soon as the Dog constructor is done, and is forgotten about. The second variable is self.name, and
that variable is complete different than name. Its value will stay after the constructor is done.

16.6. Address Class With Init Parameters


Here’s another example, this time with our Address class. We supply the address attributes as part of
our __init__ when our address is created.

1 class Address:
2 def __init__(self, line1, line2, city, state, zip, country):
3 self.line1 = line1
4 self.line2 = line2
5 self.city = city
6 self.state = state
7 self.zip = zip
8 self.country = country
9
10
11 def main():
12 # This creates the address
13 my_address = Address("701 N. C Street",
14 "Carver Science Building",
15 "Indianola",
16 "IA",
17 "50125",
18 "USA")
19
20
21 main()
16.7. Typing Attributes
It is possible to tell Python what type of data should be stored in a class attribute. This allows a
programmer to use a tool like mypy and catch errors earlier in the development process.

In this example, we are adding a type definition to the name attribute on line 3. We do this by
following the variable name with a colon, and adding str which is the abbreviation for the string
data type.

1 class Person:
2 def __init__(self):
3 self.name: str = "A"
4
5
6 mary = Person()
7 mary.name = 22

By assigning a number to the name attribute on line 7, we are storing the wrong kind of data. The
program runs, but if we use the mypy tool, it will give us an error saying we’ve made a mistake:

1 test.py:7: error: Incompatible types in assignment (expression has type "int",


2 Found 1 error in 1 file (checked 1 source file)

Typing is great for large programs, and for programs where we want to make sure to catch all the
errors we can before shipping to customers.

As we are just learning programming, it can be distracting to try adding typing to our programs at
this stage. But we will be both looking and using, other people’s code which does use typing.
Therefore it is important to know what typing is, even if we don’t need to use it ourselves until later.

16.8. Data Classes


When creating a class and a constructor to define a set of fields, we end up with code that looks like
this:
1 class Address:
2 def __init__(self,
3 name: str = "",
4 line1: str = "",
5 line2: str = "",
6 city: str = "",
7 state: str = "",
8 zip_code: str = ""
9 ):
10 self.name: str = name
11 self.line1: str = line1
12 self.line2: str = line2
13 self.city: str = city
14 self.state: str = state
15 self.zip_code: str = zip_code

This code is repetitive, as we state the fields twice. If your __init__ method is only going to take in
data fields and assign attribute values, you can simplify your code by using a dataclass.

Starting with Python 3.8, you can write the same thing using only this code:

1 @dataclass
2 class Address:
3 name: str = ""
4 line1: str = ""
5 line2: str = ""
6 city: str = ""
7 state: str = ""
8 zip_code: str = ""

This makes the code a lot easier to both write, and to read.

16.9. Static Variables


Class attributes are also called instance variables because they can be different
for each instance of the class. If you have five instances of the Dog class, each
instance will have its own name.

In a few rare cases, we want to share data between all instances of a class. In
this example with a Cat class, we have a population variable. This variable is
not different for each cat.
1 class Cat:
2 population = 0
3
4 def __init__(self, name):
5 self.name = name
6 Cat.population += 1
7
8 def main():
9 cat1 = Cat("Pat")
10 cat2 = Cat("Pepper")
11 cat3 = Cat("Pouncy")
12
13 print("The cat population is:", Cat.population)
14
15 main()

In this case we use Cat.population to keep track of our cat population, and the program will print
out the correct count of 3.

Variables that don’t change for each instance of a class, are called class variables or static
variables. The terms mean the same thing and can be used interchangeably.

You refer to a static variable by using the class name Cat rather than any of the instance names like
cat1.

Static variables aren’t used that often. The only reason we are introducing them here is that it is not
unusual for students to accidentally use a static variable instead of an instance variable. In fact,
Python makes it a bit too easy to ‘blend’ the two concepts together.

For example, we can also print a static variable not just by using the class name, but also by using
the instance name:

1 print("The cat population is:", Cat.population)


2 print("The cat population is:", cat1.population)

When we are reading code and come across a variable like Cat.population, we immediately know it
is static. How? All class names start with a capital letter, so Cat is a class. The only attributes that we
can refer to with a class, rather than an instance, are static variables. So population must be static. If
we use cat1.population, a programmer reading that code might mistakenly assume it is an instance
variable rather than a static variable, so that makes debugging really hard. To reduce confusion,
always refer to static variables using the class name.

In this example, I set population to 4, and each print statement says population is 4. This is confusing
because I set one variable and the others change. If I just use Cat.population to refer to the
population, then I remove that confusion.

1 Cat.population = 4
2 print("The cat population is:", Cat.population)
3 print("The cat population is:", cat2.population)
4 print("The cat population is:", cat1.population)

Here’s where it gets really wild. As we just saw, I can print a static variable by referring to it with an
instance, rather than by the class name. I shouldn’t, but I can.

What if, instead of printing, I assign a value that way?

1 Cat.population = 4
2 cat3.population = 5
3 print("The cat population is:", Cat.population)
4 print("The cat population is:", cat1.population)
5 print("The cat population is:", cat2.population)
6 print("The cat population is:", cat3.population)

In this case Cat.population, cat1.population, and cat2.population all refer to the same static
variable. But once I assign a value to cat3.population it creates a brand-new instance variable. So
all the other cats use the static population value, while cat3 uses a new instance variable with the
same exact name as the static variable. The static variable is shadowed by the instance variable.
Therefore when we print cat3.population we get a 5. That type of bug is very hard to find.

For our purposes, we won’t need to use static variables, we only introduce them so that you can
better understand some confusing errors people occasionally run into.

16.10. Review
In this chapter we learned how to bundle together several related data items into a class. We call
these class attributes, instance variables, or fields. Each instance of a class is an object. Functions
defined in a class are called methods. A special magic method called when an object is created is
the __init__ method, which is used to set up instance variables and assign them their initial values.
Inside the class we refer to instance variables by putting self. in front of them, such as self.name.
Outside the class, we need to use a variable that refers to the class, such as customer.name.

Using classes helps simplify our code. We can use classes to represent:

Characters in a video game, with attributes for health, speed, and armor.
Graphs, with attributes for heading, size, and data.
A customer order, with a list as an attribute for each item in the order.

Data classes can be used to make it easier to define a class with a lot of attributes. Typing can be
used to make sure we don’t put the wrong type of data in an attribute. Static variables are
attributes that don’t change from object to object.

16.10.1. Review Questions

1. What are the three main advantages of object-oriented programming?


2. What keyword is used to define a new class?
3. All class names should start with an upper-case or lower-case letter?
4. Where do the comments for a class go? What kind of comments do you use? Why is there a
standard?
5. What is the difference between a function and a method?
6. What three different terms can be used to refer to data that is tied to a a class?
7. What is a magic method?
8. What is a dunder method?
9. All class methods should have start with the same parameter. What is that parameter?
10. What is the name of the method in a class where we define our attributes?
11. When defining a class attribute, what needs to go right before it?
12. What is a constructor?
13. What is the difference between a class and an object?
14. What are the common mistakes when creating instances (objects) of a class?
15. How can we make sure our attributes are assigned when the object is created?
16. What is the point of adding “typing” to a class?
17. What is a data class?
18. What are static variables?

16.10.2. Lab 6: Text Adventure

In Lab 6: Text Adventure, you’ll use a class to represent a room in an text adventure. You’ll use
attributes to store the room description, and which rooms are north, south, east and west of it. You’ll
use a list to store all the rooms in your adventure.

You might also like