Програмиране с Python: Обектно-ориентирано програмиране, част 1
Author: Стефан Кънев, Николай Бачийски, Димитър Димитров

# Обектно-орентирано програмиране, част 1

### „ Програмиране с Python“, ФМИ

#### Стефан Кънев && Николай Бачийски && Митьо Питона

#### 07.04.2008г.

# От източника

Actually I made up the term "object-oriented", and I can tell you I did not have C++ in mind.

— Alan Kay

# Абстракция

Абстракцията е процесът или резултатът на генерализация чрез намаляването на информационното съдържание на концепция или наблюдаемо явление, обикновено за да се остави само информацията, която е значима за конкретна цел.

o.O

# Иначе

Programs must be written for people to read, and only incidentally for machines to execute.

— Structure and Interpretation of Computer Programs,
Abelson & Sussman

# Arcane Magic

    _l1:
        mov eax,4
        mov ebx,1
        mov ecx,arr
        add ecx,esi
        mov edx,1
        int 80h

        inc esi
        cmp esi,12
        jl _l1

# По-прегледно...

    for (int i = 0; i < 12; i++) {
        printf(arr[i]);
    }

# Или иначе

    for (int i = 0; i < MONTHS_IN_YEAR; i++) {
        printf(monthNames[i]);
    }

# Обектно-ориентирано програмиране

Идеята на обектно-ориентираното програмиране е проста - разделяте програмата на абстрактни концепции, които си комуникират помежду си. Всяка такава концепия (обект) може да отговаря на съобщения. Всяко съобщение има име и приема параметри.

Този модел е много близък до начина, по който човешкия мозък работи.

# Обектно-ориентирано програмиране (2)

ООП модела ни позволява няколко красиви концепции:

- Абстракция
- Енкапсулация
- Разделяне (decoupling)

# По темата

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

— C.A.R. Hoare / Donald Knuth

# Python и Зен философията

    >>> import this
    The Zen of Python, by Tim Peters

    Beautiful is better than ugly.
    Explicit is better than implicit.
    Simple is better than complex.
    Complex is better than complicated.
    Flat is better than nested.
    Sparse is better than dense.
    Readability counts.
    Special cases aren't special enough to break the rules.
    Although practicality beats purity.
    Errors should never pass silently.
    Unless explicitly silenced.
    In the face of ambiguity, refuse the temptation to guess.
    There should be one-- and preferably only one --obvious way to do it.
    Although that way may not be obvious at first unless you're Dutch.
    Now is better than never.
    Although never is often better than *right* now.
    If the implementation is hard to explain, it's a bad idea.
    If the implementation is easy to explain, it may be a good idea.
    Namespaces are one honking great idea -- let's do more of those!

# Класовете, обектите и питоните

- Всичо е обект - дори функциите и модулите.
- Обектите и класовете са динамични - може да ги разширявате с полета и методи по време на изпълнение
- Има множествено наследяване.
- Има „нов стил“ класове и „стар“ класове.
- Може да предефинирате оператори.

# Основи на класовете (1)

- Дефинират се с `class`, последван от блок
- Дефинираните функции в този блок са методи на класа
- Първият им аргумент - `self` - reference към обекта, на който ги извиквате
- Дефинираните в този блок променливи са статични за класа

# Основи на класовете (2)

    class Person:
        """Represents a person."""
        people = 0

        def __init__(self, name):
            """Constructs a new person."""
            self.name = name
            Person.people += 1

        def sayHi(self):
            """Presents one's self."""
            print "Hello, I'm %s!" % self.name

    >>> mityo = Person("Mityo the Python")
    >>> mityo.sayHi()
    Hello, I'm Mityo the Python!
    >>> guido = Person("Guido")
    >>> guido.sayHi()
    Hello, I'm Guido!
    >>> print Person.people
    2

# Полета (1)

- Методите имат достъп до полетата на обекта със `self`
- Извън тялото на класа, имате достъп до полетата му през неговото име

    class Spam:
        def __init__(self, arg)
            self.stored = arg

    >>> spam = Spam(42)
    >>> print spam.stored
    42
    >>> spam.stored = 60
    >>> print spam.stored
    60
    >>> spam.foo = 10
    >>> print spam.foo
    10

# Полета (2)

По този начин може да използвате класовете като структури:

    class Student: pass

    >>> mityo = Student()
    >>> mityo.name = "Mityo the Python"
    >>> mityo.age = 22
    >>> mityo.facultyNumber = 42424

# Методи

- Всички методи вземат reference към обекта, на който са извикани като първи аргумент.
- Стандарт е този reference да се казва `self`.
- Можете да викате метода както с `обект.метод(аргументи)`, така и с `клас.метод(обект, аргументи)`

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

        def greet(self, somebody):
            print "Hello %s, I'm %s!" % (somebody, self.name)

    >>> mityo = Person("Mityo the Python")
    >>> mityo.greet('Stefan')
    Hello Stefan, I'm Mityo the Python!
    >>> Person.greet(mityo, 'Stefan')
    Hello Stefan, I'm Mityo the Python!

# Методи (2)

Интересен страничен (или не толкова страничен) ефект е следното:

    >>> person = Person("Mityo the Python")
    >>> greetSomeone = person.greet
    ...
    >>> greetSomeone("Stefan")
    Hello Stefan, I'm Mityo the Python!

Обаче:

    >>> greeter = Person.greet
    >>> greeter(mityo, "Stefan")

# Статични методи

При статичните методи положението е малко странно:

    class Person:
        people = []
        def register(name):
            Person.people.append(name)
            print len(Person.people), "people are registered now"
        register = staticmethod(register)

    >>> Person.register("Mityo the Python")
    1 people are registered now
    >>> Person.register("Pooh")
    2 people are registered now

# Класови методи

В Python има "класови" методи, които вземат класът на който са извикани като първи аргумент. Понякога е полезно при наследяване:

    class Something:
        def greet(cls, someone):
            print someone, "was greeted from", cls
        greet = classmethod(greet)

    >>> Something.greet("Mityo")
    Mityo was greeted from __main__.Something

# Конструкция

- За да създадете инстанция на клас, ползвате оператора `()` върху класа
- Конструктурът на класовете се кaзва `__init__` и взема новосъздадения обект като първи аргумент
- Има и алтернативен начин на създаване - `__new__`, но той работи само при класове от нов стил

    class Person:
        def __init__(self, name, age, location="Sofia")
            self.name = name
            self.age = age
            self.location = location

    >>> person = Person("Mityo the Python", 30, "Great Tarnovo")

# Наследяване

Проста хватка:

    class Something:
        def __init__(self, name): self.name = name
        def introduce(self): "This is something called", self.name

    class Vegetable(Something):
        def eat(self): print self, "was eaten"

    class Animal(Something):
        def notice(self): print "Look! This is a", self.name
        def introduce(self): print "This is an animal called", self.name

    >>> snake = Animal("python")
    >>> snake.introduce()
    This is an animal called python
    >>> snake.notice()
    Look! It's a python

# Методи и променливи на родителите

Стандартното `клас.метод` би трябвало да ви е достатъчно:

    class PopularPerson(Person):
        def greet(self, somebody):
            Person.greet(self, somebody)
            print "Do you want my autograph?"

    >>> pop = PopularPerson("Mityo the Python")
    >>> pop.greet("Dim")
    Hello Dim, I'm Mityo the Python!
    Do you want my autograph?

# Protected и private

- В Python няма protected и private променливи - всичко е достъпно отвън! Все пак има неписано правила.
- Имена от типа `_име` се смятат за protected
- Имена от типа `__име` се смятат за private
- Интерпретаторът променя имената от типа `__име` до `_клас__име`. Това се нарича name mangling и ефектът му е подобен на private в други езици

    >>> class Spam:
    ...     def __stuff(self): pass
    ...
    >>> dir(Spam)
    ['_Spam__stuff', '__doc__', '__module__']

# Protected и private (2)

    class Base:
        def __init__(self, name, age):
            self.__name = name
            self._age = age
        def reportBase(self):
            print "Base:", self.__name, self._age

    class Derived(Base):
        def __init__(self, name, age, derivedName):
            Base.__init__(self, name, age)
            self.__name = derivedName
            self._age = 33
        def reportDerived(self):
            print "Derived:", self.__name, self._age

    >>> derived = Derived("Mityo the Python", 30, "Mityo the Gun")
    >>> derived.reportBase()
    Base: Mityo the Python 33
    >>> derived.reportDerived()
    Derived: Mityo the Gun 33
    >>> derived._Base__name, ", ", derived._Derived__name
    Mityo the Python, Mityo the Gun

# Класове в нов стил

- Въведени в Python 2.2 за да добавят нови възможности в езика, без да чупят стар код
- Наследяват `object` или клас, който наследява `object` - `list`, `dict`, `tuple`, `str` и т.н.
- Другите класове се наричат "класически"
- Променят малко множественото наследяване.
- Дават ви нов начин за конструкция - `__new__`.
- Можете да ограничавате полетата им със `__slots__`.

# Класове в нов стил — __new__

    class SpamTuple(tuple):
        def __new__(cls, number = 1):
            result = ("Spam",) * number
            return tuple.__new__(cls, result)

        def countSpam(self):
            return len(self)

    spams = SpamTuple(3)
    print spams             # ('Spam', 'Spam', 'Spam')
    print spams.countSpam() # 3

# Класове в нов стил - __slots__

    class Something(object):
        __slots__ = ['spam', 'eggs']

    >>> smth = Something()
    >>> smth.spam = 1
    >>> smth.eggs = 2
    >>> smth.foo = 3

    Traceback (most recent call last):
      File "<stdin>", line 5, in <module>
    AttributeError: 'Something' object has no attribute 'foo'