Важнейшим основополагающим аспектом 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. Их перечень составлен на основе личного опыта. На мой взгляд, они отлично способствуют поддержанию читаемости кода.
Благодарю за внимание!
Читайте также:
- Программирование на квантовых компьютерах: какой язык учить?
- Зачем Python столько знаков подчеркивания?
- Отправляем E-mail с помощью Python
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Yong Cui: 6 Best Practices for Defining the Initialization Method In Python