Or a class about classes!
Object-oriented programming is about using classes to define data abstraction. A strong abstraction should make use of encapsulation, modularity, and inheritance.
Actually it can be simpler
class Simple:
pass
But this is not compatible with Python 3.X so don’t do it.
class Image(object):
"""
Base class for images.
"""
def __init__(self, height, width):
self.height, self.width = height, width
def dimensions(self):
return (self.height, self.width)
The __init__ method defines the class initialized.
image = Image(100, 100)
print image.dimensions()
The self is the first argument to all class methods. self similar to this in C++ or Java. Unlike this in C++ or Java, self is not a keyword only a strong convention. It represents the current class instance. It is automatically passed when the object calls the method.
While can self be used to store information about the current instance, you can also use class level attributes to maintain information about all class instances. To do this we can use the __class__ attribute which is an attribute on all class instances.
class Counter(object):
count = 0
def __init__(self):
self.__class__.count += 1
first = Counter()
print first.count
second = Counter()
print second.count
Part of encapsulation is being able to keep information hidden. To mark a method as private you prefix the name with a double underscore. When this is done you can only call the method from inside the class.
class Hidden(object):
def __init__(self, data):
self.data = data
self.__print_data()
def __print_data(self):
print self.data
example = Hidden(4)
example.__print_data() # Will raise an error
Classes do not always have to extend from object. They can extend from one or more base classes. Doing this we can extend the functionality provided by the base class.
The super function delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class.
from image import Image
class SquareImage(Image):
def __init__(self, side):
super(SquareImage, self).__init__(side, side)
square = SquareImage()
print square.dimensions()
You can also extend built in Python types such as int, float, dict, list.
Possible extensions:
__init__ is called after the object is constructed. For immutable types (int, string, tuple) it is too late to modify the value. If you want to change how they are created you need to override the class constructor __new__.
class LanguageString(str):
def __new__(cls, value, lang=u'en'):
obj = str.__new__(cls, value)
obj.lang = lang
return obj
english_string = LanguageString('Hello')
spanish_string = LanguageString('Hola', lang='sp')
__new__ takes the class (cls) as its first argument. If it returns a new instance of the class then __init__ is called passing the new instance and the rest of the arguments. If __new__ doesn’t return a new instance then __init__ will not be called.
If you are extending object or a subclass of object will typically not need to change this method.
One use of overriding __new__ for a mutable type is the Singleton pattern.
The Singleton pattern is a common programming pattern for having a class which only allows for a single instance.
class Singleton(object):
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
return cls.__instance
class ExampleSingleton(Singleton):
pass
x = ExampleSingleton()
y = ExampleSingleton()
print x is y
Some critics of the Singleton pattern have noted that it is often used in cases where you don’t really care about object identity but really all you care about is shared state (such as global configuration).
class Borg(object):
_state = {}
def __new__(cls, *p, **k):
self = object.__new__(cls, *p, **k)
# override instance namespace with shared state
self.__dict__ = cls._state
config1 = Borg()
config1.debug = True
config2 = Borg()
print config2.debug
print config1 is config2
Python supports multiple inheritance.
Multiple inheritance can cause some ambiguity in what version of the method will be called. In Python methods are resolved depth-first from left to right in the defined parent classes. This means the order of base classes matter in the class definition.
class TypeA(object):
def name(self):
print u"Type A"
class TypeB(object):
def name(self):
print u"Type B"
Mixins are a sytle of using multiple inheritance. Each mixin class adds a small and specific piece of functionality.
This would be similar to interfaces in Java or C#.
A great example comes right from the Python standard library: SocketServer.py
class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
Comparison methods __lt__, __le__, __eq__, __ne__, __gt__, and __ge__ can be used to define object comparisons. They should return either True or False but can return any value which will be converted to a bool.
__cmp__(self, other) is called if the above methods are not defined. It should return a negative int if self < other, zero if equal and a positive integer if self > other.
class Student(object):
def __init__(self, name, grade):
self.name, self.grade = name, grade
def __lt__(self, other):
return self.name < other.name
def __cmp__(self, other):
return self.grade - other.grade
You can define common operators by defining __add__, __sub__, __mul__, __floordiv__, __mod__, __pow__, __lshift__, __rshift__, __and__, __or__, and __xor__.
class Media(object):
def __init__(self, css=None, js=None):
self.css = set(css or [])
self.js = set(js or [])
def __add__(self, other):
css = self.css | other.css
js = self.js | other.js
return Media(css=css, js=js)
Using the file system and handling problems.