class: center, middle .center[![Alt text](http://m1.paperblog.com/i/201/2016454/guia-python-conceptos-programacion-atributos--L-DTucOw.png)] # Python Course: Object Oriented Shahid Beheshti University Instructor: S. M. Masoud Sadrnezhaad --- # Why should we even use classes - They allow us to logically group our **data and functions** in a way that's easy to **reuse** and also easy to build upon if need be. - Data and functions that are associated with a specific class we call those **attributes** and **methods**. - So method is a function that is associated with a class. - Example: We had an application for our company and we wanted to represent our employees and our Python code. - each individual employee is going to have specific attributes and methods - each employee is going to have a **name** and **email address** a **pay** and also **actions** that they can perform. - It'd be nice if we had a class that we could use as a blueprint to create each employee so that we didn't have to do this manually each time from scratch. --- A First Look at Classes ======================= - Classes introduce a little bit of **new syntax**, three new object types, and some new semantics. Class Definition Syntax ----------------------- - The simplest form of class definition looks like this: ```python class ClassName:
. . .
``` - Class definitions, like function definitions (`def` statements) must be **executed before** they have any effect. - (You could conceivably place a class definition in a branch of an `if` statement, or inside a function.) --- Class Definition Syntax (Contd) ======================= - In practice, the statements **inside a class** definition will usually be **function definitions**, but other statements are allowed, and sometimes useful --- we'll come back to this later. - The **function definitions** inside a class normally have a peculiar form of argument list, dictated by the calling conventions for **methods** --- again, this is explained later. - When a class definition is entered, **a new namespace** is created, and used as the local scope --- thus, all **assignments to local variables** go into **this new namespace**. - In particular, function definitions **bind the name of the new function here**. - When a class definition is left normally (via the end), a ***class object*** is created. - This is basically a **wrapper** around the contents of the namespace created by the class definition. - The original local scope (the one in effect just before the class definition was entered) is reinstated, and the **class object** is **bound** here to the **class name** given in the class definition header (`ClassName` in the example). --- Class Objects ======================= - Class objects support two kinds of operations: **attribute references** and **instantiation**. - ***Attribute references*** use the standard syntax used for all attribute references in Python: `obj.name`. - Valid attribute names are all the **names that were in the class's namespace** when the class object was created. - So, if the class definition looked like this: ```python class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world' ``` - then `MyClass.i` and `MyClass.f` are **valid attribute references**, returning an integer and a function object, respectively. --- Class Objects (Contd) ======================= ```python class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world' ``` - **Class attributes** can also be assigned to, so you can change the value of `MyClass.i` by **assignment**. - `__doc__` is also a valid attribute, returning the **docstring** belonging to the class: `"A simple example class"`. --- Class Objects (Contd) ======================= - Class *instantiation* uses function notation. - Just pretend that the class object is a **parameterless function** that **returns a new instance** of the class. - For example (assuming the above class): ```python x = MyClass() ``` - Creates a **new *instance*** of the class and assigns this object to the local variable `x`. - The instantiation operation ("calling" a class object) **creates an empty object**. - Many classes like to create objects with instances customized to a specific **initial state**. Therefore a class may define **a special method** named `__init__`, like this: ```python def __init__(self): self.data = [] ``` --- Class Objects (Contd) ======================= - When a class defines an `__init__` method, class instantiation **automatically invokes `__init__`** for the **newly-created class instance**. - Of course, the `__init__` method may have **arguments** for greater flexibility. - In that case, arguments given to the class instantiation operator are **passed on to `__init__`**. - For example, : ```python >>> class Complex: ... - def __init__(self, realpart, imagpart): ... - self.r = realpart ... - self.i = imagpart ... >>> x = Complex(3.0, -4.5) >>> x.r, x.i (3.0, -4.5) ``` --- Instance Objects ======================= - Now what can we do with instance objects? The only operations understood by instance objects are **attribute references**. - There are two kinds of valid attribute names, **data attributes** and **methods**. - ***data attributes*** correspond to "instance variables" in Smalltalk, and to "data members" in C++. - **Data attributes** need **not be declared**; like **local variables**, they spring into existence when they are **first assigned to**. - For example, if `x` is the instance of `MyClass` created above, the following piece of code will print the value `16`, without leaving a trace: ```python x.counter = 1 while x.counter < 10: x.counter = x.counter * 2 print(x.counter) ``` --- Instance Objects (Contd) ======================= - The other kind of **instance attribute reference** is a ***method***. - A method is a **function** that **"belongs to" an object**. - Valid method names of an instance object **depend on its class**. - By definition, all **attributes of a class** that are **function objects** define corresponding methods of its instances. - So in our example, **`x.f` is a valid method reference**, since **`MyClass.f` is a function**, but `x.i` is not, since `MyClass.i` is not. - But `x.f` is not the same thing as `MyClass.f` --- it is a ***method object***, not a function object. --- Method Objects ======================= - Usually, a method is called right after it is bound: ```python x.f() ``` - In the `MyClass` example, this will return the string `'hello world'`. - However, it is not necessary to call a method right away: **`x.f` is a method object**, and can be **stored away** and **called at a later time**. - For example: ```python xf = x.f while True: print(xf()) ``` - will continue to print `hello world` until the end of time. - What exactly happens when a method is called? You may have noticed that `x.f()` was called **without an argument above**, **even though** the **function definition** for `f` **specified an argument**. --- Method Objects (Contd) ======================= - What happened to the argument? - Surely Python **raises an exception** when a function that requires an argument is **called without any** --- even if the argument isn't actually used... - Actually, you may have guessed the answer: - the special thing about methods is that the **instance object** is passed **as the first argument of the function**. - In our example, the call `x.f()` is exactly equivalent to `MyClass.f(x)`. - In general, calling a method with a list of *n* arguments **is equivalent** to calling the **corresponding function** with an argument list that is created by **inserting the method's instance object** before the first argument. --- Method Objects (Contd) ======================= - If you still don't understand how methods work, a **look at the implementation** can perhaps clarify matters. - When an instance attribute is referenced that isn't a data attribute, **its class is searched**. - If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. - When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list. --- Class and Instance Variables ======================= - Generally speaking, **instance variables** are for data **unique to each instance** and **class variables** are for attributes and methods **shared by all instances** of the class: ```python class Dog: kind = 'canine' # class variable shared by all instances def __init__(self, name): self.name = name # instance variable unique to each instance >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.kind # shared by all dogs 'canine' >>> e.kind # shared by all dogs 'canine' >>> d.name # unique to d 'Fido' >>> e.name # unique to e 'Buddy' ``` --- Class and Instance Variables (Contd) ======================= - **Shared data** can have possibly **surprising effects** with involving `mutable` objects such as lists and dictionaries. - For example, the *tricks* **list** in the following code should **not be used** as a **class variable** because just a single list would be shared by all *Dog* instances: ```python class Dog: tricks = [] # mistaken use of a class variable def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks # unexpectedly shared by all dogs ['roll over', 'play dead'] ``` --- Class and Instance Variables (Contd) ======================= - Correct design of the class should use **an instance variable instead**: ```python class Dog: def __init__(self, name): self.name = name self.tricks = [] # creates a new empty list for each dog def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks ['roll over'] >>> e.tricks ['play dead'] ``` --- Random Remarks ============== .. These should perhaps be placed **more carefully**... - **Data attributes** override **method attributes** with the **same name**; to avoid accidental **name conflicts**, which may cause **hard-to-find bugs** in large programs, it is wise to use some **kind of convention** that minimizes the chance of conflicts. - Possible **conventions** include **capitalizing method names**, **prefixing data attribute names** with a small unique string (perhaps just **an underscore**), or using **verbs for methods** and **nouns for data attributes**. --- Random Remarks (Contd) ============== - **Data attributes** may be **referenced by methods** as well as by ordinary users ("clients") of an object. - In other words, **classes** are **not usable** to implement pure **abstract data types**. - In fact, **nothing in Python** makes it possible to enforce **data hiding** --- it is all **based upon convention**. - (On the other hand, the **Python implementation, written in C**, can completely **hide** implementation details and control access to an object if necessary; this can be used by **extensions to Python written in C**.) --- Random Remarks (Contd) ============== - **Clients** should **use data attributes with care** --- clients may mess up invariants maintained by the methods by **stamping on their data attributes**. - Note that clients may **add data attributes** of their own to an instance object **without affecting the validity** of the methods, as long as **name conflicts are avoided** --- again, a **naming convention** can save a lot of headaches here. - Often, the **first argument of a method** is called `self`. - This is **nothing more than a convention**: the name `self` has absolutely **no special meaning to Python**. - Note, however, that **by not following the convention** your code may be **less readable** to other Python programmers, and it is also conceivable that a ***class browser* program** might be written that **relies** upon such a convention. --- Random Remarks (Contd) ============== - Any **function object** that is a **class attribute** defines a **method for instances** of that class. - It is **not necessary** that the **function definition** is textually **enclosed in the class definition**: assigning a function object to a local variable in the class is also ok. ```python # Function defined outside the class def f1(self, x, y): return min(x, x+y) class C: f = f1 def g(self): return 'hello world' h = g ``` - Now `f`, `g` and `h` are all **attributes of class `C`** that refer to **function objects**, and consequently they are all **methods of instances of `C` --- `h`** being exactly equivalent to `g`. Note that this practice usually only serves to **confuse** the reader of a program. --- Random Remarks (Contd) ============== - Methods may call other methods by using **method attributes of the `self` argument**: ```python class Bag: def __init__(self): self.data = [] def add(self, x): self.data.append(x) def addtwice(self, x): self.add(x) self.add(x) ``` - Methods may reference **global names** in the **same way as ordinary functions**. - The **global scope associated with a method** is the **module containing its definition**. - A class is **never** used as a **global** scope. --- Random Remarks (Contd) ============== - While one rarely encounters a good reason for using global data in a method, there are many **legitimate uses of the global scope**: for one thing, **functions and modules imported** into the **global** scope can be **used by methods**, as well as functions and classes defined in it. - Usually, the class containing the method is itself defined in this global scope, and in the next section we'll find some good reasons why a method would want to reference its own class. - **Each value is an object**, and therefore **has a *class*** (also called its *type*). It is stored as `object.__class__`. --- Inheritance =========== - Of course, a language feature would not be worthy of the name "class" without **supporting inheritance**. - The syntax for a derived class definition looks like this: ```python class DerivedClassName(BaseClassName):
. . .
``` - The name `BaseClassName` must be defined **in a scope containing the derived class definition**. - In place of a base class name, **other arbitrary expressions** are also allowed. - This can be useful, for example, when the **base class is defined in another module**: ```python class DerivedClassName(modname.BaseClassName): ``` --- Inheritance =========== - Execution of a derived class definition proceeds the same as for a base class. When the class object is constructed, the base class is remembered. - This is used for resolving attribute references: if a requested attribute is not found in the class, the search proceeds to look in the base class. - This rule is applied recursively if the base class itself is derived from some other class. - There's nothing special about instantiation of derived classes: `DerivedClassName()` creates a new instance of the class. - Method references are resolved as follows: the corresponding class attribute is searched, descending down the chain of base classes if necessary, and the method reference is valid if this yields a function object. - Derived classes may override methods of their base classes. - Because methods have no special privileges when calling other methods of the same object, a method of a base class that calls another method defined in the same base class may end up calling a method of a derived class that overrides it. --- Inheritance (Contd) =========== - (For C++ programmers: all methods in Python are effectively `virtual`.) - An overriding method in a derived class may in fact want to extend rather than simply replace the base class method of the same name. There is a simple way to call the base class method directly: just call `BaseClassName.methodname(self, arguments)`. - This is occasionally useful to clients as well. - (Note that this only works if the base class is accessible as `BaseClassName` in the global scope.) - Python has two built-in functions that work with inheritance: * Use `isinstance` to check an instance's type: `isinstance(obj, int)` will be `True` only if `obj.__class__` is `int` or some class derived from `int`. * Use `issubclass` to check class inheritance: `issubclass(bool, int)` is `True` since `bool` is a subclass of `int`. - However, `issubclass(float, int)` is `False` since `float` is not a subclass of `int`. --- Multiple Inheritance =========== - Python **supports** a form of **multiple inheritance** as well. - A class definition with **multiple base classes** looks like this: ```python class DerivedClassName(Base1, Base2, Base3):
. . .
``` - For most purposes, in the simplest cases, you can think of the **search** for attributes inherited from a parent class as **depth-first**, **left-to-right**, **not searching twice in the same class** where there is an **overlap in the hierarchy**. - Thus, if an attribute is **not found in `DerivedClassName`**, it is searched for **in `Base1`**, then (recursively) in the **base classes of `Base1`**, and **if it was not found there**, it was **searched for in `Base2`**, **and so on**. --- Multiple Inheritance (Contd) =========== - In fact, it is slightly **more complex** than that; the method resolution order changes dynamically to support cooperative calls to `super`. - This approach is known in some other multiple-inheritance languages as call-next-method and is more powerful than the super call found in single-inheritance languages. - Dynamic ordering is necessary because all cases of multiple inheritance exhibit one or more diamond relationships (where at least one of the parent classes can be accessed through multiple paths from the bottommost class). - For example, all classes inherit from `object`, so any case of multiple inheritance provides more than one path to reach `object`. - To keep the base classes from being accessed more than once, the dynamic algorithm linearizes the search order in a way that preserves the left-to-right ordering specified in each class, that calls each parent only once, and that is monotonic (meaning that a class can be subclassed without affecting the precedence order of its parents). Taken together, these properties make it possible to design reliable and extensible classes with multiple inheritance. - For more detail, see https://www.python.org/download/releases/2.3/mro/. --- Private Variables ================= - "Private" instance variables that cannot be accessed except from inside an object don't exist in Python. - However, there is a convention that is followed by most Python code: a name prefixed with an underscore (e.g. `_spam`) should be treated as a non-public part of the API (whether it is a function, a method or a data member). - It should be considered an implementation detail and subject to change without notice. - Since there is a valid use-case for class-private members (namely to avoid name clashes of names with names defined by subclasses), there is limited support for such a mechanism, called `name mangling`. - Any identifier of the form `__spam` (at least two leading underscores, at most one trailing underscore) is textually replaced with `_classname__spam`, where `classname` is the current class name with leading underscore(s) stripped. - This mangling is done without regard to the syntactic position of the identifier, as long as it occurs within the definition of a class. --- Private Variables (Contd) ================= Name mangling is helpful for letting subclasses override methods without breaking intraclass method calls. - For example: ```python class Mapping: def __init__(self, iterable): self.items_list = [] self.__update(iterable) def update(self, iterable): for item in iterable: self.items_list.append(item) __update = update # private copy of original update() method class MappingSubclass(Mapping): def update(self, keys, values): # provides new signature for update() # but does not break __init__() for item in zip(keys, values): self.items_list.append(item) ``` --- Private Variables (Contd) ================= Note that the mangling rules are designed mostly to avoid accidents; it still is possible to access or modify a variable that is considered private. - This can even be useful in special circumstances, such as in the debugger. - Notice that code passed to `exec()` or `eval()` does not consider the classname of the invoking class to be the current class; this is similar to the effect of the `global` statement, the effect of which is likewise restricted to code that is byte-compiled together. - The same restriction applies to `getattr()`, `setattr()` and `delattr()`, as well as when referencing `__dict__` directly. --- Odds and Ends ============= - Sometimes it is useful to have a **data type** similar to the Pascal "record" or C "struct", **bundling together a few named data items**. - An **empty class definition** will do nicely: ```python class Employee: pass john = Employee() # Create an empty employee record # Fill the fields of the record john.name = 'John Doe' john.dept = 'computer lab' john.salary = 1000 ``` --- Odds and Ends (Contd) ============= - A piece of Python code that expects **a particular abstract data type** can often be **passed a class** that emulates the methods of that data type instead. - For instance, if you have a function that formats some data from a file object, you can define a class with methods `read` and `readline` that get the data from a string buffer instead, and pass it as an argument. - (Unfortunately, this technique has its limitations: a class can't define operations that are accessed by special syntax such as sequence subscripting or arithmetic operators, and assigning such a "pseudo-file" to sys.stdin will not cause the interpreter to read further input from it.) - Instance method objects have attributes, too: `m.__self__` is the instance object with the method `m`, and `m.__func__` is the function object corresponding to the method. --- Iterators ========= By now you have probably noticed that most container objects can be looped over using a `for` statement: ```python for element in [1, 2, 3]: print(element) for element in (1, 2, 3): print(element) for key in {'one':1, 'two':2}: print(key) for char in "123": print(char) for line in open("myfile.txt"): print(line, end='') ``` - This style of access is clear, concise, and convenient. - The use of iterators pervades and unifies Python. - Behind the scenes, the `for` statement calls `iter` on the container object. - The function returns an iterator object that defines the method `~iterator.__next__` which accesses elements in the container one at a time. --- Iterators (Contd) ========= - When there are no more elements, `~iterator.__next__` raises a `StopIteration` exception which tells the `for` loop to terminate. - You can call the `~iterator.__next__` method using the `next` built-in function; this example shows how it all works: ```python >>> s = 'abc' >>> it = iter(s) >>> it
>>> next(it) 'a' >>> next(it) 'b' >>> next(it) 'c' >>> next(it) Traceback (most recent call last): File "
", line 1, in
next(it) StopIteration ``` --- Iterators (Contd) ========= - Having seen the mechanics behind the iterator protocol, it is easy to add iterator behavior to your classes. - Define an `__iter__` method which returns an object with a `~iterator.__next__` method. - If the class defines `__next__`, then `__iter__` can just return `self`: ```python class Reverse: """Iterator for looping over a sequence backwards.""" def __init__(self, data): self.data = data self.index = len(data) def __iter__(self): return self def __next__(self): if self.index == 0: raise StopIteration self.index = self.index - 1 return self.data[self.index] ``` --- Iterators (Contd) ========= ```python >>> rev = Reverse('spam') >>> iter(rev) <__main__.Reverse object at 0x00A1DB50> >>> for char in rev: ... print(char) ... m a p s ``` --- Generators ========== - `Generator`\s are a simple and powerful tool for creating iterators. - They are written like regular functions but use the `yield` statement whenever they want to return data. - Each time `next` is called on it, the generator resumes where it left off (it remembers all the data values and which statement was last executed). --- Generators (Contd) ========== - An example shows that generators can be trivially easy to create: ```python def reverse(data): for index in range(len(data)-1, -1, -1): yield data[index] ``` : ```python >>> for char in reverse('golf'): ... - print(char) ... f l o g ``` - Anything that can be done with generators can also be done with class-based iterators as described in the previous section. - What makes generators so compact is that the `__iter__` and `~generator.__next__` methods are created automatically. --- Generators (Contd) ========== - Another key feature is that the local variables and execution state are automatically saved between calls. - This made the function easier to write and much more clear than an approach using instance variables like `self.index` and `self.data`. - In addition to automatic method creation and saving program state, when generators terminate, they automatically raise `StopIteration`. In combination, these features make it easy to create iterators with no more effort than writing a regular function. --- Generator Expressions ===================== - Some simple generators can be coded succinctly as expressions using a syntax similar to list comprehensions but with parentheses instead of square brackets. These expressions are designed for situations where the generator is used right away by an enclosing function. - Generator expressions are more compact but less versatile than full generator definitions and tend to be more memory friendly than equivalent list comprehensions. --- Generator Expressions (Contd) ===================== - Examples: ```python >>> sum(i*i for i in range(10)) # sum of squares 285 >>> xvec = [10, 20, 30] >>> yvec = [7, 5, 3] >>> sum(x*y for x,y in zip(xvec, yvec)) # dot product 260 >>> unique_words = set(word for line in page for word in line.split()) >>> valedictorian = max((student.gpa, student.name) for student in graduates) >>> data = 'golf' >>> list(data[i] for i in range(len(data)-1, -1, -1)) ['f', 'l', 'o', 'g'] ``` --- # Underscores in Python I discusse the use of the `_` character in Python. Like with many things in Python, we’ll see that different usages of `_` are mostly (not always!) a matter of convention. - Single Lone Underscore (`_`) - Single Underscore Before a Name (e.g. `_shahriar`) - Double Underscore Before a Name (e.g. `__shahriar`) - Double Underscore Before and After a Name (e.g. `__init__`) --- # Single Lone Underscore This is typically used in 3 cases: - **In the interpreter:** The `_` name points to the **result of the last executed statement** in an interactive interpreter session. ```python >>> _ Traceback (most recent call last): File "
", line 1, in
NameError: name '_' is not defined >>> 42 >>> _ 42 >>> 'alright!' if _ else ':(' 'alright!' >>> _ 'alright!' ``` --- # Single Lone Underscore (Contd) This is typically used in 3 cases: - **As a name:** `_` is used as a **throw-away name**. This will allow the next person reading your code to know that, by convention, a certain name is **assigned but not intended to be used**. For instance, you may not be interested in the actual value of a loop counter: For instance, you may not be interested in the actual value of a loop counter: ```python n = 42 for _ in range(n): do_something() ``` --- # Single Lone Underscore (Contd) This is typically used in 3 cases: - **i18n:** One may also see `_` being used as a function. In that case, it is often the name used for the function that does **internationalisation and localisation string translation** lookups. For instance, as seen in the Django documentation for translation, you may have: ```python from django.utils.translation import ugettext as _ from django.http import HttpResponse def my_view(request): output = _("Welcome to my site.") return HttpResponse(output) ``` The second and third purposes can conflict, so one should avoid using `_` as a throw-away name in any code block that also uses it for i18n lookup and translation. --- # Single Underscore Before a Name A single underscore before a name is used to specify that the name is to be **treated as “private” by a programmer**. It’s **kind of* a convention** so that the next person (or yourself) using your code knows that a name starting with `_` is for internal use. As the Python documentation notes: > a name prefixed with an underscore (e.g. `_spam`) should be treated as a non-public part of the API (whether it is a function, a method or a data member). It should be considered an implementation detail and subject to change without no I say **kind of a convention** because it actually does mean something to the interpreter; if you `from
import *`, none of the names that start with an `_` will be imported unless the module’s/package’s `__all__` list explicitly contains them. See “Importing * in Python” for more on this. --- # Double Underscore Before a Name The use of double underscore `(__)` in front of a name (specifically a method name) is **not a convention**; it has a specific meaning to the interpreter. Python mangles these names and it is used to **avoid name clashes with names defined by subclasses**. As the Python documentation notes, any identifier of the form `__spam` (at least two leading underscores, at most one trailing underscore) is textually replaced with `_classname__spam`, where classname is the current class name with leading underscore(s) stripped. --- # Double Underscore Before a Name (Contd) Take the following example: ```python >>> class A(object): ... def _internal_use(self): ... pass ... def __method_name(self): ... pass ... >>> dir(A()) ['_A__method_name', ..., '_internal_use'] ``` As expected, `_internal_use` doesn’t change but `__method_name` is mangled to `_ClassName__method_name`. Now, if you create a subclass of A, say B then you can’t easily override A‘s `__method_name`: ```python >>> class B(A): ... def __method_name(self): ... pass ... >>> dir(B()) ['_A__method_name', '_B__method_name', ..., '_internal_use'] ``` --- # Double Underscore Before and After a Name These are **special method names** used by Python. As far as one’s concerned, this is just a convention, a way for the Python system to use names that won’t conflict with **user-defined names**. You then typically override these methods and define the desired behaviour for when Python calls them. For example, you often override the `__init__` method when writing a class. There is nothing to stop you from writing your own special-method-looking name (but, please don’t): ```python >>> class C(object): ... def __mine__(self): ... pass ... >>> dir(C) ... [..., '__mine__', ...] ``` It’s easier to stay away from this type of naming and let only Python-defined special names follow this convention. --- # References - Corey M. Schafer Youtube Course. - https://shahriar.svbtle.com/underscores-in-python - https://docs.python.org/3/tutorial/classes.html --- class: center, middle .center[![Alt text](http://m1.paperblog.com/i/201/2016454/guia-python-conceptos-programacion-atributos--L-DTucOw.png)] # Thank you. Any questions?