Как создать инициализатор класса someclass python
Перейти к содержимому

Как создать инициализатор класса someclass python

  • автор:

Объектно-ориентированное программирование. Специальные методы.

Декоратор @staticmethod определяет обычную функцию (статический метод) в пространстве имён класса. У него нет обязательного параметра-ссылки self. Может быть полезно для вспомогательных функций, чтобы не мусорить пространство имён модуля. Доступ к таким методам можно получить как из экземпляра класса, так и из самого класса:

class SomeClass(object): @staticmethod def hello(): print("Hello, world") SomeClass.hello() # Hello, world obj = SomeClass() obj.hello() # Hello, world 
Hello, world Hello, world

Декоратор @classmethod создаёт метод класса и требует обязательную ссылку на класс (cls). Поэтому объект класса явно передаётся через первый параметр как это с параметром self происходит для обычных методов. Также как и для self, переданный cls может отличаться от класса, в котором определён класс-метод (может быть потомок). Часто используется для создания альтернативных конструкторов.

class SomeClass(object): @classmethod def hello(cls): print('Hello, класс <>'.format(cls.__name__)) SomeClass.hello() # Hello, класс SomeClass 
Hello, класс SomeClass

Давайте взглянем на пример кода, в котором одновременно показаны она декоратора, это может помочь понять основные принципы:

class Person: def __init__(self, name, age): self.name = name self.age = age # classmethod чтобы создать объект по году рождения, # "альтернативный" конструктор @classmethod def fromBirthYear(cls, name, year): return cls(name, 2019 - year) # статический метод,чтобы проверить совершеннолетие @staticmethod def isAdult(age): return age > 18 person1 = Person('Петя', 21) person2 = Person.fromBirthYear('Петя', 1996) print(person1.age) print(person2.age) # print the result print(Person.isAdult(22)) 
21 23 True

Важно понимать, что ни classmethod ни staticmethod НЕ являются функциями от конкретного объекта класса и соответственно не принимают self. Подчеркнем еще раз их различия: — classmethod принимает cls как первый параметр, тогда как staticmethod в специальных аргументах не нуждается — classmethod может получать доступ или менять состояние класса, в то время как staticmethod нет — staticmethod в целом вообще ничего не знают про класс. Это просто функция над аргументами, объявленная внутри класса.

Специальные методы (магические) вида _ _

В Python существует огромное количество специальных методов, расширяющих возможности пользовательских классов. Например, можно определить вид объекта на печати, его «официальное» строковое представление или поведение при сравнениях.

Эти методы могут эмулировать поведение встроенных классов, но при этом они необязательно существуют у самих встроенных классов. Например, у объектов int при сложении не вызывается метод add. Таким образом, их нельзя переопределить.

Давайте для примера переопределим стандартную операцию сложения. Рассмотрим класс Vector, используемый для представления радиус-векторов на координатной плоскости, и определим в нем поля-координаты: x и y. Также очень хотелось бы определить для векторов операцию +, чтобы их можно было складывать столь же удобно, как и числа или строки.

Для этого необходимо перегрузить операцию +: определить функцию, которая будет использоваться, если операция + будет вызвана для объекта класса Vector. Для этого нужно определить метод add класса Vector, у которого два параметра: неявная ссылка self на экземпляр класса, для которого она будет вызвана (это левый операнд операции +) и явная ссылка other на правый операнд:

class Vector(): def __init__(self, x = 0, y = 0): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) A = Vector(1, 2) B = Vector(3, 4) C = A + B print(C.x, C.y) 

Теперь при вызове оператора A + B Питон вызовет метод A.add(B), то есть вызовет указанный метод, где self = A, other = B.

class Vector: def __lt__(self, other): return self.x  other.x or self.x == other.x and self.y  other.y 

В этом примере оператор вернет True, если у левого операнда поле x меньше, чем у правого операнда, а также если поля x у них равны, а поле y меньше у левого операнда.

Список основных перегружаемых операторов

Метод Использование
Операторы сравнения
__lt__(self, other) x < y
__le__(self, other) x
__eq__(self, other) x == y
__ne__(self, other) x != y
__gt__(self, other) x > y
__ge__(self, other) x >= y
Арифметические операторы
Сложение
__add__(self, other) x + y
__radd__(self, other) y + x
__iadd__(self, other) x += y
Вычитание
__sub__(self, other) x — y
__rsub__(self, other) y — x
__isub__(self, other) x -= y
Умножение
__mul__(self, other) x * y
__rmul__(self, other) y * x
__imul__(self, other) x *= y
Математическое умножение (например векторное)
__matmul__(self, other) x @ y
__rmatmul__(self, other) y @ x
__imatmul__(self, other) x @= y
Деление
__truediv__(self, other) x / y
__rtruediv__(self, other) y / x
__itruediv__(self, other) x /= y
Целочисленное деление
__floordiv__(self, other) x // y
__rfloordiv__(self, other) y // x
__ifloordiv__(self, other) x //= y
__divmod__(self, other) divmod(x, y)
Остаток
__mod__(self, other) x % y
__rmod__(self, other) y % x
__imod__(self, other) x %= y
Возведение в степень
__pow__(self, other) x ** y
__rpow__(self, other) y ** x
__ipow__(self, other) x **= y
Отрицание, модуль
__pos__(self) +x
__neg__(self) -x
__abs__(self) abs(x)
__len__(self) len(x)
Преобразование к стандартным типам
__int__(self) int(x)
__float__(self) float(x)
__complex__(self) complex(x)
__str__(self) str(x)
__round__(self, digits = 0) round(x, digits)
Блок with
__enter__(self)
__exit__(self)

Задачи:

Задача 1:

Реализуйте свой класс Complex для комплексных чисел, аналогично встроенной реализации complex:

  1. Добавьте инициализатор класса
  2. Реализуйте основные математические операции
  3. Реализуйте операцию модуля (abs, вызываемую как |c|)
  4. Оба класса должны давать осмысленный вывод как при print, так и просто при вызове в ячейке

Задача 2:

  1. Создайте класс Vector с полями x, y, z определите для него конструктор, метод __str__, необходимые арифметические операции. Реализуйте конструктор, который принимает строку в формате «x,y».
  2. Программа получает на вход число N, далее координаты N точек. Доопределите в классе Vector недостающие операторы, найдите и выведите координаты точки, наиболее удаленной от начала координат.
  3. Используя класс Vector выведите координаты центра масс данного множества точек.
  4. Даны два вектора. Выведите площадь параллелограмма, построенного на заданных векторах.
  5. Даны три вектора. Выведите объём параллелепипеда, построенного на заданных векторах.
  6. Среди данных точек найдите три точки, образующие треугольник с наибольшим периметром. Выведите данный периметр.
  7. Среди данных точек найдите три точки, образующие треугольник с наибольшей площадью. Выведите данную площадь.

Сайт построен с использованием Pelican. За основу оформления взята тема от Smashing Magazine. Исходные тексты программ, приведённые на этом сайте, распространяются под лицензией GPLv3, все остальные материалы сайта распространяются под лицензией CC-BY.

Конструктор класса – метод __init__

В объектно-ориентированном программировании конструктором класса называют метод, который автоматически вызывается при создании объектов. Его также можно назвать конструктором объектов класса. Имя такого метода обычно регламентируется синтаксисом конкретного языка программирования. Так в Java имя конструктора класса совпадает с именем самого класса. В Python же роль конструктора играет метод __init__ .

В Python наличие пар знаков подчеркивания спереди и сзади в имени метода говорит о том, что он принадлежит к группе методов перегрузки операторов. Если подобные методы определены в классе, то объекты могут участвовать в таких операциях как сложение, вычитание, вызываться как функции и др.

При этом методы перегрузки операторов не надо вызывать по имени. Вызовом для них является сам факт участия объекта в определенной операции. В случае конструктора класса – это операция создания объекта. Так как объект создается в момент вызова класса по имени, то в этот момент вызывается метод __init__ .

Необходимость конструкторов связана с тем, что нередко объекты должны иметь собственные свойства сразу. Пусть имеется класс Person , объекты которого обязательно должны иметь имя и фамилию. Если класс будет описан подобным образом

class Person: def set_name(self, n, s): self.name = n self.surname = s

то создание объекта возможно без полей. Для установки имени и фамилии метод set_name нужно вызывать отдельно:

>>> from test import Person >>> p1 = Person() >>> p1.set_name("Bill", "Ross") >>> p1.name, p1.surname ('Bill', 'Ross')

В свою очередь, конструктор класса не позволит создать объект без обязательных полей:

class Person: def __init__(self, n, s): self.name = n self.surname = s p1 = Person("Sam", "Baker") print(p1.name, p1.surname)

Здесь при вызове класса в круглых скобках передаются значения, которые будут присвоены параметрам метода __init__ . Первый его параметр – self – ссылка на сам только что созданный объект.

Теперь, если мы попытаемся создать объект, не передав ничего в конструктор, то будет возбуждено исключение, и объект не будет создан:

>>> p1 = Person() Traceback (most recent call last): File "", line 1, in TypeError: __init__() missing 2 required positional arguments: 'n' and 's'

Однако бывает, что надо допустить создание объекта, даже если никакие данные в конструктор не передаются. В таком случае параметрам конструктора класса задаются значения по умолчанию:

class Rectangle: def __init__(self, w=0.5, h=1): self.width = w self.height = h def square(self): return self.width * self.height rec1 = Rectangle(5, 2) rec2 = Rectangle() rec3 = Rectangle(3) rec4 = Rectangle(h=4) print(rec1.square()) print(rec2.square()) print(rec3.square()) print(rec4.square())
10 0.5 3 2.0

Если класс вызывается без значений в скобках, то для параметров будут использованы их значения по умолчанию. Однако поля width и height будут у всех объектов.

Кроме того, конструктору вовсе не обязательно принимать какие-либо параметры, не считая self .

В других языка программирования, например в Java, классы могут содержать несколько конструкторов, которые между собой отличаются количеством параметром, а также, возможно, их типом. При создании объекта срабатывает тот конструктор, количество и типы параметров которого совпали с количеством и типами переданных в конструктор аргументов.

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

Практическая работа. Конструктор и деструктор

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

Это не значит, что сам деструктор уничтожает объект. В теле самого метода нет никаких инструкций по удалению экземпляра. Непосредственное удаление выполняется автоматически так называемым сборщиком мусора.

Деструктор (финализатор) в коде вашего класса следует использовать, когда при удалении объекта необходимо выполнить ряд сопутствующий действий, например, отправить сообщение, закрыть файл, разорвать соединение с базой данных.

В языке программирования Python объект уничтожается, когда исчезают все связанные с ним переменные или им присваивается другое значение, в результате чего связь со старым объектом теряется. Удалить переменную можно с помощью команды языка del . Также все объекты уничтожаются, когда программа завершает свою работу.

В классах Python функцию деструктора выполняет метод __del__ .

Напишите программу по следующему описанию:

  1. Есть класс Person , конструктор которого принимает три параметра (не учитывая self ) – имя, фамилию и квалификацию специалиста. Квалификация имеет значение заданное по умолчанию, равное единице.
  2. У класса Person есть метод, который возвращает строку, включающую в себя всю информацию о сотруднике.
  3. Класс Person содержит деструктор, который выводит на экран фразу «До свидания, мистер …» (вместо троеточия должны выводиться имя и фамилия объекта).
  4. В основной ветке программы создайте три объекта класса Person . Посмотрите информацию о сотрудниках и увольте самое слабое звено.
  5. В конце программы добавьте функцию input() , чтобы скрипт не завершился сам, пока не будет нажат Enter . Иначе вы сразу увидите как удаляются все объекты при завершении работы программы.

Курс с примерами решений практических работ:
pdf-версия

X Скрыть Наверх

Объектно-ориентированное программирование на Python

Чем отличаются классы Python от классов C++.

Осваивать новый язык легче, сравнивая его с уже знакомым. Правда, иногда такое сравнение может стать источником заблуждения, когда внешне похожие (одинаковые на первый взгляд) свойства двух разных языков на деле оказываются совершенно различными. Данный текст призван помочь избежать некоторых заблуждений тем, кто знаком с C++ и начал изучать Python.

Очевидные различия

Некоторые отличия сразу бросаются в глаза. Нет, речь не о синтаксисе, очевидно, что по синтаксису Python сильно отличается от C++.

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

Одно из важнейших отличий заключается в составе аргументов любого метода класса. В качестве первого аргумента указывается экземпляр класса, для которого вызван этот метод. Имя значения не имеет и может быть различным в разных методах, но традиционно используется » self «. Отсюда в частности следует, что любой метод должен иметь хотя бы один параметр. В первом приближении можно считать, что этот параметр является аналогом » this » в C++.

Отличия self от this
self this
Произвольный идентификатор Зарезервированное слово
Указывается явно в списке параметров метода Не указывается в списке параметров
Является ссылкой Является указателем

Второе отличие напрямую связано с первым. При использовании внутри метода, атрибуты не связываются автоматически с объектом, для которого вызван метод. Необходимо явно указать, к какому объекту относится атрибут, приписав к нему слева » self. » (здесь и далее под » self » мы будем подразумевать первый параметр метода, если не указано другое). Приведём небольшой пример:
Код на C++

class ExampleC < int a; public: void set_a(int sa) void set_abs_a(int sa) < if (sa<0) set_a(-sa); else set_a(sa); >>

Код на Python

class ExamplePy: def set_a(self, sa): self.a = sa def set_abs_a(self, sa): if sa

Вероятно, кто-то скажет, что последняя строка функции set_a в питоновском примере ошибочна — атрибут a не описан в определении класса ExamplePy ! Однако, на самом деле это не ошибка, а проявление принципиального отличия переменных (не только атрибутов объектов) Python от переменных C++. В то время, как в C++ с именем переменной на этапе компиляции связывается область памяти и тип, переменные Python являются элементами специального словаря (вернее, различных словарей), где имя переменной является ключом, который связан со значением переменной. Выполняя оператор присваивания, Python связывает значение выражения с именем переменной. Если с этим именем уже было связано какое-либо значение, связь со старым значением разрывается.

Как это всё работает

А работает это совсем не так, как в C++. И основное отличие состоит в том, что если в C++ описание класса это объявление, то в Python это исполняемый код. Эффектно проиллюстрировать это можно, поместив определение класса в ветвь условного оператора:

if 1: class Example: def f(self): print "aaa" else: class Example: def f(self): print "bbb" e = Example() e.f()

Попробуйте запустить эту программу сначала в приведённом здесь виде, а затем, заменив в условии 1 на 0.

Разберёмся, что здесь происходит. В результате выполнения определения класса создаётся объект с именем " Example ". Создание объекта e (экземпляра класса Example ) происходит путём обращения к объекту Example . То есть, класс в Python является объектом! Не удивляйтесь, в Python объектом является практически всё.

В C++ мы имели дело с полями и методами. Поля могли быть обычными (принадлежащими объекту) и статическими (принадлежащими классу). Методы так же были статические и обычные, последние разделялись на невиртуальные и виртуальные. В Python мы имеем дело с атрибутами класса и атрибутами объекта. Рассмотрим пример:

class my_class: "Some class" a = 10 def __init__(self, arg): self.b = arg def f(self): "Some method" print my_class.a, self.a, self.b c1 = my_class(1) c2 = my_class(2) c1.f() # 10 10 1 c2.f() # 10 10 2 c2.a = 20 c1.f() # 10 10 1 c2.f() # 10 20 1 my_class.a = 30 c1.f() # 30 30 1 c2.f() # 30 20 1

Здесь мы определили класс my_class с атрибутами a , __init__ и f . На самом деле класс содержит ещё два атрибута: __module__ и __doc__ . Первый содержит название модуля, в котором определён класс ("__main__", если это основная программа), второй содержит строку с описанием модуля (в данном случае "Some class"). Атрибут a можно сравнить со статическим полем класса — он доступен любому объекту класса, но не принадлежит ни одному из них. Попытка присвоить новое значение атрибуту объекта с тем же именем не повлияет на атрибут класса, а приведёт к созданию атрибута объекта, который "заслонит" для этого объекта атрибут класса. Доступ к атрибуту класса можно получить с помощью конструкции имя_класса.имя_атрибута , в данном примере: my_class.a . Не забываем, что класс — это объект!

Атрибуты __init__ и f это методы класса, причём __init__ неявно вызывается при создании экземпляра класса, то есть играет роль конструктора. Все методы в Python виртуальные. В методе __init__ мы присваиваем значение атрибуту b , который, в отличии от a , уже является атрибутом объекта, а не класса. b уникален для каждого объекта и не имеет никакого отношения к классу.

Теперь рассмотрим как работает наш пример. Сначала создаётся класс с двумя методами и атрибутом a , равным 10. Затем создаются два экземпляра класса. При создании им передаётся параметр со значением 1 и 2 соответственно, который присваивается атрибуту b соответствующего класса. Вызов метода f для каждого класса даёт:

10 10 1 10 10 2

После выполнения строки c2.a = 20 в экземпляре c2 появляется новый атрибут a , который заслоняет одноимённый атрибут класса. Однако последний по прежнему доступен через с помощью конструкции my_class.a . Теперь вызов метода f даёт:

10 10 1 10 20 2

Наконец, мы меняем значение атрибута класса строкой my_class.a = 30 . Это изменение затрагивает всё экземпляры класса и на выходе мы получаем:

30 30 1 30 20 2

Методы __init__ и f являются, так же как и a , атрибутами класса. Их отличие лишь в том, что это функции. Метод можно описать и вне класса, тогда в определении класса он не будет отличаться от атрибутов-данных.

def func(self, mult): print self.a*mult, self.b*mult class my_class: a = 5 g = func def __init__(self, b): self.b = b def f(self): print self.a, self.b c = my_class(3) c.f() c.g(10) my_class.h = lambda self,format:format % (self.a, self.b) print c.h('%d---%d') c1 = my_class(8) print c1.h('a=%d, b=%d') c1.h1 = lambda x: x*2 print c1.h1(2)

Здесь мы определили функцию от двух аргументов и присвоили ссылку на неё атрибуту класса my_class с именем g . По внешнему виду описание атрибута g ничем не отличается от a , но g это метод, так как func является функцией. Как и следовало ожидать, метод g требует один аргумент — второй аргумент func .

Затем мы делаем и вовсе невообразимую вещь — присваиваем новому атрибуту объекта my_class ссылку на функцию. В результате создаётся ещё один метод, причём доступный как для новых, так и для ранее созданных экземпляров my_class ! Впрочем, сильно удивляться не стоит. В насквозь динамическом Python экземпляр класса содержит ссылку на породивший его класс. Каждый раз при вызове метода интерпретатор ищет в классе (и, если не находит, то ищет в классах, от которых этот класс унаследован) атрибут-функцию с указанным именем. После этого он вызывает её, передавая в качестве первого аргумента ссылку на экземпляр класса, а в качестве остальных — явно указанные при вызове метода аргументы.

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

Наследование: можно, но необязательно

  1. Реализовать в базовом классе свойства, общие для всех классов-потомков, что позволяет избежать копирования кода.
  2. Получить возможность использовать экземпляры (вернее, указатели на них) производных классов везде, где ожидается указатель на экземпляр базового класса, то есть реализовывать полиморфизм.

Приведём пример с наследованием. Опишем класс, который накапливает объекты в списке и может распечатать содержимое списка. Кроме того, создадим два класса-потомка, которые по разному реализуют вывод элементов списка:

class base: "Базовый класс" def __init__(self): "Инициализируем список" self.lst = [] def add(self, item): "Добавляем элемент" self.lst.append(item) def print_list(self): "Выводим список" for item in self.lst: self.print_item(item) class child1(base): "Производный класс" def __init__(self, first): "Переопределяем __init__" base.__init__(self) # Вызываем конструктор базового класса self.lst.append(first) # Добавляем свой код def print_item(self, item): "Вывод элемента" print item class child2(base): "Производный класс" def print_item(self, item): "Вывод элемента" print "item: %s" % item def print_list(self): "Переопределяем print_list" print "lenght of list = %d" % len(self.lst) # Добавляем свой код base.print_list(self) # Вызываем print_list из базового класса c1 = child1('head') c1.add(1) c1.add([5,'qwerty']) c1.print_list() print '------------' c2 = child2() c2.add(2) c2.add([10,'item']) c2.print_list()

Сразу можно увидеть, что в методе print_list класса base вызывается не определённый в этом классе метод print_item . Это не ошибка, наличие этого метода будет необходимо только при выполнении print_list . Однако, если создать экземпляр класса base и вызвать для него print_list , возникнет исключение. Таким образом, класс base — абстрактный, он предназначен только для наследования, но не для создания экземпляров. При этом, в отличии от C++, нет необходимости описывать в классе base пустую функцию print_item . Встретив вызов self.print_item(item) , интерпретатор будет искать print_item в словаре класса, для объекта которого вызван print_list , а не найдя — в словаре(словарях) базового класса(классов). Если поиск завершится успехом, будет вызван найденный метод, иначе будет сгенерировано исключение. Разумеется, найденный метод должен принимать тот набор параметров, который передаётся при его вызове, иначе, опять же, будет сгенерировано исключение. Поскольку поиск производится по имени во время выполнения программы (позднее связывание), то все методы автоматически являются виртуальными.

Класс child1 переопределяет конструктор. Конструктор базового класса можно вызвать из произвольного места собственного конструктора.

Класс child2 переопределяет метод print_list , из которого явным образом вызывается метод базового класса, который, в свою очередь, вызывает метод print_item класса child2 . Обычный полиморфизм в действии.

Множественное наследование

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

В нашем примере класс lister (самый старший в иерархии) может добавлять элементы в атрибут-список l (метод add ) и поэлементно передавать весь список не определённому в нём методу out_item (метод out_all ).

Класс filer может выводить данные из списка в файл, который открывается в конструкторе и закрывается в деструкторе.

Класс formater может выдавать элементы списка в указанном формате. Строка формата задаётся в конструкторе.

Наконец класс list_writer может записывать элементы списка в файл в указанном формате.

class lister: """Класс позволяет добавлять элементы в список и выводить все элементы. Для вывода элементов необходимо определить в производном классе метод out_item. """ def __init__(self): print "lister init:", if hasattr(self, 'l'): print "list already created" else: print "create empty list" self.l = [] def add(self, item): self.l.append(item) def out_all(self): for item in self.l: self.out_item(item) class filer(lister): """Класс позволяет выводить данные в файл. Файл открывается при создании экземпляра класса и закрывается при его удалении. """ def __init__(self, filename): print "filer init" lister.__init__(self) self.f = open(filename, 'w') def out_item(self, item): self.f.write(item) def __del__(self): print "close file" self.f.close() class formater(lister): """Класс позволяет получать данные в заданном формате. """ def __init__(self, format): print "formater init" lister.__init__(self) self.format = format def printf(self, item): return self.format % item def out_item(self, item): print self.printf(item) class list_writer(filer, formater): """Класс позволяет добавлять элементы и записывать всё элементы в указанный файл в заданном формате. """ def __init__(self, filename, format): print "list_writer init" filer.__init__(self, filename) formater.__init__(self, format) def out_item(self, item): filer.out_item(self, self.printf(item)) # Создаём экземпляр. Указываем имя файла и формат(будем работать с целыми числами). lw = list_writer("data.txt", "item: %d\n") # Добавляем элементы. lw.add(1) lw.add(2) lw.add(3) # Записываем элементы в файл. lw.out_all() # При завершении программы файл закрывается.

В конструкторе класса list_writer вызываются конструкторы классов filer и formater , в каждом из которых вызывается конструктор класса lister . В результате, он вызывается дважды. Чтобы отследить вызов конструктора для уже проинициализированного экземпляра, в код конструктора включена проверка на существование атрибута l (функция hasattr). Если атрибут существует, повторная инициализация не производится.

list_writer init filer init lister init: create empty list formater init lister init: list already created close file

Обходимся без наследования

Допустим, нам нужно, в зависимости от ситуации, выводить строки в файл или сформировать из них одну длинную строку. Создадим два класса:

class to_file: "Вывод в файл" def __init__(self, f): self.f = f def write(self, line): self.f.write(line) class to_line: "Формирование длинной строки" def __init__(self): self.buf = "" def write(self, line): self.buf += line def get(self): return self.buf def writer(dest): dest.write("Первая строка\n") dest.write("Вторая строка\n") f = open("data.txt", 'w') tf = to_file(f) writer(tf) f.close() tl = to_line() writer(tl) print tl.get()

Классы to_file и to_line совершенно не связаны друг с другом, ни один из них не наследуется прямо или косвенно от другого и они не имеют общего предка (точнее, ни один из них вообще не имеет предков). Однако, функция writer одинаково успешно работает с экземплярами обоих классов. Для этого достаточно, чтобы каждый класс содержал используемый функцией writer метод write , который в каждом из классов принимает одинаковый набор аргументов.

В приведённом примере метод write встречается не только у экземпляров классов to_file и to_line , но и у объекта f , возвращённого функцией open (ссылка на него записывается в self.f в конструктора класса to_file и используется в методе write ). А нельзя ли обойтись без класса to_file и передать в функцию writer сам объект f ? Никаких проблем:

f = open("data.txt", 'w') writer(f) f.close()

Серьёзным (в отличии от "игрушечного", приведённого здесь) примером является стандартный модуль Python StringIO , реализующий файловый интерфейс для строк (программисты на C++ сразу вспомнят строковые потоки). Определённый в модуле класс StringIO не имеет предков, тем не менее, его экземпляры можно использовать везде, где можно использовать файловые объекты (возвращённые функцией open ).

Никакой защиты

Напоследок я, возможно, огорчу C++ программистов, решивших изучать Python. В этом языке отсутствует возможность запретить доступ к атрибутам класса или экземпляра. Никаких модификаторов "private", "protected" и "public" здесь не предусмотрено. Действуют два соглашения:

Соглашение первое, неформальное: если имя атрибута начинается с подчёркивания, этот атрибут не следует использовать напрямую. Выполнение этого соглашения полностью лежит на совести программиста.

Соглашение второе, формальное: если имя атрибута начинается с двух подчёркиваний и не заканчивается двумя подчёркиваниями, этот атрибут не удастся использовать напрямую обычным способом имя_объекта.имя_атрибута . Pyhton не позволяет выполнить такое обращение, однако оставляет возможность обратиться к атрибуту с помощью более сложной конструкции. Для этого нужно добавить слева к имени атрибута имя класса, предварённое символом подчёркивания. Пример:

class counter: def __init__(self): self.__a = 0 def __up(self): self.__a +=1 def get_a(self): self.__up() return self.__a c = counter() print c.get_a() # 1 print c.get_a() # 2 #c.__up() #print c.__a print c._counter__a # 2 c._counter__up() print c._counter__a # 3

Если раскомментировать любую из двух закомментированных строк, возникнет исключение AttributeError .

В версии 2.2 появилась возможность контролировать доступ к атрибутам с помощью слотов

Заключение

При написании статьи подразумевалось использование версии 2.1 . Большая часть информации остаётся верной и для новых версий (2.2 и 2.3), однако в современных версиях появились новые возможности, в том числе связанные с классами.

  • наследование от встроенных типов
  • возможность создавать статические методы и методы класса
  • слоты
  • свойства (Properties) — стандартный способ подмены чтения, изменения и других действий с атрибутом вызовами соответствующих функций (тем, кто имел дело с C++ Builder, это должно быть знакомо).

Инициализация и протоколы — Python: Введение в ООП

Теперь, когда вы знакомы со связанными методами, настало время рассказать про самый важный метод в Python: метод __init__ ("dunder-init", "дандер инит"). Этот метод отвечает за инициализацию экземпляров класса после их создания.

На прошлых уроках Бобу — экземпляру класса Person , мы задавали имя уже после того, как сам объект был создан. Такое заполнение атрибутов объекта и выглядит громоздко и может приводить к разного рода ошибкам: объект может какое-то время находиться в "недозаполненном" (более общее название — "неконсистентном") состоянии. Инициализатор же позволяет получить уже полностью настроенный экземпляр.

Реализуем класс Person так, чтобы имя можно было указывать при создании объекта:

class Person: def __init__(self, name): self.name = name bob = Person('Bob') bob.name # 'Bob' alice = Person('Alice') alice.name # 'Alice' 

Теперь нет нужды иметь в классе атрибут name = 'Noname' , ведь все объекты получают имена при инстанцировании!

Методы и протоколы

Вы заметили, что Python сам вызывает метод __init__ в нужный момент? Это же касается большинства dunder-методов: таковые вы только объявляете, но вручную не вызываете. Такое поведение часто называют протоколом (или поведением): класс реализует некий протокол, предоставляя нужные dunder-методы. А Python, в свою очередь, работает с объектом посредством протокола.

Продемонстрирую протокол получения длины имени объекта:

class Person: def __init__(self, name): self.name = name def __len__(self): return len(self.name) tom = Person('Thomas') len(tom) # 6 

Я объявил метод dunder-len, и объекты класса Person научились возвращать "свою длину" (да, пример надуманный, но суть отражает). Вызов функции len на самом деле приводит к вызову метода __len__ у переданного в аргументе объекта!

Протоколы и "утиная типизация"

Существует такой термин: "утиная типизация" ("duck typing") — "Если что-то крякает как утка и плавает как утка, то возможно это утка и есть!". Данный термин, пусть и звучит как шутка, описывает важный принцип построения абстракций: коду практически всегда достаточно знать о значении, с которым этот код работает, что это значение обладает нужными свойствами. Все остальное — не существенно.

Если коду достаточно знать, что сущность умеет плавать и крякать, то код не должен проверять сущность на фактическую принадлежность к уткам. Это позволит коду работать с робо-утками, даже если все настоящие утки вымрут, ведь код работает с абстрактными утками!

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *