ООП. Композиція. Паттерн Стратегія.

Основи Python и Django. -> Паттерн Стратегія.

Паттерн Стратегия.

Задача.

Создать игру “Зоопарк” в котором все звери могут издавать определенные звуки, ходить и быть отрисованными на экране.

Создадим класс animal, в котором определим три метода и одно свойство.

class animal(object):

    sound = 'uuuuuuuuuuuuu!'
    def say(self):
        print self.sound

    def move(self):
        print 'I go a head!'

    def display(self):
        print '''##########DISPLAY########'''

От него унаследуем пустой класс cat.

class cat(animal):
    pass

Теперь допустим что возникла необходимость добавить зверям дополнительную функцию полета.

Нет ничего проще добавить эту функцию в базовый класс.

class animal(object):

    sound = 'uuuuuuuuuuuuu!'
    def say(self):
        print self.sound

    def move(self):
        print 'I go a head!'

    def display(self):
        print '''##########DISPLAY########'''

    def fly(self):
        print 'I can fly'

Но при таком подходе все звери получат такую возможность. Даже те, которые летать не должны.

Чтоб исправить положение, можно переопределять метод fly там, где он неуместен.

class bird(animal):
    def display(self):
        print "-------DISPLAY BIRD--------"
    def fly(self):
        print 'I can fly'

class cat(animal):
    def fly(self):
        print 'Oups I can not fly!'

if __name__ == "__main__":
    o1 = cat()
    o1.display()
    o1.say()
    o1.move()
    o1.fly()

Такой подхот имеет огромный минус - мы вынуждены дублировать переопределение этого метода во всех классах животных, неумеющих летать.

Что очень усложняет сопровождение программы.

Выход - это создать два новых класса, определяющих возможность полета с общим интерфейсом.

В нашем случае общим названием метода fly.

class canfly():
    def fly(self):
        print 'I can fly'

class cannotfly():
    def fly(self):
        print 'I can NOT fly'

И использовать объекты этого класса для внедрения их в класс животных в качестве одного из свойств.

class animal(object):
    sound = 'uuuuuuuuuuuuu!'
    fly_behavior = None
    def say(self):
        print self.sound

    def move(self):
        print 'I go a head!'

    def display(self):
        print '''##########DISPLAY########'''


class bird(animal):
    def __init__(self):
        self.fly_behavior = canfly()

class cat(animal):
    def __init__(self):
        self.fly_behavior = cannotfly()

Теперь мы свели метод fly в одно место программы и избежали дублирования.

Плюс получили возможность изменять поведение на лету, изменяя свойство fly_behavior.

if __name__ == "__main__":
    o1 = bird()
    o1.display()
    o1.say()
    o1.move()
    o1.fly_behavior.fly()
    ###POLYMORPHISM###########
    def myfly():
        print 'sssssssssssssssss'
    o1.fly_behavior.fly = myfly
    o1.fly_behavior.fly()
Основи Python и Django. -> Композиція.

Композиція.

Композиція - це складання цілого з елементів.

В ООП цей підхід полягає в тому, що є клас-контейнер, він же агрегатор, який включає виклики інших класів.

В результаті виходить, що при створенні об’єкта класу-контейнера, також створюються об’єкти включених до нього класів.

Припустимо, у нас є клас месенжера, який повинен відправляти повідомлення, використовуючи різні месенжери (telegramm email тощо).

Можна його описати так.

class BaseMessanger():

    def send(self, type, message):

        if type == 'email':
            print("Посылаю %s на email" % message)

        if type == 'telegram':
            print("Посылаю %s на telegram" % message)

m = BaseMessanger()
m.send('email','Hello!')

За такого підходу існує низка проблем.

  1. Вся логіка вибору способу передачі повідомлення знаходиться в одному методі, який може сильно роздутися і стати важко читається.

  2. Якщо цей клас має багато спадкоємців, то будь-яка поломка в базовому класі призведе до обвалення всіх дочірніх.

  3. Такий клас складно тестувати, наприклад, якщо в тестах потрібно замість реального надсилання на email зберегти повідомлення у файлі нам доведеться правити базовий клас.

  4. Неможливо змінити спосіб передачі після створення об’єкта.

Вирішити цю ситуацію можна композицією.

При цьому ми створимо ряд класів (транспортних) на кожен вид доставки та делегуємо процес відправлення.

Далі ми впровадимо об’єкт необхідного класу до нашого основного класу при ініціалізації його об’єкта в конструкторі.

class BaseMessanger(object):


    def __init__(self,message,sender):
        self.message = message
        self.sender = sender

    def send(self):
        self.sender.send(self.message)


class EmailSender():

    def send(self,message):
        print 'I am sending this %s via email' % message

class SkypeSender():

    def send(self,message):
        print 'I am sending this %s via skype' % message

class SmsSender():

    def send(self,message):
        print 'I am sending this %s via sms' % message

email_sender = EmailSender()
message = BaseMessanger('hello',email_sender)
message.send()

Можна вказувати транспорт у конструкторі

class Messanger(object):

def __init__(self,message,sender=EmailSender()):

Потрібно пам’ятати, що клас EmailSender має визначити перед класом Messanger.

email_sender = EmailSender()
message = Messanger('hello')
message.send()
message.sender = SmsSender()
message.send()


>>I am sending this hello via email
>>I am sending this hello via sms
Основи Python и Django. -> Тестування. Оператор assert.

Оператор assert

Цей оператор використовується для тестування та перевірки певної умови у програмі.

Він еквівалентний конструкції raise-if-not і викликає виняток при недотриманні умови.

Зазвичай, програмісти використовують цей оператор на початку функції для перевірки валідності параметрів, що передаються в неї.

Синтаксис:

assert Expression[, Arguments]

Якщо перевірка не пройшла, то Python використовує параметр Arguments як аргументи для виключення AssertionError.

AssertionError може бути оброблений як будь-який інший виняток try-except.

Однак, якщо воно не відпрацьоване – програма аварійно завершить роботу.

#!/usr/bin/python

def KelvinToFahrenheit(Temperature):
   assert (Temperature >= 0),"Colder than absolute zero!"
   return ((Temperature-273)*1.8)+32

print (KelvinToFahrenheit(273))
print (int(KelvinToFahrenheit(505.78)))
print (KelvinToFahrenheit(-5))

Висновок.

32.0
451
Traceback (most recent call last):
  File "asert.py", line 7, in <module>
    print (KelvinToFahrenheit(-5))
  File "asert.py", line 2, in KelvinToFahrenheit
    assert (Temperature >= 0),"Colder than absolute zero!"
AssertionError: Colder than absolute zero!
Основи Python и Django. -> Ітератори та генератори.

Генератори та ітератори.

Ітератори

Для того щоб пройти в циклі колекції, зазвичай використовується цикл for.

За списком.

for i in [1, 2, 3, 4]:
    print i,

По рядку.

for c in "python":
    print c

За файлом. Що якщо з якоїсь причини ми не хочемо використовувати цикл для проходу по колекції?.

for line in open("a.txt"):
    print line,

Що якщо з якоїсь причини ми не хочемо використовувати цикл для проходу по колекції?

Для цього потрібно мати спеціальний метод (next), у якому по порядку повертати її елементи доти не досягнемо кінця колекції.

На щастя, така функція існує і вбудована в Python.

with open('/etc/passwd') as f:
    try:
        while True:
            line = next(f)
            print(line, end='')
    except StopIteration:
        pass


with open('/etc/passwd') as f:
     while True:
         line = next(f, None)
         if line is None:
             break
         print(line, end='')

Але застосовна лише до об’єктів-ітераторів.

При виклик функції next(obj) задіюється внутрішній метод ітератора obj.next().

Такий код викличе виняток ‘list’ object is not an iterator.

mlst = [1,2,3,4]

try:
    next(mlst)
except Exception as e:
    print(e)

Функция iter()

Щоб перетворити об’єкт на ітератор, необхідно використовувати вбудовану функцію iter().

Ця функція, подібно до next(), викликає вбудований метод iter об’єкта.

mlst = [1,2,3,4]

mlistIter = iter(mlst)  # перетворюємо на ітератор
print(next(mlistIter))
print(next(mlistIter))

>> 1
>> 2

Також можна використовувати mlistIter в циклі:

for i in mlistIter:
    print(i)

Виникає питання. Як працює цей код якщо mlst не є спочатку ітератором?

mlst = [1,2,3,4]
for i in mlst:
    print(i)

Справа в тому, що в циклі Python приховано викликає внутрішній метод об’єкта __iter_, і намагається спочатку перетворити його на ітератор.

У Python є і вбудована функція iter, яка приймає параметром об’єкт і перетворює його на ітерований. Після цього такий об’єкт може бути використаний у конструкціях циклу for або while.

Власне, сам процес проходу циклу можна як послідовність викликів ф-ции next() ітератора.

>>> items = [1, 2, 3]
>>> # Get the iterator
>>> it = iter(items)     # Invokes items.__iter__()
>>> # Run the iterator
>>> next(it)             # Invokes it.__next__()
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

Цей код демонструє базовий механізм ітерації усередині циклових конструкцій.

Використовуючи механізм неявного виклику iter, можна створювати свої власні ітератори.

class Myiter:
    def __init__(self, value):
        self._value = value

    def __repr__(self):
        return 'Node({!r})'.format(self._value)

    def __iter__(self):
        print('Iter execution')
        return iter(self._value)



d = Myiter([1,2,3])
print(d)
for i in d:
    print(i)

>>Node([1, 2, 3])
>>Iter execution
>>1
>>2
>>3

У цьому прикладі ми продемонстрували ще одну особливість: під час виклику print(obj) неявно викликається внутрішній метод obj.repr().

Отже, протокол ітерування Python передбачає використання iter() для отримання об’єкта, який реалізує метод next() до роботи у циклі.

Ускладнимо наш ітератор, додавши контейнер self._children і можливість додавання елементів до нього.

class Myiter:
    def __init__(self, value):
        self._value = value
        self._children = []

    def __repr__(self):
        return 'Node({!r})'.format(self._value)

    def add_child(self, node):
        self._children.append(node)

    def __iter__(self):
        return iter(self._children)

# Example
if __name__ == '__main__':
    root = Myiter(0)
    root.add_child('hello')
    root.add_child('world')
    for ch in root:
        print(ch)

>> Outputs hello world

Генератори.

Нам вже відомі такі вбудовані функції, як range() або reversed() і т.д.

Вони повертають нам ітератори (об’єкти, що ітеруються).

Що робити, якщо ми хочемо створити аналогічну функцію?

Аби спробувати так:

def mygen():
    return [1,2,3,4]

for i in mygen():
    print(i)

Цей код працює, але немає можливості динамічно формувати послідовність усередині функції.

Це може знадобитися в тому випадку, якщо нам необхідно розбити великий масив на ряд дрібних з метою економії пам’яті.

Де за кожної ітерації оперувати фіксованим набором даних, а чи не заганяти в цикл весь масив.

Оператор yield

Для цього існує спеціальний опреатор yeld, який повинен повернути значення, що попадає у кожну ітерацію циклу.

Наступний приклад демонструє, як це можна зробити.

def frange(start, stop, increment):
    x = start
    while x < stop:
        yield x
        x += increment

У процесі циклу він багато разів викликатиме функцію і щоразу при досягненні до оператора yield, передавати те, що він повертає в тіло циклу, інакше цикл завершиться.

Така функція, яка використовує оператор yield називається генератором.

Цікавою особливістю є той факт, що всередині цього генератора зберігається позиція чергового попереднього yield.

def mygen():
    yield 1
    yield 2
    yield 3

for i in mygen():
    print(i)

Тобто. у циклі функція не виконується кожного разу з початку, а тільки з місця попереднього повернення значення yield.

Таким чином, наше завдання з розділення масиву можна вирішити так.

supermas = [[1,2,3],[4,5,6],[7,8,9]]

def genmas():
    for i in supermas:
        yield i

for i in genmas():
    print(i)

>>[1, 2, 3]
>>[4, 5, 6]
>>[7, 8, 9]

Насправді такий підхід часто використовується для проходу великою таблиці бази даних.

При цьому замість одного великого SQL запиту створюється генератор, який робить багато дрібних, запам’ятовуючи поточну позицію (курсор) всередині генератора.

http://nvie.com/posts/iterators-vs-generators/ http://anandology.com/python-practice-book/iterators.html http://chimera.labs.oreilly.com/books/1230000000393/ch04.html