Objects¶
In Python, everything is an object. If you’re familiar with another programming language, you might know there is a distinction between primitives and objects, but in Python even what would normally be a primitive is an object. Even types themselves are objects.
Methods¶
A method is a function associated with an object. To call a method, you first
type an expression that resolves to the object in question (normally a variable),
a period (.
), then the method name followed by parentheses and any arguments:
my_object.my_method(...)
You can think of methods as defining the “behavior” of an object.
Note
Because everything in Python is an object, including types and functions, everything has some methods that are defined for it. This can vary by what type an object is, but even integers have methods that define their behavior.
Method Chaining¶
If one method returns another object, you can immediately call a method on that returned object in the same line:
my_object.method1(...).method2(...)
Classes¶
Warning
We likely won’t need to make a whole lot of classes during this course, but it’s important to know what they are, how they work, and how to make them.
Every object is an instance of a class. Simply put, a class defines the common behaviors shared by each instance.
Think, for example, if you have a list of attributes and behaviors that all dogs share. Obviously, each dog is different, but the shared attributes and behaviors persist. The description of the attributes and behaviors is like a class, and each dog is like an instance of that class. Similarly, every instance of a class in Python might be different, but they will all share common attributes and behaviors.
Anatomy of a Class¶
Let’s start with an example class definition and then break it down into its components:
class MyClass:
def __init__(self, ...):
local_var = ...
self.instance_var = ...
def method(self, ...):
...
The class
Statement¶
class MyClass:
Every class definition is started with class ClassName
. In Python, it’s conventional
to use TitleCasing for class names (every word starts with a capital letter). Anything
indented below this statement is a member of the class definition.
Note
If you need an empty class definition for some reason, you can use pass
:
class EmptyClass:
pass
self
¶
Every method in a class definition accepts one argument at a minimum: self
.
This argument is implicit, meaning you don’t have to provide it when you call the
method. The only exceptions to this rule are static methods
(see below) and class methods
(even further below).
self
is a variable that evaluates to the instance the method is being called on. So
for example, if you call my_instance.some_method()
, then the self
parameter
used when calling some_method()
will be set to my_instance
.
Note
Use self
when accessing instance methods and variables.
The __init__
Method¶
def __init__(self, ...):
local_var = ...
self.instance_var = ...
The __init__
method defines how an instance of a class gets initialized. It’s sometimes
called the constructor of the class. It gets called when you create a new instance of
a class:
my_new_instance = MyClass(...)
This is kind of like calling a function, but you’re instead calling the class itself,
which has the effect of creating a new instance. Any parameters passed into this call
will get provided to the __init__
method.
Note
If no __init__
method is defined, the __init__
method of the superclass
is used instead (see Inheritance).
Instance Variables¶
Parameters passed to __init__
are often used to initialize instance variables,
which are variables associated to an instance of a class. To access them, you use
the same “dot notation” as when accessing a method:
my_instance.instance_var # access
my_instance.instance_var = something_else # assignment
Instance variables are created in the __init__
method by using the self
parameter
as the instance in the dot notation. Any variables not created on self
are just
local variables of __init__
and cannot be accessed by other pieces of code, including
methods on the same instance.
Class Variables
Variables can be defined at the class level, instead of the instance level. This creates a class variable, which is shared by the class itself and all instances of the class.
class MyClass:
class_variable = ...
Modifying a class variable is a little bit strange:
Modifying it on the class itself, modifies it on all existing instances
Modifying it on an instance only changes it on the instance, and also makes it so that modifications made on the class are no longer reflected on that instance
MyClass.class_variable = 0
instance1 = MyClass()
instance2 = MyClass()
print(instance1.class_variable, instance2.class_variable) # 0 0
MyClass.class_variable = 1
print(instance1.class_variable, instance2.class_variable) # 1 1
instance1.class_variable = 2
print(instance1.class_variable, instance2.class_variable) # 2 1
MyClass.class_variable = 3
print(instance1.class_variable, instance2.class_variable) # 2 3
instance2.class_variable = 4
print(instance1.class_variable, instance2.class_variable) # 2 4
MyClass.class_variable = 5
print(instance1.class_variable, instance2.class_variable) # 2 5
Instance Methods¶
To create your own methods, you create them like any other function, indented inside
the class body, including self
as the first parameter.
Static Methods
Sometimes, you might want a method that doesn’t actually need any specific instance
of a class to run, but it’s associated to that class and makes sense to bundle into it.
Methods of this type are called static, don’t have the self
parameter, and can
be called either from the class itself or from any instance:
class MyClass:
@staticmethod
def my_static_method():
...
# Can be called like:
MyClass.my_static_method()
# or
my_instance.my_static_method()
The staticmethod()
decorator right before the method
definition tells Python that the method is a static method and shouldn’t be
provided with the self
parameter.
Inheritance¶
In Python, classes can inherit from one another. Basically any attributes or behavior created in a superclass will also exist for a subclass:
# Superclasses are specified as seen below
class Subclass(Superclass):
...
Note
A class can have multiple superclasses, if you want, but it can make things significantly weirder.
Going back to the dog analogy, you might have a class Dog
that defines behavior
for all dogs, and you might make subclasses of Dog
that define breed-specific
attributes and behavior, such as ChocolateLab
or LittleCrustyWhiteDog
.
Important
All classes inherit from object
. It is implied, and you don’t need to manually
specify it.
A note on class variables
Class variables are inherited by a subclass. Similar to the weirdness with instances and class variables, subclasses share the same value of the class variable unless it gets redefined.
class A:
var = 1
class B(A):
var = 2 # B.var is no longer linked to A.var
class C(A):
pass # C.var is the same as A.var
B.var = 3 # doesn't change A.var
A.var = 4 # also affects C.var
C.var = 5 # C.var no longer tied to A.var
A.var = 6 # B.var is still 4, C.var is still 5
Inherited Methods¶
All methods in a superclass are inherited by the subclass. That is, you can call
a method on an instance of LittleCrustyWhiteDog
that is defined in Dog
, and it will
exhibit the same behavior as an instance of Dog
. If you want this behavior to
differ, you can redefine the method to do something different:
class Dog:
def speak(self):
print("Bark!")
class LittleCrustyWhiteDog(Dog):
def speak(self):
print("Yip!")
You can, optionally, call the corresponding method on the superclass using
super()
:
class GrandmasDog(LittleCrustyWhiteDog):
def speak(self):
super().speak() # Prints "Yip!"
print("Also pees on the ground for no reason")
A note on __init__
When no __init__
method is defined, the superclass’s __init__
will get called
by default, accepting the same parameters. If an __init__
method is defined
on the subclass, the superclass __init__
MUST be called with
super().__init__(...)
.
A note on static methods
If you want to redefine a static method in a subclass, you’ll want to decorate it with
staticmethod()
in the subclass. Otherwise, Python will think
you’re redefining the method to be an instance method.
Class Methods
Class methods are methods that are associated to the class itself, not to an instance
of the class. Instead of accepting the self
parameter, they accept a cls
implicit parameter, which is defined to be the class the method is called on:
class MyClass:
@classmethod
def my_class_method(cls, ...):
...
Class methods can be called on an instance on the class as well as the class itself.
Like static methods, class methods are decorated with classmethod()
.
Inheritance is where class methods are most useful. Say you have:
class A:
@classmethod
def do_something(cls):
print(cls.__name__)
class B(A):
@classmethod
def do_something(cls):
super().do_something()
print("Hello!")
Running A.do_something()
would print “A”, and running B.do_something()
would
print “B” followed by “Hello!”. Note how, as with static methods, when redefining
a class method, you want to use the classmethod()
decorator
again in the subclass.
Useful Functions and Methods¶
type(x)
and x.__class__
¶
If you want a reference to an object’s class, you can either use type
or x.__class__
:
class A:
pass
instance = A()
print(type(instance) == A) # True
print(type(instance) is A) # True
print(instance.__class__ == A) # True
print(instance.__class__ is A) # True
Note how in the example above, class comparisons can be performed with ==
and is
.
isinstance
¶
Because Python is dynamically typed, you might not know what type of object your variable represents. Or you might have a function that accepts multiple types of inputs, but should behave slightly differently for each type of input.
You can use the isinstance()
function to check to see if your
variable is an instance of a given class (or one of multiple classes). An instance
of a subclass is also considered to be an instance of the superclass.
class A:
pass
class B(A):
pass
var1 = A()
var2 = B()
print(isinstance(var1, A)) # True
print(isinstance(var2, A)) # True
print(isinstance(var1, B)) # False
print(isinstance(var2, B)) # True
print(isinstance(var1, (int, A))) # True because var1 is an instance of either int or A
Note
isinstance(x, object)
is always true, regardless of what x
is.
- isinstance(obj, class_or_tuple, /)
Return whether an object is an instance of a class or of a subclass thereof.
A tuple, as in
isinstance(x, (A, B, ...))
, may be given as the target to check against. This is equivalent toisinstance(x, A) or isinstance(x, B) or ...
etc.
issubclass
¶
If you want to see if a class inherits from another, you can use
issubclass()
.
class A:
pass
class B(A):
pass
var = B()
print(issubclass(B, A)) # True
print(issubclass(type(var), A)) # True
- issubclass(cls, class_or_tuple, /)
Return whether ‘cls’ is derived from another class or is the same class.
A tuple, as in
issubclass(x, (A, B, ...))
, may be given as the target to check against. This is equivalent toissubclass(x, A) or issubclass(x, B) or ...
.
Note
A class is considered to be a subclass of itself.
Magic Methods¶
Sometimes, you might want your class to mimic built-in types or have more control
over the life cycle of a class. This is facilitated with special methods that have
names starting with two underscores and ending with two underscores. These are often
called “magic methods” or “dunder methods” (for double-underscore). __init__
is
one such method.
There’s a huge list of all magic methods and their descriptions available on Python’s documentation.
Some useful ones to know:
__init__ (initialization)
__str__ (conversion to a human-readable string)
__bool__ (conversion to
True
orFalse
)__int__ (conversion to an integer)
__float__ (conversion to a floating-point number)
__getitem__ (access with indexing)
__setitem__ (assignment with indexing)
__delitem__ (deletion with indexing)
__contains__ (used with
in
andnot in
)Rich comparison methods (
<
,<=
,==
,>=
,>
,!=
)Numeric operators (
+
,-
,*
,/
,//
,%
,**
, etc.)