{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# What is object oriented programming?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fundamental for object (-orientated) programming (OOP) is the concept of **object**---a complete combination of *data and operations* on these data. Objects usually (not always) hide data and externally provide only a limited set of operations, ie. *methods*. Typically the person who accesses an object is not interested in the implementation of those methods (the object can silently delegate their execution to other objects), or the way the objects's data are arranged.\n", "\n", "Object-oriented programming also includes several other concepts. Programming languages adopt and interpret them in different ways. Let us show which concepts are present in Python and how to use them.\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Objects\n", "\n", "In Python, *anything is an object* (unlike C ++ or Java). Objects are all built-in types (numbers, strings, ...), all containers, as well as functions, modules, and object types. Absolutely everything provides some public methods." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Object's type\n", "\n", "Each object has a type; types can be divided into *built-in types* (list, tuple, int, ...) and *classes* (types defined using the `class` keyword ). The type determines what methods an object offers, it is a sort of a template (general characteristics), from which the individual object differs by its internal state (specific properties). We say that *object is an instance of that type (class)*. To determine the type of an object Python has a built-in function `type`." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "\n", "\n", "True\n" ] } ], "source": [ "print(type(\"Babička\"))\n", "print(type(46878678676848648486)) # long in Python 2, int in Pythonu 3\n", "print(type(list())) # an instance of the list type\n", "print(type(list)) # the list type itself\n", "\n", "print(isinstance(list(), list)) # The \"isinstance\" checks the object type" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Instantiation\n", "\n", "An instance of a given type is created similarly to calling a function. If we have a data type (class), create an instance, just as we like to call it, ie. using parethesis. After all, we have already done so with built-in types like tuple, dict or list.\n", "Effectively, the instantiation process involves calling the class constructor (see below)." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "objekt = []\n", "objekt2 = \n" ] } ], "source": [ "objekt = list() # Creates a new instace of the list type\n", "objekt2 = list # This does not create a new instace! It just gives a new name to the list type\n", "\n", "# Let's see what we've got\n", "print(\"objekt = %s\" % objekt)\n", "print(\"objekt2 = %s\" % objekt2)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Now we can create a list using obejct2\n", "objekt2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using methods\n", "\n", "Method is a function that is tied to some object (and is basically meaningless without the object) and operates with its data. It can also change the internal state of the object, ie. the attributes' values.\n", "\n", "In Python, methods are called using dot notation, **`object.method(arguments)`**" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[45, 46, 47, 48, 49]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "objekt = [45, 46, 47, 48] # objekt is a list instance\n", "objekt.append(49) # we call its append method\n", "\n", "objekt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The append method has no meaning in itself, only in conjunction with a specific list; it adds a new element to the list." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Classes\n", "\n", "Class is any user type. Like built-in types, it offers methods and data (attributes), which we can arbitrarily define.\n", "\n", "The simplest definition of an empty class (pass is used for empty classes and methods to circumvent the indentation):" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class MyClass(object): # create a new class calles MyClass\n", " pass # the class is empty" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Method definition\n", "\n", "Methods are defined within the calss block. (NB. Methods can be added to the class later, but it is not the preferred method.)\n", "\n", "Conventional methods (instance methods) are called on a particular object. Besides, there are also so-called class methods and static methods, which we are not going to discuss.\n", "\n", "Quite unusual (unlike C ++, Java and other languages) is that the first argument of the method is the object on which the method is called. Without that, the method does not know with which object it is working! By convention (which is perhaps never violated), this first argument is called **self**. When the method is called, Python fill this argument automatically.\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rolling 100 kilometers.\n" ] } ], "source": [ "class Car(object):\n", " def roll(self, distance): # Don't forget *self*\n", " print(\"Rolling {} kilometers.\".format(distance))\n", " \n", "car = Car() \n", "car.roll(100) # self is omitted" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Error! Notice the number of arguments that Python complains about." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "ename": "TypeError", "evalue": "roll() takes 2 positional arguments but 3 were given", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mcar\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mroll\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcar\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m100\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: roll() takes 2 positional arguments but 3 were given" ] } ], "source": [ "car.roll(car, 100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Constructor\n", "\n", "Contructor is the method that initializes the object. It's called when we create a new instance. We can (and in most cases we do) define it but we do not have to, in which case the default constructor is used that simply does nothing (special). The constructor in Python is always named **`__init__`** (two underscores before and after)." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Before instantiating MyClass2\n", "We are in the constructor\n", "After instantiating MyClass2\n" ] } ], "source": [ "class MyClass2(object):\n", " def __init__(self):\n", " print(\"We are in the constructor\")\n", "\n", "print(\"Before instantiating MyClass2\")\n", "# The constructor will be called now\n", "objekt = MyClass2()\n", "print(\"After instantiating MyClass2\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Attributes\n", "\n", "Python does not distinguish between methods and data (such as in general the variables - everything is an object). Everything is an **attribute** of the object. Values are assigned similarly as variables but we have to add the object and the dot. \n", "The attributes may not even exist yet when it is assigned (it does not have to be declared).\n", "(NB. Internally attributes are stored in dictionaries and access to them is through the dictionary of the object itself, its class, its parent class, ...). " ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "My car has a consumtions of 15 l/100 km.\n", "Rolling 150 kilometrs, using 22.5 liters of gas.\n" ] } ], "source": [ "from __future__ import division # division operator from Pythonu 3\n", "\n", "class Car(object):\n", " def __init__(self, consupmtion): # constructor with an argument\n", " self.consupmtion = consupmtion # simply store as an attribute (of self)\n", " \n", " def roll(self, distance):\n", " # the consumption attribute is used\n", " gas = distance / 100 * self.consupmtion\n", " # gas is local, not an attribute\n", " print(\"Rolling {} kilometrs, using {} liters of gas.\".format(distance, gas))\n", " \n", "car = Car(15)\n", "print(\"My car has a consumtions of {} l/100 km.\".format(car.consupmtion)) \n", "car.roll(150)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The list of all attributes is returned by `dir`" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "'consupmtion, roll'" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# attributes with underscore are special, we'll filter them out\n", "\", \".join(item for item in dir(car) if not item.startswith(\"_\")) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Properties\n", "\n", "Properties are \"smarter\" data. They allow you to enter into the process of reading or setting attributes. It is useful for example if an object has several interdependent parameters, and we do not want to store them independently; or if we want to check what value is stored; or if we want to do anything interesting with the values.\n", "\n", "From the syntactic point of view, we must first define the method that bears the name of the property and that \"reads\" the property (returns its value). The line above must a **`property`** *decorator* (for details see e.g. [Python decorators in 12 easy steps](http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/)). If we want, we can then create methods for writing and deleting.\n", "\n", "Once we have created the following properties, we approach them as common data attributes - call them without brackets and assign to them using the sign \"equals\".\n", "\n", "Properties work like properties in C# or Java JavaBeans. However notice that for accessing properties exactly the same notation as for accessing data attributes is used. Hence if someone wants to change the behavior of a data attribute and make it a property, clients of the class will not recognize it and will not have to make any changes in the code. It is therefore not suitable to aggressively create trivial properties that encapsulate only access to attributes (like we would certainly do in Java).\n", "\n", "We will show how properties work on a simple example of a Circle class, which can set both the radius and the area consistently." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "r = 1\n", "S = 3.141592653589793\n", "Changing the area to 5\n", "r = 1.2615662610100802\n", "S = 5.000000000000001\n" ] } ], "source": [ "import math\n", "import numbers\n", "\n", "class Circle(object):\n", " def __init__(self, r):\n", " self.radius = r\n", " \n", " @property # this will be our are \"reader\"\n", " def area(self): # this looks like any other method\n", " return math.pi * self.radius ** 2\n", " \n", " @area.setter # area \"setter\"\n", " def area(self, s): \n", " print(\"Changing the area to {}\".format(s))\n", " if not isinstance(s, numbers.Number): # is s a number?\n", " raise TypeError(\"Area must me a number\")\n", " # the radius must be set consistently\n", " self.radius = math.sqrt(s / math.pi)\n", " \n", " @area.deleter\n", " def area(self):\n", " raise Exception(\"Deleting circle's does not make any sense\")\n", " \n", "# create a circle with unity radius\n", "circle = Circle(1)\n", "print(\"r = {}\".format(circle.radius)) # usual attribute\n", "print(\"S = {}\".format(circle.area)) # a property\n", "\n", "circle.area = 5 # Changing radius using the area setter\n", "print(\"r = {}\".format(circle.radius)) # We've changed the radius accordingly\n", "print(\"S = {}\".format(circle.area)) # a property" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Changing the area to Just like the biggest Czech pond, which is called Rožmberk.\n" ] }, { "ename": "TypeError", "evalue": "Area must me a number", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# Let's see if the check in the \"setter\" works\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mcircle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0marea\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m\"Just like the biggest Czech pond, which is called Rožmberk.\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36marea\u001b[1;34m(self, s)\u001b[0m\n\u001b[0;32m 14\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Changing the area to {}\"\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ms\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 15\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ms\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mNumber\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;31m# is s a number?\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 16\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Area must me a number\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 17\u001b[0m \u001b[1;31m# the radius must be set consistently\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 18\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mradius\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mmath\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msqrt\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ms\u001b[0m \u001b[1;33m/\u001b[0m \u001b[0mmath\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpi\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mTypeError\u001b[0m: Area must me a number" ] } ], "source": [ "# Let's see if the check in the \"setter\" works\n", "circle.area = \"Just like the biggest Czech pond, which is called Rožmberk.\"" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "ename": "Exception", "evalue": "Deleting circle's does not make any sense", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mException\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# Another meaningless operation\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[1;32mdel\u001b[0m \u001b[0mcircle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0marea\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36marea\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 20\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0marea\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdeleter\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 21\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0marea\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 22\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Deleting circle's does not make any sense\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 23\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 24\u001b[0m \u001b[1;31m# create a circle with unity radius\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mException\u001b[0m: Deleting circle's does not make any sense" ] } ], "source": [ "# Another meaningless operation \n", "del circle.area" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Encapsulation \n", "\n", "Python does not adhere to this (fundamental) OOP concept very strongly. The principles of OOP claim that the data should not be accessible from outside. Other languages usually offer a way of hiding some methods (such as the keywords private or protected in C++, Java). Python does not try to resolve this issue and, by default, everything is accessible. \n", "\n", "Instead, there exist the following conventions:\n", "\n", "* Object's data (unless the class is really primitive) are not modified from outside.\n", "* Methods whose name starts with an underscore will are not called from outside (because they are not part of the \"public\" interface).\n", "* To protect data, we can make them properties.\n", "* Any differences in general and the way in which the methods and data are handled should be included in the class documentation.\n", "* There are ways you can enforce encapsulation (redefining the access to attributes, ...) but those are rarely used (and rarely rally useful).\n", "\n", "In return, Python offers a very high level of **introspection**, or the ability to learn information about objects (their type, attributes, etc.) at runtime.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The underscore convention\n", "\n", "In Python conventions are generally very strongly entrenched. It is perhaps the most visible in the context of objects.\n", "\n", "1. \"Private\" attributes (attributes in Python often means both data and methods - everything is an object) are named with an underscore at the beginning, e.g \\_private_method.\n", "2. Two underscores at the beginning of the name of an attribute renames it so it's really hard to reference the attribute outside the context of the class.\n", "3. Attributes with two undescores at the beginning and at the end have a special meaning (see [documentation](http://docs.python.org/2/reference/datamodel.html#special-method-names)). We have already seen __init__ and will look at several others.\n", " * `__repr__` and `__str__` convert the object to a string.\n", " * `__getattr__` and `__setattr__` are used for reading and storing not found attributes.\n", " * `__call__` will be called when we use the object as a function.\n", " * `__doc__` contains documentation (docstring).\n", " * `__dict__` contains the dictionary with the namespace of the object.\n", " * ... Furthermore, there are special features for logical operators, to emulate the functionality of containers (iteration, items, cuts), for arithmetic operations, etc.\n" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "['__class__',\n", " '__delattr__',\n", " '__dir__',\n", " '__doc__',\n", " '__eq__',\n", " '__format__',\n", " '__ge__',\n", " '__getattribute__',\n", " '__gt__',\n", " '__hash__',\n", " '__init__',\n", " '__le__',\n", " '__lt__',\n", " '__ne__',\n", " '__new__',\n", " '__reduce__',\n", " '__reduce_ex__',\n", " '__repr__',\n", " '__setattr__',\n", " '__sizeof__',\n", " '__str__',\n", " '__subclasshook__']" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# what an instance ob the object type contains?\n", "dir(object())" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "['__annotations__',\n", " '__call__',\n", " '__class__',\n", " '__closure__',\n", " '__code__',\n", " '__defaults__',\n", " '__delattr__',\n", " '__dict__',\n", " '__dir__',\n", " '__doc__',\n", " '__eq__',\n", " '__format__',\n", " '__ge__',\n", " '__get__',\n", " '__getattribute__',\n", " '__globals__',\n", " '__gt__',\n", " '__hash__',\n", " '__init__',\n", " '__kwdefaults__',\n", " '__le__',\n", " '__lt__',\n", " '__module__',\n", " '__name__',\n", " '__ne__',\n", " '__new__',\n", " '__qualname__',\n", " '__reduce__',\n", " '__reduce_ex__',\n", " '__repr__',\n", " '__setattr__',\n", " '__sizeof__',\n", " '__str__',\n", " '__subclasshook__']" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# and a simple function?\n", "def foo(x):\n", " \"\"\"Toto je funkce foo\"\"\"\n", " return x\n", "dir(foo)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inheritance \n", "\n", "Class can inherit (derive) its behavior (and data) from another class, thus saving a lot of work in the repetition of common features. In this case, we say that our new class (child or subclass) inherits from the original (parent) class.\n", "\n", "* In a subclass, we can change the definition of some of the methods of the superclass.\n", "* Constructors are inherited by default (unlike C++ or Java, in Python we have to explicitely call the superclass constructor only if we define a new constructor).\n", "* Subclasses can be used wherever the parent class(es) can be used. *This applies even more generally in Python as we usually do not check the specific type. Instead, we look for particular attributes / methods. This is easily possible because Python is dynamically typed.*\n", "\n", "**Syntax:** The name of the parent class is given in paretheses after the name (instead of `object` from which the class usually inherits)." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Serviceman: Hello!\n", "Patient: Hello!\n", "---Achoo---\n", "Serviceman: I'm Joe Smith.\n", "Patient: By dabe is Bill Jodes.\n", "---Achoo---\n", "Patient: Cad you fix by TV, please?\n", "---Achoo---\n", "Serviceman: Give me 5 minutes.\n", "---The serviceman is working.---\n", "Serviceman: Done.\n", "Patient: Thadk you very buch.\n", "---Achoo---\n", "Serviceman: Good bye!\n", "Patient: Good bye!\n", "---Achoo---\n" ] } ], "source": [ "class Human(object):\n", " def __init__(self, name): # The constructor sets the name\n", " self.name = name\n", " \n", " def say(self, what): # The default say method\n", " print(type(self).__name__ + \": \" + what)\n", " \n", " def introduce(self): \n", " self.say(\"My name is %s.\" % self.name)\n", " \n", " def greet(self): \n", " self.say(\"Hello!\")\n", " \n", " def goodbye(self):\n", " self.say(\"Good bye!\") \n", " \n", " \n", "class Serviceman(Human):\n", " def repair_tv(self): # A new method\n", " self.say(\"Give me 5 minutes.\")\n", " print(\"---The serviceman is working.---\")\n", " self.say(\"Done.\")\n", " \n", " def introduce(self): # introduce differently; self.name is used here\n", " self.say(\"I'm %s.\" % self.name)\n", " \n", "class Patient(Human):\n", " def say(self, what): # redefined method\n", " \"\"\"Say something with a running nose.\"\"\"\n", " trantab = \"\".maketrans(\"nmNM\", \"dbDB\")\n", " \n", " Human.say(self, what.translate(trantab)) # call parent class' method\n", " self.sneeze()\n", " \n", " def sneeze(self): # A new method - other humans do not sneeze\n", " print(\"---Achoo---\")\n", " \n", "joe = Serviceman(\"Joe Smith\")\n", "bill = Patient(\"Bill Jones\")\n", "\n", "# A daily conversation\n", "joe.greet()\n", "bill.greet()\n", "joe.introduce()\n", "bill.introduce()\n", "bill.say(\"Can you fix my TV, please?\")\n", "joe.repair_tv()\n", "bill.say(\"Thank you very much.\")\n", "joe.goodbye()\n", "bill.goodbye()" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "ename": "AttributeError", "evalue": "'Patient' object has no attribute 'repair_tv'", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mbill\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrepair_tv\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# Patients do not repair TV's\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mAttributeError\u001b[0m: 'Patient' object has no attribute 'repair_tv'" ] } ], "source": [ "bill.repair_tv() # Patients do not repair TV's" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A sick electrician could be created using multiple inheritance, in which case we would have to consider if parent methods are called properly. Even better, we could use so-called mix-ins and inject properties into objects dynamically, but this is really an advanced topic that we will not cover.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Inheriting from built-in types \n", "Classes can also inherit from built-in types (and it is often useful, although our example does not prove it)." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "What about pleading?\n", "None\n" ] } ], "source": [ "# A list that does not return its item unless pleaded\n", "class PeevishList(list):\n", " def __getitem__(self, index): # redefining the method that handles getting items by [...]\n", " if isinstance(index, tuple) and index[1].lower()[:6] in (\"please\"):\n", " return list.__getitem__(self, index[0]) # the parent's method\n", " else:\n", " print(\"What about pleading?\")\n", " return None\n", " \n", "s = PeevishList((1, 2, 3, 4))\n", "print(s[1])\n" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "print(s[1, \"please\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Advanced topics \n", "\n", "Following topics are very interesting and terribly useful but we do not have so much time to devote to them. However, we recommend reading about them, if you have a little time.\n", "\n", "* Multiple inheritance\n", "* Class methods\n", "* Static methods\n", "* Abstract classes\n", "* Polymorphism\n", "* Metatclasses\n", "* Design Patterns" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.3" } }, "nbformat": 4, "nbformat_minor": 0 }