Functions
1 2 3 4 |
import math def compute_area(radius): return math.pi * (radius ** 2) |
1 2 3 4 5 6 |
>>> compute_area(.5) 0.7853981633974483 >>> compute_area(1) 3.141592653589793 >>> compute_area <function compute_area at 0x7fa4c99425f0> |
Methods are Functions Owned by a Class
Methods always take the instance as their first variable, but often this is done implicitly using the object instance followed by ‘.’ (dot), followed by the method name.
1 2 3 4 5 |
class Pizza(object): def __init__(self, size): self.size = size def get_size(self): return self.size |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
>>> Pizza.get_size() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unbound method get_size() must be called with Pizza instance as first argument (got nothing instead) # Note: is <function Pizza.get_size at 0x7f307f984dd0> in Python 3 >>> Pizza.get_size <unbound method Pizza.get_size> >>> Pizza.get_size(Pizza(42)) 42 >>> Pizza(42).get_size <bound method Pizza.get_size of <__main__.Pizza object at 0x7f3138827910>> >>> Pizza(42).get_size() 42 >>> m = Pizza(42).get_size >>> m() 42 >>> p1 = Pizza(12) >>> isinstance(p1, Pizza) True >>> issubclass(Pizza,object) True >>> issubclass(Pizza,Pizza) True >>> a = Pizza >>> a(42) <__main__.Pizza object at 0x103b4d518> >>> a(42).get_size() 42 |
Static Methods
Static methods act like regular functions, but are logically grouped with the class and can be called with the class name or object variable, followed by ‘.’ (dot), followed by the function name.
1 2 3 4 5 6 7 8 9 |
class Pizza(object): cheese = ['mozzarella'] def __init__(self, vegetables): self.vegetables = vegetables @staticmethod def mix_ingredients(x, y): return x + y def cook(self): return self.mix_ingredients(self.cheese, self.vegetables) |
1 2 |
>>> Pizza(['peppers','onions']).cook() ['mozzarella', 'peppers', 'onions'] |
1 2 3 4 5 6 |
>>> Pizza([]).cook is Pizza([]).cook False >>> Pizza([]).mix_ingredients is Pizza.mix_ingredients True >>> Pizza([]).mix_ingredients is Pizza([]).mix_ingredients True |
An Aside: Class and Instance Variables
Class variables are those defined without using self (Pizza’s cheese variable, for instance), while instance variables use self (self.vegetable, for example). You must be very careful to keep these straight. Say we are interested in changing the value of the ‘cheese’ class variable. Upon creating two Pizza variables they have exactly what we expect for ‘cheese’:
1 2 3 4 5 6 |
>>> p1 = Pizza(['peppers']) >>> p2 = Pizza(['onions']) >>> p1.cheese ['mozzarella'] >>> p2.cheese ['mozzarella'] |
Then, we set p2’s cheese to cheddar.
1 2 3 4 5 6 7 |
>>> p2.cheese = ['cheddar'] >>> p1.cheese ['mozzarella'] >>> p2.cheese ['cheddar'] >>> Pizza.cheese ['mozzarella'] |
Why wasn’t Pizza’s class variable changed? Well, when we said p2.cheese = [‘cheddar’], we created a new *instance variable* called cheese which is owned by p2. When looking up the value of a variable, instance variables are checked before class variables, so that’s why we get the instance variable’s value back. And indeed, when we change the class variable:
1 |
>>> Pizza.cheese = ['brie'] |
The instance variable still takes precedence for p2:
1 2 3 4 |
>>> p1.cheese ['brie'] >>> p2.cheese ['cheddar'] |
This “creation of instance variables on the fly” is a thing which you can actually always do, and not only with variable names which are already used for class variables. It’s rather dangerous:
1 2 3 4 5 6 7 8 9 10 11 |
>>> p1.meat = ['pepperoni'] >>> p1.meat ['pepperoni'] >>> p2.meat Traceback (most recent call last): File "", line 1, in AttributeError: 'Pizza' object has no attribute 'meat' >>> Pizza.meat Traceback (most recent call last): File "", line 1, in AttributeError: type object 'Pizza' has no attribute 'meat' |
Class Methods
Class methods implicitly take the class as their first argument.
1 2 3 4 5 |
class Pizza(object): radius = 42 @classmethod def get_radius(cls): return cls.radius |
1 2 3 4 5 6 7 8 9 10 |
>>> Pizza.get_radius <bound method type.get_radius of <class '__main__.Pizza'>> >>> Pizza().get_radius <bound method type.get_radius of <class '__main__.Pizza'>> >>> Pizza.get_radius is Pizza().get_radius True >>> Pizza.get_radius() 42 >>> Pizza().get_radius() 42 |
Class methods are usually used in two cases. First, as a factory method, building a class instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Fridge(object): def __init__(self, meat, cheese, vegetables): self.meat = meat self.cheese = cheese self.vegetables = vegetables def get_cheese(self): return self.cheese def get_vegetables(self): return self.vegetables def get_meats(self): return self.meat class Pizza(object): def __init__(self, ingredients): self.ingredients = ingredients @classmethod def from_fridge(cls, fridge): return cls(fridge.get_cheese() + fridge.get_vegetables()) def cook(self): return self.ingredients |
1 2 3 4 |
>>> f = Fridge(['beef', 'pork'], ['mozzarella'], ['peppers', 'onions']) >>> p = Pizza.from_fridge(f) >>> p.cook() ['mozzarella', 'peppers', 'onions'] |
Static methods calling static methods (avoids need to hard-code class name):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import math class Pizza(object): def __init__(self, radius, height): self.radius = radius self.height = height @staticmethod def compute_area(radius): return math.pi * (radius ** 2) @classmethod def compute_volume(cls, height, radius): return height * cls.compute_area(radius) def get_volume(self): return self.compute_volume(self.height, self.radius) |
Abstract Methods
An abstract method is a method defined in a base class, but that may not provide any implementation.
On the surface this seems a lot like Java’s abstract methods in interfaces or abstract classes, but see the next section.
1 2 3 4 5 6 7 |
from abc import ABC class BasePizza(ABC): # __metaclass__ = abc.ABCMeta # Use this instead of inheriting from ABC for Python 2.7 @abc.abstractmethod def get_radius(self): """Method that should do something.""" |
1 2 3 4 |
>>> BasePizza() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius |
If you’re interested in metaclasses, check out A Primer on Python Metaclasses.
Mixing Static, Class and Abstract Methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from abc import ABC class BasePizza(ABC): default_ingredients = ['cheese'] @classmethod @abc.abstractmethod def get_ingredients(cls): """Returns the ingredient list.""" return cls.default_ingredients # You can have implementation in the abstract method, and still force it to be overridden! class Calzone(BasePizza): def get_ingredients(self, with_egg=False): egg = ['egg'] if with_egg else [] return super().get_ingredients() + egg # Call Calzone's parent's get_ingredients() #return super(Calzone, self).get_ingredients() + egg # Python 2.7 compliant version (sometimes needed in 3 as well) class DietPizza(BasePizza): def get_ingredients(self): return ['peppers'] + super().get_ingredients() |
1 2 3 4 5 6 7 8 |
>>> p1 = Calzone() >>> p1.get_ingredients() ['cheese'] >>> p1.get_ingredients(with_egg=True) ['cheese', 'egg'] >>> p2 = DietPizza() >>> p2.get_ingredients() ['peppers', 'cheese'] |
Making Functions Attributes of Classes???
If you’ve done this, it’s not a good sign – you probably have made a mistake somewhere.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import math def get_volume(self): return Pizza.compute_volume(self.height, self.radius) class Pizza(object): get_pizza_volume = get_volume def __init__(self, radius, height): self.radius = radius self.height = height @staticmethod def compute_volume(height, radius): return height * math.pi * (radius ** 2) |
1 2 |
>>> Pizza(1,2).get_pizza_volume() 6.283185307179586 |
As Python 2.7 will only be supported until 2020, this page has been updated to reflect Python 3, with notes about Python 2.7.
Some of the contents of this page are adapted from a post on Julien Danjou’s blog. This page is therefore licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.