03. ООП

03. ООП

03. ООП

16 март 2015

Въпрос 1

elements = (1, 'larodi', 42),
for element in elements:
  print(element)

(1, 'larodi', 42)

Въпрос 2

{x+y for x in range(3) for y in range(3)}

{0, 1, 2, 3, 4}

Въпрос 3

keys = [1, 2, 3]
keys.append([4, 5, 6])
{key: "Panda" for key in keys}

TypeError: unhashable type: 'list'

Въпрос 4

len(x ** 2 for x in range(5))

TypeError: object of type 'generator' has no len()

Въпрос 5

{1, 2, 3, 3, 3, 4, 5}.count(3)

AttributeError: 'set' object has no attribute 'count'

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(double 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()

Въпроси?