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

Основы 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. -> Итераторы и генераторы.

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

Итераторы

Для того чтобы пройти в цикле по коллекции, обычно используется цикл for.

По списку.

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

По строке.

for c in "python":
    print c

По файлу.

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

Что если по какой либо причине мы не хотим использовать цикл for для прохода по коллекции?

Для этого нужно иметь специальный метод (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

Основы 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. -> Тестирование. Оператор 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!