7. Classes and OOP

Everything in python are objects, even classes and codes.

Object identity

Every object has an identify, a type and a value. An object’s identify never changes once it has been created. You may think of it as the object’s address in memory. The is operator compares the identity of two objects. The id() functions returns an integer representing its identity.

CPython implementation detail: For CPython, id(x) is the memory address where x is stored.

An object’s type determines the operators that the object supports and also defines the possible values for objects of that type. The type() function returns an object’s type (which is an object itself).

The value of some objects can change. Objects whose value can change are said to be mutable; objects whose value is unchangeable once they are created are called immutable.

Objects are never explicitly destroyed; however, when they become unreachable they may be garbage-collected.

7.1. Basic usage

class Person:
    """
    Class represents a person
    """

    def __init__(self, name, job=None, pay=0):
        """
        Constructor method
        """
        self.name = name
        self.job = job
        self.pay = pay

    def __del__(self):
        """
        Deconstructor: it will be called when gc recycle this object, or *del* called.
        """

    def last_name(self):
        """
        The first argument of instance methods is *self*.
        It's the reference to current instance, just like *this* in C++ and Java.
        """
        return self.name.split()[-1]

    def give_raise(self, percent):
        self.pay = int(self.pay * (1 + percent))


if __name__ == '__main__':
    bob = Person('Bob Smith')
    sue = Person('Sue Jones', 'engineer', 8000)

    print(bob.last_name(), bob.pay)     # ('Smith', 0)
    sue.give_raise(.1)
    print(sue.last_name(), sue.pay)     # ('Jones', 8800)

Provding print displays:

>>> print(bob)
<__main__.Person instance at 0x1031634d0>

class Person:
  ...

  def __str__(self):
    return self.name

  def __repr__(self):
    return '[%s: %s, %s]' % (self.__class__.__name__, self.name, self.pay)

>>> bob
[Person: Bob Smith, 0]
>>> print(bob)
Bob Smith

>>> str(sue)
'Sue Jones'
>>> repr(sue)
'[Person: Sue Jones, 8800]'

Subclasses:

class Manager(Person):

  def __init__(self, name, pay):
    super().__init__(name, 'manager', pay)

  def give_raise(percent, bouns=.1)
    Person.give_raise(self, percent + bouns)

>>> tom = Manager('Tom Jones', 'manager', 5000)
>>> tom.give_raise(.1)
>>> repr(tom)
[Manager: Tom Jones, 6000]

Special class attributes:

>>> tom.__class__
<class 'person.Manager'>
>>> tom.__class__.__bases__
(<class 'person.Person'>,)
>>> tom.__dict__
{'job': 'manager', 'name': 'Tom Jones', 'pay': 6000}

Class methods and static methods

Properties:

class Person:
  def __init__(self, name):
    self._name = name

  def getName(self):
    print('fetch...')
    return self._name

  def setName(self, value):
    print('change...')
    self._name = value

  def delName(self):
    print('remove...')
    del self._name

  name = property(getName, setName, delName, "name property docs")

bob = Person('Bob Smith')
print(bob.name)             # getName
bob.name = 'Robert Smith'   # setName
print(bob.name)
Class decorator
Similar as function decorator. It’s a callable object which accepts a class and return a class.

7.2. Special attributes

object.__dict__
A dictionary or other mapping object used to store an object’s (writable) attributes.
instance.__class__
The class to which a class instance belongs.
class.__bases__
The tuple of base classes of a class object.
class.__name__
The name of the class or type.
class.__qualname__

The qualified name of the class or type.

New in version 3.3.

See PEP 3155 - Qualified name for classes and functions

class.__mro__
This attribute is a tuple of classes that are considered when looking for base classes during method resolution.
class.mro()
This method can be overridden by a metaclass to customize the method resolution order for its instances. It is called at class instantiation, and its result is stored in __mro__.
class.__subclasses__()

Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. Example:

>>> int.__subclasses__()
[<class 'bool'>]

7.3. Operator overloadding

Emulating numeric types

class Number:

  def __eq__(self, right):
    ...

  def __add__(self, right):
    ...

  def __sub__(self, right):
    ...

  def __mul__(self, right):
    ...

ten = two * five
six - one = ten - five

Full methods list for numeric types

Method Operator
__add__ +
__sub__ -
__mul__ *
__truediv__ /
__floordiv__ //
__mod__ %
__divmod__ divmod
__pow__ **, pwer
__lshift__ <<
__rshift__ >>
__and__ &
__xor__ ^
__or__ |
__radd__ +
__iadd__ +=
__neg__ -
__pos__ +
__abs__ abs
__invert__ ~
__complex__ complex
__int__ int
__float__ float
__round__ round
__index__ operator.index()

Comparisons

Method Operator
__lt__ <
__le__ <=
__eq__ ==
__ne__ !=
__gt__ >
__ge__ >=

To automatically generate ordering operations from a single root operation, see functools.total_ordering().

String related

Method Operator
__str__ str
__repr__ repr
__bytes__ bytes
__format__ format

Emulating callable objects

Method Operator
__call__ ()
>>> class Foo:
...   def __call__(self):
...     print("Callable")
...
>>> foo = Foo()
>>> foo()
Callable

Emulating container types

Method Operator
__len__ len
__length_hit__ operator.length_hint()
__getitem__ v = obj[key]
__setitem__ obj[key] = v
__delitem__ del obj[key]
__iter__ for _ in obj, Iteration
__reversed__ reversed()
__contains__ key in obj

With statment context manager

Method Operator
__enter__ with
__exit__ with
class cd:

    def __init__(self, path):
        self.path = path
        self.old = os.getcwd()

    def __enter__(self):
        os.chdir(self.path)

    def __exit__(self, exc_type, exc_value, traceback):
        os.chdir(self.old)

  with cd('/some/path'):
      ...
  # cd back to old path even exception occurs

See PEP 0343 - The “with” statement

Instance and subclass checks

Method Operator
__instancecheck__ isinstance
__subclasscheck__ issubclass

See PEP 3119 - Introducing Abstract Base Classes

Misc.

Method Operator Comments
__hash__ hash members of hashable collections including set, forzenset, dict.
__bool__ bool if a class defines neither __bool__ and __len__, all its instances considered true

7.4. Customize attribute access

Method Operator
__getattr__ o.attr, getattr(o, ‘attr’)
__getattribute__ o.attr, getattr(o, ‘attr’)
__setattr__ o.attr = val
__delattr__ del o.attr
__dir__ dir()
class Proxy:

  def __init__(self, wrapped):
    self.__dict__['_wrapped'] = wrapped

  def __getattr__(self, name):
    return getattr(self._wrapped, name)

  def __setattr__(self, name, value):
    setattr(self._wrapped, name, value)
>>> d = {}
>>> p = Proxy(d)
>>> p['a'] = 1
>>> p.b = 2
>>> p.keys()
dict_keys(['a'])
>>> p.__dict__
{'b': 2, '_wrapped': {'a': 1}}

Comparison between __getattr__ and __getattribute__

  • Both methods should return the (computed) attribute value or raise an AttributeError exception
  • __getattr__ is called when an attribute lookup has not found; however __getattribute__ is called unconditionally.
  • If AttributeError was raised in __getattribute__ then __getattr__ will be called.
  • In order to avoid infinite recursion in __getattribute__, its implementation should always call object.__getattribute__(self, name) to get attributes it needs.
  • Similarly, always call object.__setattr__(self, name, value) in __setattr__.

Descriptor

  • __get__
  • __set__
  • __delete__

See Descriptor HowTo Guide

Slots

__slots__

See Saving 9GB of ram with Python’s __slots__

7.5. Customize class creation

Method Operator
__new__ C() before __init__
__init__ C()
__del__ del o (gc)
__prepare__  
__new__
Called to create a new instance of class cls.
  • If __new__() returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self, …), where self is the new instance and the remaining arguments are the same as were passed to __new__().
  • If __new__() does not return an instance of cls, then the new instance’s __init__() method will not be invoked.

__new__() is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It’s also commonly overridden in custom metaclasses in order to customize class creation.

class LoginForm(forms.Form):

  email = forms.EmailField()
  password = forms.PasswordField()

form = LoginForm(request.POST)    # {'username': 'abcd', 'password': 'abcd'}
if not form.is_valid()
  return HttpResponse(form.error_as_string())
class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
  ...

class DeclarativeFieldsMetaclass:

  def __new__(mcs, name, bases, attrs):
    current_fields = []
      for key, value in list(attrs.items()):
        if isinstance(value, Field):
          current_fields.append((key, value))
          attrs.pop(key)
          attrs['declared_fields'] = OrderedDict(current_fields)
    ...
    new_class = (super(DeclarativeFieldsMetaclass, mcs)
      .__new__(mcs, name, bases, attrs))
    ...

    new_class.base_fields = declared_fields
    new_class.declared_fields = declared_fields

    return new_class

class BaseForm(object):
  def __init__(self, ...):
    ...
    self.fields = copy.deepcopy(self.base_fields)

  def __getitem__(self, name):
    "Returns a BoundField with the given name."
    try:
      field = self.fields[name]
    except KeyError:
      raise KeyError(
        "Key %r not found in '%s'" % (name, self.__class__.__name__))
    return BoundField(self, field, name)

By default, classes are construted using type(name, bases, dict). In the following exmaple, both MyClass and MySubclass are instances of Meta:

class Meta(type):
  pass

class MyClass(metaclass=Meta):
  pass

class MySubclass(MyClass):
  pass

When a class definition is executed, the following steps occur:

  1. the appropriate metaclass is determined
  2. the class namespace is prepared
  3. the class body is executed
  4. the class object is created

Determining the appropriate metaclass

  • if no bases and no explicit metaclass are given, then type() is used
  • if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass
  • if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used

Preparing the class namespace

  • namespace = metaclass.__prepare__(name, bases, **kwds)
  • otherwise, an empty dict() instance

kwds come from the class definition.

Executing the class body

exec(body, globals(), namespace)

Creating the class object

Once the class namespace has been populated by executing the class body, the class object is created by calling

metaclass(name, bases, namespace, **kwds)

After the class object is created, it is passed to the class decorators included in the class definition (if any) and the resulting object is bound in the local namespace as the defined class.

class OrderedClass(type):

     @classmethod
     def __prepare__(metacls, name, bases, **kwds):
        return collections.OrderedDict()

     def __new__(cls, name, bases, namespace, **kwds):
        result = type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

class A(metaclass=OrderedClass):
    def one(self): pass
    def two(self): pass
    def three(self): pass
    def four(self): pass
>>> A.members
('__module__', 'one', 'two', 'three', 'four')
See PEP 3115 - Metaclasses in Python 3000
Introduced the __prepare__ namespace hook
See PEP 3135 - New super
Describes the implicit __class__ closure reference

See Special method names for the full list of special method names

7.6. Advanced topics

The “New style” class model
From 2.2, python introduced a new flavor of classes, known as new-style classes. classes following the original and traditional model became known as classic classes. In 3.x only the new style remained.

For 2.x, classes must explicitly inherit from object to be considered “new style”, otherwise they are “classic”:

class Foo:          # classic
  pass

class Bar(object):  # new style
  pass

See Old and New classes

MRO and super #TODO