A class is defined in Python using the class
statement. The syntax of this statement is class <ClassName>(superclass)
. In the absence of anything else, the superclass should always be object
, the root of all classes in Python.
![]() | Note |
---|---|
|
Here is a basic class definition for a class with one method. There are a few things to note about this method:
-
The single argument of the method is
self
, which is a reference to the object instance upon which the method is called, is explicitly listed as the first argument of the method. In the example, that instance isa
. This object is commonly referred to as the "bound instance." -
However, when the method is called, the
self
argument is inserted implicitly by the interpreter — it does not have to be passed by the caller. -
The attribute
__class__
ofa
is a reference to the class objectA
-
The attribute
__name__
of the class object is a string representing the name, as given in the class definition.
Also notice that "calling" the class object (A
) produces a newly instantiated object of that type (assigned to a
in this example). You can think of the class object as a factory that creates objects and gives them the behavior described by the class definition.
>>> class A(object): ... def whoami(self): ... return self.__class__.__name__ ... >>> a = A() >>> a <__main__.A object at 0x100425d90> >>> a.whoami() 'A'
The most commonly used special method of classes is the __init__()
method, which is an initializer for the object. The arguments to this method are passed in the call to the class object.
Notice also that the arguments are stored as object attributes, but those attributes are not defined anywhere before the initializer.
Attempting to instantiate an object of this class without those arguments will fail.
>>> class Song(object): ... def __init__(self, title, artist): ... self.title = title ... self.artist = artist ... def get_title(self): ... return self.title ... def get_artist(self): ... return self.artist ... >>> unknown = Song() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __init__() takes exactly 3 arguments (1 given)
Notice again that one argument was actually provided (self
) and only title and artist are considered missing.
So, calling Song
properly gives an instance of Song with an artist and title. Calling the get_title()
method returns the title, but so does just referencing the title
attribute. It is also possible to directly write the instance attribute. Using boilerplate getter / setter methods is generally considered unnecessary. There are ways to create encapsulation that will be covered later.
>>> leave = Song('Leave', 'Glen Hansard') >>> leave <__main__.Song object at 0x100431050> >>> leave.get_title() 'Leave' >>> leave.title 'Leave' >>> leave.title = 'Please Leave' >>> leave.title 'Please Leave'
One mechanism that can be utilized to create some data privacy is a preceding double-underscore on attribute names. However, it is possible to find and manipulate these variables if desired, because this approach simply mangles the attribute name with the class name. The goal in this mangling is to prevent clashing between "private" attributes of classes and "private" attributes of their superclasses.
>>> class Song(object): ... def __init__(self, title, artist): ... self.__title = title ... self.__artist = artist ... def get_title(self): ... return self.__title ... def get_artist(self): ... return self.__artist ... >>> leave = Song('Leave', 'Glen Hansard') >>> leave.get_title() 'Leave' >>> leave.__title Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Song' object has no attribute '__title' >>> leave._Song__title 'Leave'
Python provides many special methods on classes that can be used to emulate other types, such as functions, iterators, containers and more.
Functions. In order to emulate a function object, a class must define the method __call__()
. If the call operator ()
is used on an instance of the class, this method will be called behind the scenes. Here is an example that performs the same task as the adding closure in the functional programming section.
>>> class Adder(object): ... def __init__(self, extra): ... self.extra = extra ... def __call__(self, base): ... return self.extra + base ... >>> add2 = Adder(2) >>> add2(3) 5 >>> add5 = Adder(5) >>> add5(3) 8 >>> add2(1) 3
Iterators. When an object is used in a for ... in
statement, the object’s __iter__()
method is called and the returned value should be an iterator. At that point, the interpreter iterates over the result, assigning each object returned from the iterator to the loop variable in the for ... in
statement.
This example class implements the __iter__()
method and returns a generator expression based on whatever arguments were passed to the initializer.
>>> class Lister(object): ... def __init__(self, *args): ... self.items = tuple(args) ... def __iter__(self): ... return (i for i in self.items) ... >>> l = Lister('a', 'b', 'c') >>> for letter in l: ... print letter, ... a b c
Here is the same example using a generator function instead of a generator expression.
>>> class Lister(object): ... def __init__(self, *args): ... self.items = tuple(args) ... def __iter__(self): ... for i in self.items: ... yield i ... >>> l = Lister('a', 'b', 'c') >>> for letter in l: ... print letter, ... a b c
A class method in Python is defined by creating a method on a class in the standard way, but applying the classmethod
decorator to the method.
Notice in the following example that instead of self
, the class method’s first argument is named cls
. This convention is used to clearly denote the fact that in a class method, the first argument received is not a bound instance of the class, but the class object itself.
As a result, class methods are useful when there may not be an existing object of the class type, but the type of the class is important. This example shows a "factory" method, that creates Song objects based on a list of tuples.
Also notice the use of the __str__()
special method in this example. This method returns a string representation of the object when the object is passed to print
or the str()
builtin.
>>> class Song(object): ... def __init__(self, title, artist): ... self.title = title ... self.artist = artist ... def __str__(self): ... return ('"%(title)s" by %(artist)s' % ... self.__dict__) ... @classmethod ... def create_songs(cls, songlist): ... for artist, title in songlist: ... yield cls(title, artist) ... >>> songs = (('Glen Hansard', 'Leave'), ... ('Stevie Ray Vaughan', 'Lenny')) >>> for song in Song.create_songs(songs): ... print song ... "Leave" by Glen Hansard "Lenny" by Stevie Ray Vaughan
Static methods are very similar to class methods and defined using a similar decorator. The important difference is that static methods receive neither an instance object nor a class object as the first argument. They only receive the passed arguments.
As a result, the only real value in defining static methods is code organization. But in many cases a module-level function would do the same job with fewer dots in each call.
>>> class Song(object): ... def __init__(self, title, artist): ... self.title = title ... self.artist = artist ... def __str__(self): ... return ('"%(title)s" by %(artist)s' % ... self.__dict__) ... @staticmethod ... def create_songs(songlist): ... for artist, title in songlist: ... yield Song(title, artist) ... >>> songs = (('Glen Hansard', 'Leave'), ... ('Stevie Ray Vaughan', 'Lenny')) >>> for song in Song.create_songs(songs): ... print song ... "Leave" by Glen Hansard "Lenny" by Stevie Ray Vaughan
oop-1-parking.py.
''' >>> # Create a parking lot with 2 parking spaces >>> lot = ParkingLot(2) ''' ''' >>> # Create a car and park it in the lot >>> car = Car('Audi','R8', '2010') >>> lot.park(car) >>> car = Car('VW', 'Vanagon', '1981') >>> lot.park(car) >>> car = Car('Buick','Regal', '1988') >>> lot.park(car) 'Lot Full' >>> lot.spaces = 3 >>> lot.park(car) >>> car.make 'Buick' >>> car.model 'Regal' >>> car.year '1988' >>> for c in lot: ... print c 2010 Audi R8 1981 VW Vanagon 1988 Buick Regal >>> for c in lot.cars_by_age(): ... print c 1981 VW Vanagon 1988 Buick Regal 2010 Audi R8 >>> for c in lot: ... print c 2010 Audi R8 1981 VW Vanagon 1988 Buick Regal ''' if __name__ == '__main__': import doctest doctest.testmod()
As noted in the first class definition example above, a class defines a superclass using the parentheses list in the class definition. The model for overloading methods is very similar to most other languages: define a method in the child class with the same name as that in the parent class and it will be used instead.
oop-1-inheritance.py.
class Instrument(object): def __init__(self, name): self.name = name def has_strings(self): return True class PercussionInstrument(Instrument): def has_strings(self): return False guitar = Instrument('guitar') drums = PercussionInstrument('drums') print 'Guitar has strings: {0}'.format(guitar.has_strings()) print 'Guitar name: {0}'.format(guitar.name) print 'Drums have strings: {0}'.format(drums.has_strings()) print 'Drums name: {0}'.format(drums.name)
$ python oop-1-inheritance.py Guitar has strings: True Guitar name: guitar Drums have strings: False Drums name: drums
Calling Superclass Methods. Python has a super()
builtin function instead of a keyword and it makes for slightly clunky syntax. The result, however, is as desired, which is the ability to execute a method on a parent or superclass in the body of the overloading method on the child or subclass.
In this example, an overloaded __init__()
is used to hard-code the known values for every guitar, saving typing on every instance.
oop-2-super.py.
class Instrument(object): def __init__(self, name): self.name = name def has_strings(self): return True class StringInstrument(Instrument): def __init__(self, name, count): super(StringInstrument, self).__init__(name) self.count = count class Guitar(StringInstrument): def __init__(self): super(Guitar, self).__init__('guitar', 6) guitar = Guitar() print 'Guitar name: {0}'.format(guitar.name) print 'Guitar count: {0}'.format(guitar.count)
python oop-2-super.py Guitar name: guitar Guitar count: 6
There is an alternate form for calling methods of the superclass by calling them against the unbound class method and explicitly passing the object as the first parameter. Here is the same example using the direct calling method.
oop-3-super-alt.py.
class Instrument(object): def __init__(self, name): self.name = name def has_strings(self): return True class StringInstrument(Instrument): def __init__(self, name, count): Instrument.__init__(self, name) self.count = count class Guitar(StringInstrument): def __init__(self): StringInstrument.__init__(self, 'guitar', 6) guitar = Guitar() print 'Guitar name: {0}'.format(guitar.name) print 'Guitar count: {0}'.format(guitar.count)
python oop-3-super-alt.py Guitar name: guitar Guitar count: 6
Multiple Inheritance. Python supports multiple inheritance using the same definition format as single inheritance. Just provide an ordered list of superclasses to the class definition. The order of superclasses provided can affect method resolution in the case of conflicts, so don’t treat it lightly.
The next example shows the use of multiple inheritance to add some functionality to a class that might be useful in many different kinds of classes.
oop-4-multiple.py.
class Instrument(object): def __init__(self, name): self.name = name def has_strings(self): return True class Analyzable(object): def analyze(self): print 'I am a {0}'.format(self.__class__.__name__) class Flute(Instrument, Analyzable): def has_strings(self): return False flute = Flute('flute') flute.analyze()
$ python oop-4-multiple.py I am a Flute
Abstract Base Classes. Python recently added support for abstract base classes. Because it is a more recent addition, its implementation is based on existing capabilities in the language rather than a new set of keywords. To create an abstract base class, override the metaclass in your class definition (metaclasses in general are beyond the scope of this course, but they define how a class is created). Then, apply the abstractmethod
decorator to each abstract method. Note that both ABCMeta
and abstractmethod
need to be imported.
Here is a simple example. Notice that the base class cannot be instantiated, because it is incomplete.
oop-5-abc.py.
from abc import ABCMeta, abstractmethod import sys import traceback class Instrument(object): __metaclass__ = ABCMeta def __init__(self, name): self.name = name @abstractmethod def has_strings(self): pass class StringInstrument(Instrument): def has_strings(self): return True guitar = StringInstrument('guitar') print 'Guitar has strings: {0}'.format(guitar.has_strings()) try: guitar = Instrument('guitar') except: traceback.print_exc(file=sys.stdout)
$ python oop-5-abc.py Guitar has strings: True Traceback (most recent call last): File "samples/oop-5-abc.py", line 22, in <module> guitar = Instrument('guitar') TypeError: Can't instantiate abstract class Instrument with abstract methods has_strings
One feature of abstract methods in Python that differs from some other languages is the ability to create a method body for an abstract method. This feature allows common, if incomplete, functionality to be shared between multiple subclasses. The abstract method body is executed using the super()
method in the subclass.
oop-6-abcbody.py.
from abc import ABCMeta, abstractmethod class Instrument(object): __metaclass__ = ABCMeta def __init__(self, name): self.name = name @abstractmethod def has_strings(self): print 'checking for strings in %s' % \ self.name class StringInstrument(Instrument): def has_strings(self): super(StringInstrument, self).has_strings() return True guitar = StringInstrument('guitar') print 'Guitar has strings: {0}'.format(guitar.has_strings())
$ python oop-6-abcbody.py checking for strings in guitar Guitar has strings: True
oop-2-pets.py.
''' >>> cat = Cat('Spike') >>> cat.speak() 'Spike says "Meow"' >>> dog = Dog('Bowzer') >>> cat.can_swim() False >>> dog.can_swim() True >>> dog.speak() 'Bowzer says "Woof"' >>> fish = Fish('Goldie') >>> fish.speak() "Goldie can't speak" >>> fish.can_swim() True >>> generic = Pet('Bob') Traceback (most recent call last): ... TypeError: Can't instantiate abstract class Pet with abstract methods can_swim ''' if __name__ == '__main__': import doctest doctest.testmod()
As mentioned previously, while Python does not support declarations of attribute visibility (public, private, etc), it does provide mechanisms for encapsulation of object attributes. There are three approaches with different levels of capability that can be used for this purpose.
When an attribute of an object is accessed using dot-notation, there are three special methods of the object that may get called along the way.
For lookups, two separate methods are called: __getattribute__(self, name)
is called first, passing the name of the attribute that is being requested. Overriding this method allows for the interception of requests for any attribute. By contrast, __getattr__()
is only called when __getattribute__()
fails to return a value. So this method is useful if only handling undefined cases.
For setting attributes only one method, __setattr__(self, name, value)
is called. Note that inside this method body, calling self.name = value
will lead to infinite recursion. Use the superclass object.__setattr__(self, name, value)
instead.
The following example shows a course with two attributes capacity
and enrolled
. A third attribute open
is calculated based on the other two. However, setting it is also allowed and forces the enrolled attribute to be modified.
oop-7-intercept.py.
import traceback import sys class Course(object): def __init__(self, capacity): self.capacity = capacity self.enrolled = 0 def enroll(self): self.enrolled += 1 def __getattr__(self, name): if name == 'open': return self.capacity - self.enrolled else: raise AttributeError('%s not found', name) def __setattr__(self, name, value): if name == 'open': self.enrolled = self.capacity - value else: object.__setattr__(self, name, value) def __str__(self): return 'Enrolled: \t{0}\nCapacity:\t{1}\nOpen:\t{2}'.format( self.enrolled, self.capacity, self.open) course = Course(12) course.enroll() course.enroll() print course course.open = 8 print course
$ python oop-7-properties.py Enrolled: 2 Capacity: 12 Open: 10 Enrolled: 4 Capacity: 12 Open: 8
For the simple case of defining a calculated property as shown in the above example, the more appropriate (and simpler) model is to use the property
decorator to define a method as a propery of the class.
Here is the same example again using the property
decorator. Note that this approach does not handle setting this property. Also notice that while open()
is initially defined as a method, it cannot be accessed as a method.
oop-8-properties.py.
import traceback import sys class Course(object): def __init__(self, capacity): self.capacity = capacity self.enrolled = 0 def enroll(self): self.enrolled += 1 @property def open(self): return self.capacity - self.enrolled course = Course(12) course.enroll() course.enroll() print 'Enrolled: \t{0}\nCapacity:\t{1}\nOpen:\t{2}'.format(course.enrolled, course.capacity, course.open) print try: course.open() except: traceback.print_exc(file=sys.stdout) print try: course.open = 9 except: traceback.print_exc(file=sys.stdout)
$ python oop-8-properties.py Enrolled: 2 Capacity: 12 Open: 10 Traceback (most recent call last): File "samples/oop-8-properties.py", line 25, in <module> course.open() TypeError: 'int' object is not callable Traceback (most recent call last): File "samples/oop-8-properties.py", line 31, in <module> course.open = 9 AttributeError: can't set attribute
Using the property mechanism, setters can also be defined. A second decorator is dynamically created as <attribute name>.setter
which must be applied to a method with the exact same name but an additional argument (the value to be set).
In this example, we use this additional functionality to encapsulate the speed of a car and enforce a cap based on the type of car being manipulated.
oop-9-propertysetters.py.
class Car(object): def __init__(self, name, maxspeed): self.name = name self.maxspeed = maxspeed self.__speed = 0 @property def speed(self): return self.__speed @speed.setter def speed(self, value): s = int(value) s = max(0, s) self.__speed = min(self.maxspeed, s) car = Car('Lada', 32) car.speed = 100 print 'My {name} is going {speed} mph!'.format(name=car.name, speed=car.speed) car.speed = 24 print 'My {name} is going {speed} mph!'.format(name=car.name, speed=car.speed)
$ python oop-9-propertysetters.py My Lada is going 32 mph! My Lada is going 24 mph!
The final mechanism for encapsulating the attributes of a class uses descriptors. A descriptor is itself a class and it defines the type of an attribute. When using a descriptor, the attribute is actually declared at the class level (not in the initializer) because it is adopting some type information that must be preserved.
The descriptor class has a simple protocol with the methods __get__()
, __set__()
and __delete__()
being called on attribute access and manipulation. Notice that the descriptor does not store instance attributes on itself, but rather on the instance. The descriptor is only instantiated once at class definition time, so any values stored on the descriptor object will be common to all instances.
The following example tackles the speed-limit problem using descriptors.
oop-10-descriptors.py.
class BoundsCheckingSpeed(object): def __init__(self, maxspeed): self.maxspeed = maxspeed def __get__(self, instance, cls): return instance._speed def __set__(self, instance, value): s = int(value) s = max(0, s) instance._speed = min(self.maxspeed, s) class Animal(object): speed = BoundsCheckingSpeed(0) def __init__(self, name): self.name = name @property def speed_description(self): return '{name} the {type} is going {speed} mph!'.format(name=self.name, type=self.__class__.__name__.lower(), speed=self.speed) class Squirrel(Animal): speed = BoundsCheckingSpeed(12) class Cheetah(Animal): speed = BoundsCheckingSpeed(70) squirrel = Squirrel('Jimmy') squirrel.speed = 20 print squirrel.speed_description squirrel.speed = 10 print squirrel.speed_description cheetah = Cheetah('Fred') cheetah.speed = 100 print cheetah.speed_description
$ python oop-10-descriptors.py Jimmy the squirrel is going 12 mph! Jimmy the squirrel is going 10 mph! Fred the cheetah is going 70 mph!
![]() | Tip |
---|---|
Notice that values of the descriptor can be set for all instances of a certain class, while being different in different uses. The descriptor allows for the creation of a generic field "type" that can be shared in a configurable fashion across unrelated classes. It is more complex to use than properties, but can provide more flexibility in a complex object hierarchy. |
oop-3-portfolio.py.
''' >>> p = Portfolio() >>> stocks = (('APPL', 1000, 251.80, 252.73), ... ('CSCO', 5000, 23.09, 23.74), ... ('GOOG', 500, 489.23, 491.34), ... ('MSFT', 2000, 24.63, 25.44)) ... >>> for stock in stocks: ... p.add(Investment(*stock)) >>> print p['APPL'] 1000 shares of APPL worth 252730.00 >>> p['GOOG'].quantity 500 >>> p['GOOG'].close 491.33999999999997 >>> p['GOOG'].open 489.23000000000002 >>> for stock in p: ... print stock 1000 shares of APPL worth 252730.00 5000 shares of CSCO worth 118700.00 500 shares of GOOG worth 245670.00 2000 shares of MSFT worth 50880.00 >>> for stock in p.sorted('open'): ... print stock.name CSCO MSFT APPL GOOG >>> p['MSFT'].gain 0.81000000000000227 >>> p['CSCO'].total_gain 3249.9999999999927 >>> 'GOOG' in p True >>> 'YHOO' in p False ''' if __name__ == '__main__': import doctest doctest.testmod()