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:

  1. Modifying it on the class itself, modifies it on all existing instances

  2. 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 to isinstance(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 to issubclass(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: