Objects are everywhere in Python. And you can make your own!
Why?
- You can collect data and actions into one package
- You can change the object behavior to be "natural" for your situation
- You can replace parts of functions
- You can template an interface
Change behavior of objects
vector_1 = Vector(...)
vector_2 = Vector(...)
vector_3 = vector_1 + vector_2
print(vector_3)
Most of Python's syntax can be controlled by the classes. Math, printing, most built-in functions, indexing, iteration, with
statements, you name it. It's easier to go over what you can't modify: and
, or
, not
, is
, and the basic assignment operator.
Template an interface
class MySolution(InstructorTemplate):
def solution_1(...
You can implement a framework that requires a user implement a few parts, while you do the rest. Might be handy for problem sets, for example! Given Python's amazing abilities at inspection, you can do practically anything here.
Simplest class ever:
class VerySimpleClass:
pass
The class block is a lot like a "bag" that holds all definitions made in it. There are only two extras: a little bit of information is injected to make the class easier to use (like the name and the location in the file), and you "inherit" from object
- which means you get some predefined methods for free.
Aside: Python 2:
If you are stuck in Python 2, never leave the inherit part of a class empty - this will leave you with an "old-style" class, and it will not work as you expect. Put the Python base class
object
there instead.class VerySimpleClass(object): passPython 3 removed old style classes so it is safe to do this again.
Slightly more useful:
class MySimpleClass:
def __init__(self, value):
self.value = value
v = MySimpleClass(3)
v.value
Any functions (methods) in the class are expected to take the class instance as the first argument - always called self
by convention.
Special methods
__init__
: Sets up a class when a new one is created - called as part of the "constructor"__repr__
: Controls the "programmer's" display of a class (such as in interactive prompts) - often looks like the constructor.__str__
: Controls the printed form of a class__add__
: Most math operations are available, like adding (see Dive into Python 3 and official docs)
class Complex:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __add__(self, other):
return self.__class__(self.real + other.real, self.imag + other.imag)
def __sub__(self, other):
return self.__class__(self.real - other.real, self.imag - other.imag)
def __mul__(self, other):
return self.__class__(
self.real * other.real - self.imag * other.imag,
self.real * other.imag + self.imag * other.real,
)
def __repr__(self):
return f"{self.real} + {self.imag}j"
Complex(1, 2)
Complex(1, 0) + Complex(0, 1)
Complex(1, 0) - Complex(0, 1)
We can compare this with the builtin complex numbers:
Complex(1, 1) * Complex(1, 1)
complex(1, 1) * complex(1, 1)
import matplotlib.pyplot as plt
import math
# Using pure python to make a list from 0 to 5 with 500 points
x = [v / 100 for v in range(500)]
# Now compute the function, again with a list comprehension
y = [math.sin(30 * v) + math.sin(33 * v) for v in x]
plt.figure(figsize=(15, 3.5))
plt.plot(x, y)
plt.title("Beats: f(x)=sin(30*x)+sin(33*x)")
plt.xlabel("x")
plt.ylabel("f(x)")
- When we go into plotting, we'll see a "better" way to plot
- Later we'll use numpy here instead of Python's lists and math library
Reminder! list comprehensions and looping in Python is slow. We will soon see how to do this more cleanly and beautifully in numpy, and it will be faster too.
class Wave:
def __init__(self, x, coef):
self.x = x
self.y = [math.sin(coef * v) for v in x]
def __add__(self, other):
result = self.__class__(self.x, 0)
result.y = [a + b for a, b in zip(self.y, other.y)]
return result
def plot(self):
plt.plot(self.x, self.y)
plt.xlabel("x")
plt.ylabel("f(x)")
# Using pure python to make a list from 0 to 5 with 500 points
x = [v / 100 for v in range(500)]
# Now compute the function
wave = Wave(x, 30) + Wave(x, 33)
plt.figure()
wave.plot()
plt.title("Beats: f(x)=sin(30*x)+sin(33*x)")
import numpy as np
class WaveNP:
def __init__(self, x, coef):
self.x = x
self.y = np.sin(coef * x)
def __add__(self, other):
result = self.__class__(self.x, 0)
result.y = self.y + other.y
return result
def plot(self):
plt.plot(self.x, self.y)
plt.xlabel("x")
plt.ylabel("f(x)")
# Using numpy to make an array from 0 to 5 with 500 points
x = np.linspace(0, 5, 500, endpoint=False)
# Now compute the function
wavenp = WaveNP(x, 30) + WaveNP(x, 33)
plt.figure()
wavenp.plot()
plt.title("Beats: f(x)=sin(30*x)+sin(33*x)")
class Wave2(WaveNP):
def plot(self):
plt.plot(self.x, self.y, ".r")
plt.xlabel("x")
plt.ylabel("f(x)")
wave2 = Wave2(x, 30) + Wave2(x, 33)
plt.figure()
wave2.plot()
plt.title("Beats: f(x)=sin(30*x)+sin(33*x)")
You can even inherit from builtin classes, like int
!
You can also do several other things with classes:
- Forcing a user to subclass an implement one or more methods to use your class (This would be called an Abstract Base Class, or ABC)
- Multiple inheritance allows you to combine classes. Usually a bad idea.
We won't cover these things for now.
Our goal will be to take this:
original_path = "/home/myself/repository/folder"
And get this path from it:
new_path = "/home/myself/repository/other/file.txt"
In words: Go up one folder, down into other
folder, then get file.txt
in that folder.
original_path.rsplit("/", 1)[0] + "/other/file.txt"
What's wrong with this?
- Requires '/' be the separator - may break on Windows
- Breaks if you add ending slash to
original_path
- Is not self documenting - the procedure is in the code, not the intent
</font>
import os
os.path.join(os.path.dirname(original_path), "other", "file.txt")
- Avoid explicit separator
</font>
- Still breaks on ending slash
- Often not ideal on one line
- Does not scale well
- Correct function may be hard to find
</font>
from pathlib import Path
p = Path(original_path)
p.parent / "other" / "file.txt"
- Clear intent
- Ignores final slash
- Tab-completion on object
</font>
- Some libraries require that you add
str(p)
to use (or Python \< 3.6)
</font>
Other features:
- Replace name:
p.with_name("name.txt")
- With suffix:
p.with_suffix(".rst")
And, you can also make inquiries about the target file:
- Make path absolute:
p.absolute()
- Check for file at that location:
p.exists()
And many more!