04. ООП

04. ООП

04. ООП

14 март 2016

Ко да се…

panda = 'shi shi'

def say_hi():
    platypus = 'perry'
    print('Hi, {}'.format(platypus))
    print('Hi, {}'.format(panda))

say_hi()
print("Oh, you're still here, {}?".format(platypus))

Hi, perry

Hi, shi shi

NameError: name 'platypus' is not defined

Ко каза…

>>> def closure(initial):
...    def increment_by(x):
...        return initial + x
...    def decrement_by(x):
...        return initial - x
...    return increment_by, decrement_by

>>> inc, dec = closure(6)
>>> inc(3)
>>> dec(7)

9

3

Ko?!

def snitch(init_message):
    def decorator(f):
      print(init_message)
      def snitched(*args, **kwargs):
          print('Called with {}, {}'.format(args, kwargs))
          result = f(*args, **kwargs)
          print('Returned {}'.format(result))
          return result
      return snitched
    return decorator

@snitch('Initializing baba')
def baba(a, b):
    return a / b

baba(4, 2)

OOP

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

От идейна гледна точка

Разбирайте „запрограмирането генерално“

  1. Абстракция
  2. Енкапсулация
  3. Модулярност

Абстракция

Най-общо казано(демек, според википедия):

Abstraction in its main sense is a conceptual process by which general rules and concepts are derived from the usage and classification of specific examples, literal ("real" or "concrete") signifiers, first principles, or other methods. "An abstraction" is the product of this process—a concept that acts as a super-categorical noun for all subordinate concepts, and connects any related concepts as a group, field, or category.

Абстракция

А по-конкретно за областта, която ни интересува

[…] a technique for managing complexity of computer systems. It works by establishing a level of complexity on which a person interacts with the system, suppressing the more complex details below the current level. The programmer works with an idealized interface (usually well defined) and can add additional levels of functionality that would otherwise be too complex to handle.

Абстракция

Енкапсулация

Encapsulation is the packing of data and functions into a single component.

Има много механизми, с които можем да го реализираме, но обектно ориентираното програмиране е един от най-популярните

Information hiding

Abstraction and encapsulation are complementary concepts: abstraction focuses on the observable behavior of an object... encapsulation focuses upon the implementation that gives rise to this behavior... encapsulation is most often achieved through information hiding, which is the process of hiding all of the secrets of object that do not contribute to its essential characteristics.

Модулярност

Механизъм за организиране на сходна логика работеща върху свързани видове данни, обработваща сходни видове процеси от моделирания свят в добре обособени и ясно разделени парчета от кода ни

По същество

Разбирайте „конкретно за python“

  1. Всичко е обект
  2. Обектите са отворени
  3. Класовете са отворение

Последните две с някои малки уговорки, които обаче рядко ще ви интересуват

Всичко е обект

Съвсем буквално

>>> type(42)
<class 'int'>
>>> type([])
<class 'list'>
>>> type([]) is list
True
>>> type(list)
<class 'type'>
>>> type(list) is type
True

Тогава…

>>> type(type)

???

<class 'type'>

Класове

Създаваме класове с ключовата дума class, след което всяка функция дефинирана в тялото на класа е метод, а всяка променлива е клас променлива

Примерен клас Vector

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

spam = Vector(1.0, 1.0)
print(spam.x)
  1. „Конструктура“ се казва __init__, той не връща стойност
  2. Първия аргумент на методите винаги е инстанцията, върху която се извикват, той може да се казва всякак, но винаги се казва self , иначе губите огромни количества точки/колегите ви ви мразят/никой не иска да си играе с вас в пясъчника
  3. Атрибутите („член-променливите“/„член-данните“) не се нуждаят от декларации (обектите са отворени)
  4. Инстанцираме клас, като го „извикаме“ със съответните аргументи, които очаква __init__ метода му и като резултат получаваме новоконстурирания обект

Забележки

„Конструктор“ е думата, с която сте свикнали, но в случая далеч по-подходяща е „инициализатор“, както си личи от името

Въпреки, че при инстанциране на обект подаваме аргументите, които очаква инциализатора, той не е първия метод, който се извиква в този момент, но за сега не влагайте много мисъл в това, просто го имайте предвид

Примерен клас Vector (2)

import math


class Vector:
    def __init__(self, x, y): ...

    def length(self):
        return math.sqrt(self.x**2 + self.y**2)

    spam = Vector(1.0, 2.0)
    print(spam.length())
  1. В методите атрибутите могат да се достъпват само през self , няма никакви магически имплицитни scope-ове
  2. Методите се извикват през инстанцирания обект обект.име_на_метод()

Примерен клас Vector (3)

class Vector:
    def __init__(self, x, y, z): ...

    def _coords(self):
        return (self.x, self.y, self.z)

    def length(self):
        return sum(_ ** 2 for _ in self._coords()) ** 0.5
  1. _coords е protected метод
  2. Отново, методите се извикват върху self
  3. _ е валидно име за променлива

Private/protected

Казахме, че класовете са отворени. Това ще рече, че private и protected концепциите не са това, за което сте свикнали да мислите в езици като C++/Java/C#

Ограниченията за използване на защитени и частни методи в класовете в python е отговорност на програмиста, което по никакъв начин не прави живота ви по-труден

Методи/атрибути започващи с _ са защитени, т.е. би следвало да се ползват само от методи на класа и наследяващи го класове

Методи/атрибути започващи с __ са частни, т.е. би следвало да се ползват само от методи на класа

Достатъчно очевидно е, а някои много редки случаи може да се наложи тези ограничения да не се спазят

unbound methods

v1 = Vector(1.0, 2.0, 3.0)
v2 = Vector(4.0, 5.0, 6.0)
v3 = Vector(7.0, 8.0, 9.0)

print(Vector.length(v1))
print(Vector.length(v2))

Което може да бъде полезно за следното

print(list(map(Vector.length, [v1, v2, v3])))

Състояние

Mutable vs. Immutable

Най-общо повечето обекти в python са mutable, до колкото езика не ни забранява да ги променяме

Какво в python знаем, че е immutable?

mutating method

class Vector:
    def __init__(self, x, y, z): ...

    def length(self): ...

    def normalize(self):
        length = self.length()
        self.x /= length
        self.y /= length
        self.z /= length

non-mutating method

class Vector:
    def __init__(self, x, y, z): ...

    def length(self): ...

    def normalized(self):
        length = self.length()
        return Vector(self.x / length,
                      self.y / length, self.z / length)

normalize vs normalized

class Vector:
    ...

    def normalize(self):
        length = self.length()
        self.x /= length
        self.y /= length
        self.z /= length

    def normalized(self):
        return Vector(self.x / self.length(), self.y / self.length(),
                      self.z / self.length())

Сравняване на обекти

dunder methods

Известни още като "magic methods", dunder(doule under) методите в python най-често предефинират някакъв аспект от поведението на обектите ни

Аритметични оператори

Преобразуване до стандартни типове

Обекти, които могат да бъдат извиквани като функции

Можете да дефинирате поведение на обектите си, когато биват извиквани като функции

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

  def __call__(self, something):
      print("{0} was stamped by {1}".format(something, self.name))

>>> stamp = Stamp("The government")
>>> stamp("That thing there")
That thing there was stamped by The government

getattr/setattr

Можем да достъпваме/променяме атрибути на обект динамично

>>> v1 = Vector(1, 1, 1)
>>> getattr(v1, 'y')
1
>>> setattr(v1, 'z', 5)
>>> getattr(v1, 'z')
5

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

Можете да дефинирате методи, така че те да са статични -- т.е. да нямат нужда от инстанция

class GoatSimulator:
    goats = []

    @staticmethod
    def register(name):
        GoatSimulator.goats.append(name)
        print(len(GoatSimulator.goats), " goats are registered now")

>>> GoatSimulator.register("Pip the Happy Goat")
1 goats are registered now
>>> GoatSimulator.register("George the Gutsy Goat")
2 goats are registered now

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

Можете да използвате и @classmethod, за да получите класа от който е извикан метода като първи аргумент

class Countable:
    _count = 0

    def __init__(self, data):
        self.data = data
        type(self).increase_count()

    @classmethod
    def increase_count(cls):
        cls._count += 1

    @classmethod
    def decrease_count(cls):
        cls._count -= 1

"Деструктор"

Когато обект от даден тип бъде събран от garbage collector-а, се извиква неговия __del__ метод.

Той не прави това, което прави деструктора в C++, а неща от по-високо ниво, например затваря мрежови връзки или файлове, изпраща съобщения до някъде, че нещо е приключило и прочее.

class Countable:
    _count = 0

    def __init__(self, data):
        self.data = data
        type(self).increase_count()

    @classmethod
    def increase_count(cls):
        cls._count += 1

    @classmethod
    def decrease_count(cls):
        cls._count -= 1

    def __del__(self):
        type(self).decrease_count()

Property методи

Декораторът @property може се използва за да накарате някой метод да се преструва на property

class Goat:
    def __init__(self, name, trait):
        self.name = name
        self.trait = trait

    @property
    def description(self):
        return "{} the {} goat".format(self.name, self.trait)

>>> g = Goat("George", "Gutsy")
>>> g.description
'George the Gutsy goat'

Property методи (2)

Property методи (3)

class Color:
    def __init__(self, rgba):
        self._rgba = tuple(rgba)

    @property
    def rgba(self):
        return self._rgba

    @rgba.setter
    def rgba(self, value):
        self._rgba = tuple(value)

>>> red = Color([255,0,0])
>>> red.rgba
(255, 0, 0)
>>> red.rgba = [127,0,0]
>>> red.rgba
(127, 0, 0)

Vector (6)

def addition(a, b):
    return Vector(a.x + b.x, a.y + b.y, a.z + b.z)

class Vector:
    def __init__(self, x, y, z): ...

    __add__ = addition

print(Vector(1.0, 2.0, 3.0) + Vector(4.0, 5.0, 6.0))
  1. Функциите са първокласни обекти
  2. Методите са атрибути на класа
  3. Класовете са динамични
  4. Ето защо self е явен

ООП-то в python е голямо, сложно, страшно елегантно и изключително интересно

Въпроси?