class Animal():
pass
class Cat(Animal): #класс-родитель указывается в круглых скобках
pass14 ООП. Наследование и полиморфизм
14.1 Введение
Наследование - принцип ООП, согласно которому класс-наследник может получать функции и переменные от класса-родителя. Класс-родитель является более абстрактной моделью. Класс-наследник является более конкретной реализацией. Создавая иерархии классов, мы создаем множество моделей одного абстрактного явления. Множество форм - полиморфизм.
Классы наследуют от родителей переменные и функции, которые располагаются в public и protected областях.
class Animal():
def exclaim(self):
print('I am Animal!')
class Cat(Animal): #класс-родитель указывается в круглых скобках
pass
# класс Cat также имеет метод exclaim
animal = Animal()
animal.exclaim()
cat = Cat()
cat.exclaim()I am Animal!
I am Animal!
Классам-наследникам бывает необходимо изменять унаследованное поведение, переопределяя унаследованные функции, таким образом создавая множество форм (полиморфизм). Переопределить можно любой унаследованный метод.
class Animal():
def exclaim(self):
print('I am Animal!')
class Cat(Animal): #класс-родитель указывается в круглых скобках
def exclaim(self):
print('I am Cat! Child of Animal!')
animal = Animal()
animal.exclaim()
cat = Cat()
cat.exclaim()I am Animal!
I am Cat! Child of Animal!
Класс-наследник может иметь функции, которых нет в классе-родителе.
class Animal():
def exclaim(self):
print('I am Animal!')
class Cat(Animal): #класс-родитель указывается в круглых скобках
def exclaim(self):
print('I am Cat! Child of Animal!')
def purr(self):
print('Purr purr purr!')
animal = Animal()
animal.exclaim()
cat = Cat()
cat.exclaim()
cat.purr()I am Animal!
I am Cat! Child of Animal!
Purr purr purr!
Класс-потомок может вызвать метод класса-родителя.
class Person():
def __init__(self, name):
self.name = name
class EmailPerson(Person):
def __init__(self, name, email):
super().__init__(name) # вызов родительского конструктора с помощью super()
self.email = email # появилось доп.свойство в отличие от родителя
bob = EmailPerson('Bob Frapples', 'bob@frapples.com')
print(bob.name)
print(bob.email)Bob Frapples
bob@frapples.com
Когда вы определяете метод init() для своего класса, вы заменяете метод init() родительского класса, который больше не вызывается автоматически. В результате вам нужно вызывать его явно. Происходит следующее.
- Метод super() получает определение родительского класса Person.
- Метод init() вызывает метод Person.__init__(). Последний заботится о том, чтобы передать аргумент self суперклассу, поэтому вам нужно лишь передать опциональные аргументы. В нашем случае единственным аргументом класса Person() будет name.
- Строка self.email = email — это новый код, который отличает класс EmailPerson от класса Person.
Почему бы нам просто не определить новый класс так, как показано далее?
class EmailPerson(Person):
def __init__(self, name, email):
self.name = name
self.email = emailМы могли бы сделать это, но в таком случае потеряли бы возможность применять наследование. Мы использовали метод super(), чтобы создать объект, который работает примерно так же, как и объект класса Person. Есть и другое преимущество: если определение класса Person в будущем изменится, с помощью метода super() мы сможем гарантировать, что атрибуты и методы, которые класс EmailPerson наследует от класса Person, отреагируют на изменения.
Используйте метод super(), когда потомок делает что-то самостоятельно, но ему все еще нужно что-то от предка (как и в реальной жизни)
14.2 Типы методов
Одни данные (атрибуты) и функции (методы) являются частью самого класса, а другие — частью объектов, которые созданы на его основе. Когда вы видите начальный аргумент self в методах внутри определения класса, этот метод является методом экземпляра. Такие методы вы обычно пишете при создании собственного класса. Первый параметр метода экземпляра — это self, и Python передает объект методу, когда вы его вызываете.
В противоположность ему метод класса влияет на весь класс целиком. Любое изменение, которое происходит с классом, влияет на все его объекты. Внутри определения класса декоратор @classmethod показывает, что следующая функция является методом класса. Первым параметром метода также является сам класс. Согласно традиции этот параметр называется cls, поскольку слово class является зарезервированным и не может быть использовано здесь. Определим метод класса для А, который будет подсчитывать количество созданных объектов:
class A():
count = 0
def __init__(self):
A.count += 1
def exclaim(self):
print("I'm an A!")
@classmethod
def kids(cls):
print("A has", cls.count, "little objects.")
easy_a = A()
breezy_a = A()
wheezy_a = A()
A.kids()A has 3 little objects.
easy_a.exclaim()I'm an A!
A.exclaim(easy_a)I'm an A!
Обратите внимание на то, что мы вызвали метод A.count (атрибут класса) вместо self.count (который является атрибутом объекта). В методе kids() мы использовали вызов cls.count, но с тем же успехом могли бы применять вызов A.count.
Третий тип методов не влияет ни на классы, ни на объекты: он находится внутри класса только для удобства вместо того, чтобы располагаться где-то отдельно. Это статический метод, перед которым располагается декоратор @staticmethod, не имеющий в качестве начального параметра ни self, ни класс class. Рассмотрим пример, который служит в качестве рекламы класса CoyoteWeapon:
class CoyoteWeapon():
@staticmethod
def commercial():
print('This CoyoteWeapon has been brought to you by Acme')
CoyoteWeapon.commercial()This CoyoteWeapon has been brought to you by Acme
Обратите внимание на то, что нам не нужно создавать объект класса CoyoteWeapon, чтобы получить доступ к этому методу. Это здорово.
14.3 Магические методы
Теперь вы можете создавать и использовать простые объекты, но опустимся немного глубже и сделаем нечто большее.
Когда вы пишете что-то вроде a = 3 + 8, откуда целочисленные объекты со значениями 3 и 8 узнают, как реализовать операцию +? Кроме того, откуда a знает, как использовать =, чтобы получить результат? Вы можете воспользоваться этими операторами, применяя специальные методы Python (также можно назвать их магическими методами).
Имена этих методов начинаются с двойных подчеркиваний (__) и заканчиваются ими. Вы уже видели один такой метод: __init__ инициализирует только что созданный объект с помощью описания его класса и любых аргументов, которые были переданы в этот метод.
class Word():
def __init__(self, text):
self.text = text
def __eq__(self, word2):
return self.text.lower() == word2.text.lower()
def __str__(self):
return self.text
def __repr__(self):
return f'Word("{self.text}")'
first = Word('ha')first # используется __repr__Word("ha")
print(first) # используется __str__ha
14.4 Когда лучше использовать классы и объекты, а когда — модули
Рассмотрим несколько рекомендаций, которые помогут вам понять, где лучше разместить свой код — в классе или в модуле.
Объекты наиболее полезны, когда вам нужно иметь некоторое количество отдельных экземпляров с одинаковым поведением (методами), но различающихся внутренним состоянием (атрибутами).
Классы, в отличие от модулей, поддерживают наследование.
Если вам нужен только один объект, модуль подойдет лучше. Независимо от того, сколько обращений к модулю имеется в программе, будет загружена только одна копия. (Программистам на Java и С++: если вы знакомы с книгой Эриха Гаммы «Приемы объектно-ориентированного проектирования. Паттерны проектирования» (Gamma E. Design Patterns: Elements of Reusable Object-Oriented Software), можете использовать модули в Python как синглтоны.)
Если у вас есть несколько переменных, которые содержат разные значения и могут быть переданы как аргументы в несколько функций, лучше всего определить их как классы. Например, вы можете использовать словарь с ключами size и color, чтобы представить цветное изображение. Вы можете создать разные словари для каждого изображения в программе и передавать их в качестве аргументов в функции scale() и transform(). По мере добавления новых ключей и функций может начаться путаница. Более последовательно было бы определить класс Image с атрибутами size или color и методами scale() и transform(). В этом случае все данные и методы для работы с цветными изображениями будут определены в одном месте.
Используйте простейшее решение задачи. Словарь, список или кортеж проще, компактнее и быстрее, чем модуль, который, в свою очередь, проще, чем класс
14.5 Для самостоятельного обучения
Кроме особенностей, связанных с наследованием, существуют другие формы реализации полиморфизма в Python.
- Утиная типизация. Простой Python. Современный стиль программирования (c. 170-171)
- Перегрузка функций. [Обучающий материал](https://docs-python.ru/tutorial/opredelenie-funktsij-python/peregruzka-funktsij/)
14.6 Подведение итогов
TODO
14.7 Упражнения
14.7.1 Разминка
Выполняется в течение первого семинара.
Определите три класса: Bear, Rabbit и Octopus. Для каждого из них определите всего один метод — eats(). Он должен возвращать значения ‘berries’ (для Bear), ‘clover’ (для Rabbit) или ‘campers’ (для Octopus). Создайте по одному объекту каждого класса и выведите на экран то, что ест указанное животное.
Определите три класса: Laser, Claw и SmartPhone. Каждый из них имеет только один метод — does(). Он возвращает значения ‘disintegrate’ (для Laser), ‘crush’ (для Claw) или ‘ring’ (для SmartPhone). Далее определите класс Robot, который содержит по одному объекту каждого из этих классов. Определите метод does() для класса Robot, который выводит на экран все, что делают его компоненты.
14.7.2 Основное задание
Выполняется дома. Использовать профильные библиотеки для работы с последовательностями запрещается!
Объединитесь в группы по 3-4 человка. Напишите программу для работы с основными биологическими форматами данных: fasta, fastq, sam, vcf. Используйте наработки с предыдущих семинаров. Отдельный пункт: оптимизация для работы с большими файлами (генераторы!). Подготовьте демонстрационную программу и примеры для тестирования (fasta файлы можно скачать с NCBI или Uniprot). Программа должна быть задокументирована, оформлена в виде git репозитория. Документация кода должна быть собрана с помощью специальных пакетов в html файлы. Оформите релиз на GitHub с инструкцией по установке и запуску.
Примерная диаграмма классов для задания (для понимания иерархии, а не выставляемых требований по спецификации):
classDiagram
class Reader {
<<abstract>>
#filename: string
+__init__(filename: string)
+read() Iterator[Record]
+close()
#parse_line(line: str) Record
}
class SequenceReader {
<<abstract>>
+get_sequence(seq_id: str) Sequence
+validate_sequence(sequence: str) bool
}
class GenomicDataReader {
<<abstract>>
+get_chromosomes() List[str]
+get_reference_genome() string
+validate_coordinate(chrom: str, pos: int) bool
}
class FastaReader {
+read_sequences() Dict[str, Sequence]
+get_sequence_length(seq_id: str) int
}
class FastqReader {
+get_quality_scores(seq_id: str) List[int]
+get_average_quality(seq_id: str) float
}
class SamReader {
+read_alignments() List[Alignment]
+get_header() SamHeader
+filter_alignments(flag: int) List[Alignment]
+calculate_coverage(chrom: str) Dict[int, int]
}
class VcfReader {
+read_variants() List[Variant]
+get_header() VcfHeader
+filter_by_quality(min_qual: float) List[Variant]
+get_genotype(sample: str, variant: Variant) Genotype
}
Reader <|-- SequenceReader
Reader <|-- GenomicDataReader
SequenceReader <|-- FastaReader
SequenceReader <|-- FastqReader
GenomicDataReader <|-- SamReader
GenomicDataReader <|-- VcfReader
14.7.2.1 Требования к fasta
Получение количества последовательностей
Получение средней длины последовательностей
14.7.2.2 Требования к fastq
Те же, что и к fasta.
Построение графиков по качеству (библиотеки matplotlib, seaborn, plotly), наподобие FastQC. Интересуют графики per base sequence quality, per base sequence content, sequence length distribution
14.7.2.3 Требования к sam
- Получение заголовка и информации по отдельным группам заголовков (@PG, @RG и т.п.).
- Получение количества выравниваний.
- Получение статистики “количество выравниваний - хромосома.” (Используйте pandas)
- Получение выравниваний, лежащем в определенном геномном отрезке (аналог bedtools intersect).
14.7.2.4 Требования к vcf
- Получение заголовка и информации по отдельным группам заголовков
- Получение количества вариантов.
- Получение статистики “количество выравниваний - регион.” (Используйте pandas)
- Получение вариантов, лежащем в определенном геномном отрезке (аналог bedtools intersect).