You’ve just seen the difference between class and instance variables. Classes can also have class methods - methods that are shared among all instances of a certain type. As with variables, they can be overriden in a specific instance or subclass.
Let’s add a class method to our Car class:
class Car:
runs = True
number_of_wheels = 4
@classmethod
def get_number_of_wheels(cls):
return cls.number_of_wheels
def start(self):
if self.runs:
print("Car is started. Vroom vroom!")
else:
print("Car is broken :(")
And call it:
>>> my_car = Car()
>>> print(f"Cars have {Car.get_number_of_wheels()} wheels.")
Cars have 4 wheels.
# Of course, we can override this in our instance:
>>> my_car.number_of_wheels = 6
# And when we access our new instance variable:
>>> print(f"My car has {my_car.number_of_wheels} wheels.")
My car has 6 wheels.
# But, when we call our class method on our instance:
>>> print(f"My car has {my_car.get_number_of_wheels()} wheels.")
My car has 4 wheels.
Why? Because get_number_of_wheels()
is a class method, and when it’s called, the class (Car) gets passed in, and the value of Car.number_of_wheels
is returned. Although we can access the instance variable (with a value of 6), the get_number_of_wheels()
class method still returns the class variable, which is 4.
type
, isinstance
, and issubclass
Python comes with some built-in functions for inspecting classes and types:
As we’ve seen throughout the workshop, the type()
function returns the type of the object you pass it, or it’s class. For example:
>>> type(42)
<class 'int'>
>>> type("Hello world!")
<class 'str'>
>>> type(my_car)
<class '__main__.Car'>
The isinstance()
function takes an object and a class, and returns True
if the object you pass it is an instance of the class. For example:
>>> isinstance(42, int)
True
>>> isinstance("Hello world!", str)
True
>>> isinstance(my_car, float)
False
>>> isinstance(my_car, Car)
True
The issubclass
function takes two classes, and returns True
if the first class is a subclass of the second. For example:
# bool is a subclass of int
>>> issubclass(bool, int)
True
# int is a subclass of object
>>> issubclass(int, object)
True
# technically, everything is a subclass of object
>>> issubclass(bool, object)
True
__init__
Classes can have an optional magic method called __init__()
that gets run when you instantiate an instance of a class. You can use the __init__()
method to do any special thing you want to happen when your instance is instantiated, including setting instance variables. __init__
can take arguments, too.
Methods that are bracketed by underscores are sometimes called “magic methods.” We won’t be covering magic methods in this class, but we will point out a few of the interesting ones.
For example:
class Car:
runs = True
def __init__(self, make, model):
self.make = make
self.model = model
def start(self):
if self.runs:
print(f"Your {self.make} {self.model} is started. Vroom vroom!")
else:
print(f"Your {self.make} {self.model} is broken :("
>>> my_car = Car("Ford", "Thunderbird")
>>> my_car.start()
Your Ford Thunderbird is started. Vroom vroom!
Here, we accept two required variables, make
and model
in our __init__()
method, and set instance variables of the same names using self
. Later, when we call start()
, we can grab self.make
and self.model
from the bound instance and use them in our string.
__str__
and __repr__
Classes have two other magic methods that come in handy for debugging, __str__()
and __repr__()
. Both functions return a string representation of an object. __str__()
should return readable end-user output, and __repr__()
should return the Python code necessary to rebuild the object. __str__()
maps to the built-in function str()
and __repr__()
maps to the built-in function repr()
.
For example, we’ll use the datetime
library to generate a datetime
object for right now:
>>> import datetime
>>> now = datetime.datetime.now()
>>> str(now)
'2019-03-16 21:04:01.396256'
>>> repr(now)
'datetime.datetime(2019, 3, 16, 21, 4, 1, 396256)'
You can see that str()
has returned a human-readable date/time, and repr()
has returned a string that represents the Python code we would need to run to recreate this object.
We can, of course, set our own __str__()
and __repr__()
methods in our custom classes:
class Car:
def __init__(self, make, model):
self.make = make
self.model = model
def __str__(self):
return f"<<Car object: {self.make} {self.model}>>"
def __repr__(self):
return f"Car('{self.make}', '{self.model}')"
>>> my_car = Car("Ford", "Thunderbird")
>>> print(f"This object is a {str(my_car)}")
This object is a <<Car object: Ford Thunderbird>>
>>> print(f"To reproduce it, type: {repr(my_car)}")
To reproduce it, type: Car('Ford', 'Thunderbird')
You don’t have to instantiate everything by hand, you can instantiate objects in for
loops or even comprehensions. This is useful for running a function on a list of objects. For example, to convert a list of number-strings into a list of integers, you could do:
>>> my_ints = [int(str_num) for str_num in ["1", "2", "3"]]
>>> print(my_ints)
[1, 2, 3]