Важнейшим основополагающим аспектом Python как объектно-ориентированного языка программирования является определение связанных классов для управления и обработки данных в программах. При создании классов в первую очередь мы определяем метод инициализации __init__. И чем грамотнее это делается, тем проще становится понимать объекты экземпляра класса при чтении. В статье вас ждет обзор наилучших практик определения метода __init__.

1. Размещайте метод __init__ в самом начале 

Хотя для большинства из нас это правило очевидно, но встречаются программисты, которые “прячут” __init__ глубоко в теле класса с другими атрибутами. Располагать же его следует в самом начале класса до упоминания всех остальных методов, поскольку именно там его будут искать при чтении.

При наличии атрибутов класса метод __init__ размещается после них. Во избежание путаницы этого принципа следует последовательно придерживаться при определении всех классов проекта.  

2. Называйте первый параметр self

Прежде всего, необходимо понять, что означает первый параметр. Он относится к объекту экземпляра, который вызывает метод __init__. Не следует путать этот метод с инстанцированием. Исходя из имени, __init__ подразумевает инициализацию, в процессе которой задается начальное состояние вновь созданного объекта экземпляра. 

В правилах соглашения настоятельно рекомендуется называть первый параметр self, хотя это и не обязательно. Отметим, что self не является ключевым словом в Python, в отличие от многих других языков, которые могут использовать this, self или it в качестве зарезервированных слов для ссылки на текущий вызывающий объект экземпляра. 

3. Устанавливайте все атрибуты экземпляра компактно 

В Python нет строгих предписаний относительно того, где именно внутри класса определять атрибуты экземпляра. Однако это совсем не означает, что их можно размещать где угодно. Рассмотрим следующий пример:

class Student:
def __init__(self, name):
self.name = name

def verify_registration(self):
self.registered = True

def get_guardian_name(self):
self.guardian = "Someone"

Как видно, мы создаем экземпляр Student путем инициализации имени. Далее вызываем verify_registration и get_guardian_name: первый нужен для получения статуса регистрации, а второй  —  информации об опекуне студента. Однако недостаток этого шаблона реализации в том, что при таком повсеместном размещении атрибутов экземпляра читающим код будет непонятно, какие из них ему принадлежат. Поэтому лучше поместить все атрибуты в __init__ во избежание неопределенности, что мы и делаем в следующей реализации: 

class Student:
def __init__(self, name):
self.name = name
self.registered = False
self.guardian = None

4. Старайтесь обходиться без **kwargs

В Python **kwargs обозначает изменяемое число именованных параметров в определениях функций. В связи с этим включение **kwargs в метод __init__ синтаксически допускается. Однако такой способ подходит только для определенных случаев. В большинстве же ситуаций следует избегать данного синтаксиса, поскольку он скрывает требуемые параметры для инициализации экземпляров.

Допускаю, что некоторые программисты оправдывают применение **kwargs тем, что благодаря ему метод __init__ выглядит “чище”. Однако, по моему мнению, явно выраженное всегда предпочтительнее недосказанного. И пусть перечень всех параметров в заголовке метода __init__ выглядит громоздким, зато мы четко указываем пользователям, какие параметры необходимо задать для создания объектов экземпляра.  

5. Устанавливайте надлежащие значения по умолчанию 

Если известно, какие начальные значения должны быть у определенных аргументов, вы можете указать их в заголовке __init__, тем самым избавляя пользователей от необходимости устанавливать эти параметры при создании экземпляров. Как вариант, вы также можете задать эти параметры, если определенные значения подходят для большинства сценариев инстанцирования. Рассмотрим соответствующий пример: 

class Student:
def __init__(self, name, bus_rider=True):
self.name = name
self.bus_rider = bus_rider

Однако в случае, когда параметр является изменяемой структурой данных, результат будет совсем иной. Перед вами ошибочный пример: 

class Student:
def __init__(self, name, subjects=["maths", "physics"]):
self.name = name
self.subjects = subjects

Проблема в том, что при указании [“maths”, “physics”] в качестве значений по умолчанию список создается в определении функции и далее совместно используется всеми экземплярами. Следующий код наглядно выявляет этот недочет: 

>>> student0 = Student("John")
>>> student0.subjects.append("music")
>>> student0.subjects
['maths', 'physics', 'music']
>>> student1 = Student("Ashley")
>>> student1.subjects
['maths', 'physics', 'music']

6. Пишите строки документации (docstrings) 

По аналогии с другими публичными методами __init__ необходимо снабдить надлежащим строками документации. И хотя некоторые предпочитают документировать информацию о создании на уровне класса (например, располагать сведения касательно инициализации под заголовком класса), обычно рекомендуется размещать строки документации сразу под методом __init__.

Сначала указывается тип каждого параметра: str или int, после чего следует краткое описание его сути или назначения. При наличии предустановленного значения также рекомендуется его охарактеризовать с помощью соответствующих обоснований/пояснений.

Заключение

Итак, мы рассмотрели наилучшие практики определения метода __init__ для классов в Python. Их перечень составлен на основе личного опыта. На мой взгляд, они отлично способствуют поддержанию читаемости кода. 

Благодарю за внимание! 

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Yong Cui: 6 Best Practices for Defining the Initialization Method In Python

Предыдущая статьяВас неправильно учили объектно-ориентированному программированию
Следующая статья5 способов уменьшения размера пакетов JavaScript