С Новым Годом 2020!

​​Дорогие друзья!

🎄 Поздравляю вас всех с наступающим Новым Годом 2020! 🎄

Желаю вам счастья, здоровья, хорошего настроения! Поменьше багов в коде и побольше интересных проектов! А также любви, путешествий, достижений, новых открытий и позитивных эмоций!

Я искренне рад и благодарен, что вы читаете наш сайт и канал. В новом году я буду радовать вас множеством свежих интересных статей! 🎇

🎅 А пока вот небольшой праздничный сюрприз: написал программу, которая приклеивает шапку и бороду Деда Мороза на лица из видео-файла или с камеры. Используются библиотеки OpenCV и face-recognition. Код доступен на GitHub. Буду рад вашим звездочкам 🌟

Демонстрация работы SantaMask

Спасибо за поддержку! 🥂

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Переопределение свойств класса

В заметке расскажу, как переопределить свойства (@property) в классе-наследнике. Как оказалось, это не тривиально и существуют несколько вариантов, различных между собой.

Мем про property

Допустим есть базовый класс со свойством:

class Base:
    def __init__(self):
        self._x = 100

    @property
    def x(self):
        print('Base get x')
        return self._x

    @x.setter
    def x(self, value):
        print('Base set x')
        self._x = value

Ситуация А. В классе-наследнике мы хотим переопределить ТОЛЬКО сеттер, чтобы он делал что-то еще помимо того, что умеет в базовом классе. Это не так и тривиально:

class Derived1(Base):
    @Base.x.setter
    def x(self, value):
        print('Derived1 set x')
        Base.x.fset(self, value)

Ситуация B. Хотим переопределить ТОЛЬКО геттер:

class Derived3(Base):
    @Base.x.getter
    def x(self):
        print('Derived3 get x')
        return super().x

Ситуация C. Хотим переопределить и геттер, и сеттер.  

class Derived2(Base):
    @property
    def x(self):
        print('Derived2 get x')
        return super().x
    @x.setter
    def x(self, value):
        print('Derived2 set x')
        Base.x.fset(self, value)

В этом случае мы определяем свойство как бы с нуля. Старый геттер вызывается при доступе к super().x, а старый сеттер вызываем аналогично с ситуацией A. Разница только в @x.setter вместо @Base.x.setter (по-старому не работает, проверял).

Я специально разделил пост на три ситуации, потому в каждой из них работает свой подход, а другие комбинации, которые я пробовал, не работают.

Как сделать проще? Писать все геттеры и сеттеры явно, а потом делать из них property; или вообще отказаться от property.

class Base:
    def __init__(self):
        self._x = 0
    def set_x(self, v):
        print('Base set x')
        self._x = v
    def get_x(self):
        print('Base get x')
        return self._x
    x = property(get_x, set_x)

class Derived(Base):
    def set_x(self, v):
        print('Derived set x')
        super().set_x(v)
    def get_x(self):
        print('Derived get x')
        return super().get_x()
    x = property(get_x, set_x)

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

​​Unicode по имени

Вы знали, что в строке Python 3 можно вставлять символы по их названию в юникод-таблице?

Допустим нужны вам стрелки:

>>> "\N{LEFTWARDS ARROW} EXIT \N{RIGHTWARDS ARROW}"
'← EXIT →'

Найти юникод символы и их имена удобно с помощью онлайн-сервисов

Да, конечно, в Python 3 вы можете прямо в код вставлять любые юникод символы без их кодов и имен. Но профессиональнее – вставлять символы по именам, потому что читатель вашего кода может видеть его другим шрифтом, где начертание символов отличается от вашего, или вообще эти символы не отображаются. Ерунда? А вот вам пример:

>>> "Hello" == "Hello"
False

Строки выглядят одинаково, но я спрятал в одной из них символ "\N{ZERO WIDTH JOINER}", поэтому они неравны:

>>> len("Hel‍lo")
6
>>> len("Hello")
5
>>> "Hello".encode('utf-8')
b'Hel\xe2\x80\x8dlo'
>>> "Hello".encode('utf-8')
b'Hello'

Примечание: если примеры выше не работает правильно при копировании кода с сайта, это из-за того, что спрятанный юникод символ неправильно отображается на сайта. В посте в Телеграм – код корректно копируется.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

​​collections.deque – очередь Python

deque – коллекция двухсторонней очереди, которая похожа на список, за исключением того, что добавлять и удалять элементы можно либо в начало (слева), либо в конец (справа). Можно использовать и как стек. Реализована через двусвязный список. Благодаря этому, операции добавления или удаления элемента с любого конца deque имеют сложность O(1). Доступ к произольному элементу – O(n).

Создание:

from collections import deque

d1 = deque() # пустая
d2 = deque([1, 2, 3])  # из любого iterable
d3 = deque(maxlen=5)  # максимальная длина

Методы

Метод append(x) – добавить элемент в конец.

Метод appendleft(x) – добавить элемент в начало.

Метод pop(x) – удалить и вернуть элемент с конца.

Метод popleft(x) – удалить и вернуть элемент с начала.

Схема методов deque

Метод clear() – очистить очередь.

Метод reverse() – развернуть очередь наоборот:

d = deque([1, 2, 3])
d.reverse()
print(d)  # deque([3, 2, 1])

Метод rotate(n) – последовательно переносит n элементов с конца в начало (если n отрицательно, то из начала в конец):

d = deque(range(8))
# deque([0, 1, 2, 3, 4, 5, 6, 7])
d.rotate(2)
# deque([6, 7, 0, 1, 2, 3, 4, 5])
d.rotate(-1)
# deque([7, 0, 1, 2, 3, 4, 5, 6])

Метод extend(iterable) – добавляет в конец все элементы iterable.

Метод extendleft(iterable) – добавляет в начало все элементы iterable (начиная с последнего элемента iterable):

d = deque()
d.extend([1, 2, 3])
d.extendleft([10, 20, 30])
print(d)  # deque([30, 20, 10, 1, 2, 3])

Метод insert(index, x) – вставить элемент x в индекс i (медленно).

d[index] – доступ к элементу по индексу (медленно).

len(d) – число элементов в очереди (тоже работает медленно).

Метод remove(value) – удаляет первое вхождение значения value (слева направо). Остальные элементы не трогаются.

Метод count(value) – количество элементов со значением value в очереди.

Максимальная длина

Если при создание deque задано maxlen, то длина очереди будет ограничена. Это значит, что если мы попытаемся вставить элемент в очередь длины (maxlen), то элемент с противоположного конца будет вытеснен, и длина очереди не изменится:

# максимальная длина 5
d = deque(maxlen=5) 

d.extend(range(10))
# deque([5, 6, 7, 8, 9], maxlen=5)
# затерлись первые 5 элементов

d.appendleft(100)
# слева вставим 100, девятка вылетит справа
# deque([100, 5, 6, 7, 8], maxlen=5)

d.append(200)
# справа вставим 200, сотня вылетит слева
# deque([5, 6, 7, 8, 200], maxlen=5)

Резюме: deque хороша, когда частый доступ осуществляется только к концам коллекции. Подходит для очередей (например, задач), стэка, алгоритма round-robin, подсчета бегущих средних и прочего. А если нужен доступ к элементам по индексу – лучше брать list.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Перезагрузка модулей Python

Пусть в файле my_module.py написано определение класса:

class A: ...

Пишем такой код в другом файле:

from my_module import A
a = A()
from my_module import A

print(isinstance(a, A))

Ответ – True. Система модулей Python только единожды будет запускать каждый импорируемый файл. Второй import не возымеет действия, и класс А будет тем же, что и был раньше.

Бонус: если вам нужно принудительно перезагрузить модуль – воспользуйтесь функцией reload из importlib. Попробуем. В файл mymodule.py напишем:

class A:
    # будем видеть, когда класс загружен
    print('loaded class A')

В другой файле:

from importlib import reload

import mymodule
a = my_module.A()
mymodule = reload(mymodule)

print(isinstance(a, mymodule.A))

Вывод программы:

loaded class A
loaded class A
False

Запустить ход.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈