Содержание

Объектно-ориентированное программирование. Классы и объекты

Сегодня мы поговорим об объектно-ориентированном программировании и о его применении в python.

Объектно-ориентированное программирование (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов.

Класс — тип, описывающий устройство объектов. Объект — это экземпляр класса. Класс можно сравнить с чертежом, по которому создаются объекты.

Python соответствует принципам объектно-ориентированного программирования. В python всё является объектами — и строки, и списки, и словари, и всё остальное.

Но возможности ООП в python этим не ограничены. Программист может написать свой тип данных (класс), определить в нём свои методы.

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

Приступим теперь собственно к написанию своих классов на python. Попробуем определить собственный класс:

>>> # Пример простейшего класса, который ничего не делает
... class A:
...     pass

Теперь мы можем создать несколько экземпляров этого класса:

>>> a = A()
>>> b = A()
>>> a.arg = 1  # у экземпляра a появился атрибут arg, равный 1
>>> b.arg = 2  # а у экземпляра b - атрибут arg, равный 2
>>> print(a.arg)
1
>>> print(b.arg)
2
>>> c = A()
>>> print(c.arg)  # а у этого экземпляра нет arg
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'arg'

Классу возможно задать собственные методы:

>>> class A:
...     def g(self): # self - обязательный аргумент, содержащий в себе экземпляр
...                  # класса, передающийся при вызове метода,
...                  # поэтому этот аргумент должен присутствовать
...                  # во всех методах класса.
...         return 'hello world'
...
>>> a = A()
>>> a.g()
'hello world'

И напоследок еще один пример:

>>> class B:
...     arg = 'Python' # Все экземпляры этого класса будут иметь атрибут arg,
...                    # равный "Python"
...                    # Но впоследствии мы его можем изменить
...     def g(self):
...         return self.arg
...
>>> b = B()
>>> b.g()
'Python'
>>> B.g(b)
'Python'
>>> b.arg = 'spam'
>>> b.g()
'spam'

Основы работы с классами в Pyton 3. Класс и объект в Pyton

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

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

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

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

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

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

Как создавать класс в Python

Класс представляет собой проект объекта. Для примера можно взять небольшой код:

Здесь, в последней строке, можно увидеть переменную «х», которая может выглядеть, как большой объем информации, но в ней имеется множество методов. Для их обнаружения, необходимо вписать ключевое слово dir. Из этого следует, что строка основана на классе, а переменная х является экземпляром класса.

Для создания класса, первоначально потребуется ввести команду class, после чего поставить двоеточие. Далее прописывается описание класса, но этот момент не является обязательным и может быть пропущен по желанию пользователя. Чтобы открыть доступ к строке, введите команду Class._doc_ Для полного ознакомления с классом, рассмотрим из чего он состоит.

Атрибут

Эта часть относится к переменной в классе. Другими словами, все его действия происходят внутри классового кода. Атрибут Class способен создать только одну копию внутри класса. Она же, впоследствии, работает со всеми функциями и объектами этого класса. Если это утверждение разобрать на примере, то оно будет выглядеть следующим образом:

class class_attribute:    val = 1    def product(self):        class_attribute.val *= 10        print(class_attribute.val) obj1 = class_attribute() obj1.product() obj2 = class_attribute() obj2.product()

Для создания переменной класса используем слово val и придадим ему значение 1. Чтобы получить доступ к переменной val, прописываем ниже функцию product(). Создаем манипуляцию значения, умножив его на 10.

Ниже можно увидеть, что после создания двух объектов копия переменной будет использоваться в них обоих. Так как начальное значение атрибута равно 1, а после оно умножается на 10, то в obj1 мы получим итоговое значение val=10, а вот при вызове obj2, val умножается на 10 и получает значение 100. Как итог на экране высветиться следующее:

Метод

Это функция, которая является элементом класса, и отвечает за выполнение какого-либо действия. В качестве первого параметра метод принимает экземпляр класса. Чтобы начать им пользоваться, нужно вызвать функцию через объект. Всего различается три вида методов:

  • Методы экземпляра класса. Наиболее часто встречающийся метод. В качестве первого аргумента self, экземпляр класса принимает объект класса, указывающий на сам экземпляр. Данный вид метода имеет неограниченное количество параметров. Использование аргумента self поможет поменять состояние объекта и обратиться к другим методам и параметрам, привязанным к нему. К примеру, при использовании атрибута self._class_, возможно получить доступ к атрибутам класса и изменить его состояние. Из этого следует, что экземпляры класса предоставляют возможность изменения состояния отдельного объекта или класса в целом.
  • Методы класса. Сущность метода класса в том, что он в качестве параметра cls принимает класс. Декларируя методы, необходимо использовать декоратор classmethod. В отличие от предыдущего метода, метод класса привязан только к классу, но не имеет привязанности к его экземпляру. Метод класса работает над изменением состояния класса, что отражается на всех объектах, содержащихся внутри класса, но он не способен изменить состояние конкретно выбранного объекта.
  • Статические методы. Декларация статического метода производится использованием декоратора staticmethod. Характерная особенность этого вида в том, что они не способны изменять состояний класса или их экземпляров. Они работают в статическом состоянии и потому не нуждаются в предварительном записывании первого аргумента.

Конструктор

Объектно-ориентированное программирование характеризует конструктор как метод, проявляемый автоматически при создании объектов. Другими словами, он участвует в построении объектов класса. В каждом языке программирования конструктору дается свое имя, так в Python это метод _init_().

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

  • Отображается символическим значением _init_.
  • Его необходимость заключена в создании объекта.
  • Через свойства объекта конструктор передает значения аргументов.
  • Независимо, какого размера класс, он вмещает только один конструктор.
  • В случае невозможности обнаружения конструктором класса, программа переключается на родительский класс.
# Прямоугольник.

class Rectangle :

'Это класс Rectangle'

# Способ создания объекта (конструктор)

def __init__(self, width, height):        

self.width= width

self.height = height




def getWidth(self):       

return self.width




def getHeight(self):       

return self.height




# Метод расчета площади.

def getArea(self):

return self.width * self.height

Создаем объект используя класс Rectangle:

# Создаем 2 объекта: r1 & r2

r1 = Rectangle(10,5)

r2 = Rectangle(20,11)




print("r1.width = ", r1.width)

print("r1.height = ", r1.height)

print("r1.getWidth() = ", r1.getWidth())

print("r1.getArea() = ", r1.getArea())




print("-----------------")




print("r2.width = ", r2.width)

print("r2.height = ", r2.height)

print("r2.getWidth() = ", r2.getWidth())

print("r2.getArea() = ", r2.getArea())

Что происходит при создании объекта с помощью класса

Работая над конструированием объекта, программа автоматически запускает конструктор. При этом атрибуты получают значения аргументов.

Конструктор с аргументами по умолчанию

Рассматривая иные языки, можно заметить, что они содержат по несколько конструкторов. Питон допускает использование одного. Но преимущество в том, что возможна установка значений по умолчанию.

Примечание от эксперта! Аргументы, участвующие в построении класса, указываются до аргументов со значениями по умолчанию.

class Person:

    # Параметры возраста и пола имеют значение по умолчанию.

    def __init__(self, name, age=1, gender="Male"):

        self.name = name

        self.age = age

        self.gender= gender

        

    def showInfo(self):

        print("Name: ", self.name)

        print("Age: ", self.age)

        print("Gender: ", self.gender)

Где gender =”Male” относится к значению по умолчанию. Дальше задаем другие значения:

from person import Person




# Создать объект Person.

aimee = Person("Aimee", 21, "Female")

aimee.showInfo()

print(" --------------- ")




# возраст по умолчанию, пол.

alice = Person( "Alice" )

alice.showInfo()




print(" --------------- ")




# Пол по умолчанию.

tran = Person("Tran", 37)

tran.showInfo()

В итоге получаем результат с итоговыми значениями.

Сравниваем объекты

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

Оператор ==, прописанный в коде, необходим для проведения анализа: на одну или разные ячейки ссылаются выбранные объекты. Если ссылка ведет к одному месту, то оператор == возвратит True. Для понимания ссылаются или нет выбранные объекты на места с разными адресатами, прописывается другой оператор !=

from rectangle import Rectangle







r1 = Rectangle(20, 10)

r2 = Rectangle(20 , 10)

r3 = r1




# Сравните r1 и r2

test1 = r1 == r2 # --> False

# Сравните r1 и r3

test2 = r1 == r3 # --> True




print ("r1 == r2 ? ", test1)

print ("r1 == r3 ? ", test2)




print (" -------------- ")




print ("r1 != r2 ? ", r1 != r2)

print ("r1 != r3 ? ", r1 != r3)

Где конечным результатом будет:

Что такое атрибуты

В Питоне также существует второе понятие Атрибуты, которое имеет отличие от первого, изученного нами. Если первое относится к «Переменным класса», то второе к «Атрибутам». Разберем на примере:

class Player:

    # Переменная класса

    minAge  = 18

    maxAge = 50

    

    def __init__(self, name, age):

        self.name = name

        self.age = age

Атрибут

Для создания объектов из одного класса используются разные ячейки памяти. Следовательно, их атрибуты с одинаковым именем будут ссылаться на различную адресацию. К примеру:

from player import Player







player1 = Player("Tom", 20)




player2 = Player("Jerry", 20)




print("player1.name = ", player1.name)

print("player1.age = ", player1.age)




print("player2.name = ", player2.name)

print("player2.age = ", player2.age)




print(" ------------ ")




print("Assign new value to player1.age = 21 ")




# Присвойте новое значение атрибуту возраста player1.

player1.age = 21




print("player1.name = ", player1.name)

print("player1.age = ", player1.age)




print("player2.name = ", player2.name)

print("player2.age = ", player2.age)

Итоговый результат:

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

from player import Player







player1 = Player("Tom", 20)

player2 = Player("Jerry", 20)




# Создайте новый атрибут с именем «address» для player1.

player1.address = "USA"




print("player1.name = ", player1.name)

print("player1.age = ", player1.age)

print("player1.address = ", player1.address)




print(" ------------------- ")




print("player2.name = ", player2.name)

print("player2.age = ", player2.age)




# player2 е имеет атрибута 'address' (Error!!)

print("player2.address = ", player2.address)

В результате получаем вывод:

player1.name =  Tom

player1.age =  20

player1.address =  USA

 -------------------

player2.name =  Jerry

player2.age =  20

Traceback (most recent call last):

  File "C:/Users/gvido/class.py", line 27, in <module>

    print("player2.address = ", player2.address)

AttributeError: 'Player' object has no attribute 'address'

Атрибуты функции

Для получения доступа к атрибутам объекта, используется оператор «точка» (к примеру, такие значения: player.age). Для облегчения задачи используют различные функции.

getattr (obj, name[,default])Работает над возвратом значений атрибута или проводит установку значений по умолчанию, если они не были обнаружены.
hasattr (obj, name)Работает над проверкой атрибута объекта: совершалась ли передача аргументом name.
setattr (obj, name, value)Работает над приданием значений атрибуту. При его отсутствии создается новый.
delattr (obj, name)Работает над удалением атрибута.

Разновидности встроенных атрибутов класса

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

АтрибутОписание
_dict_Предоставление самых необходимых данных о классе.
            _doc_Возвращение строки с описанием класса. При невозможности определения значения, возвращается None.
_class_Возвращение объекта, содержащего информацию о классе.
      _module_Возвращение имя модуля класса. При определении класса в выполняемом модуле возвращается _main_.

Виды переменных класса

Если приходилось сталкиваться с другими языками программирования, такими как Java или C#, то наверняка знаете, что такое Field. Переменные класса в Питоне имеют примерно то же значение.

Совет от эксперта! Для получения доступа к переменной, воспользуйтесь именем, а не его объектом класса. В таком случае не произойдет путаницы между переменной и атрибутами.

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

Варианты составляющих класса или объекта

Чтобы найти все атрибуты, методы, объекты и переменные класса, можно воспользоваться вводом функции dir.

from player import Player







# Вывести список атрибутов, методов и переменных объекта 'Player'

print(dir(Player))

print("\n\n")




player1 = Player("Tom", 20)

player1.address ="USA"




# Вывести список атрибутов, методов и переменных объекта 'player1'

print(dir(player1))

Как результат на экран выведутся следующие значения:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',

'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',

'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',

'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',

'__repr__', '__setattr__', '__sizeof__', '__str__',

'__subclasshook__', '__weakref__', 'maxAge', 'minAge'] 




['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',

'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',

'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',

'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',

'__repr__', '__setattr__', '__sizeof__', '__str__',

'__subclasshook__', '__weakref__', 'address', 'age', 'maxAge',

'minAge', 'name']

Заключение

Объектно-ориентированное программирование предусматривает работу с классами, которые были разобраны в данной статье. Что они включают в свой состав. И какие функции, методы и атрибуты используют для выполнения различных действий.

Оцените качество статьи. Нам важно ваше мнение:

Создание классов и объектов. Урок 2 курса «Объектно-ориентированное программирование на Python»

В языке программирования Python классы создаются с помощью инструкции class, за которой следует произвольное имя класса, после которого ставится двоеточие, далее с новой строки и с отступом реализуется тело класса:

class ИмяКласса:
    код_тела_класса

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

Объект создается путем вызова класса по его имени. При этом после имени класса обязательно ставятся скобки:

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

имя_переменной = ИмяКласса()

В последствии к объекту обращаются через связанную с ним переменную.

Пример «пустого» класса и двух созданных на его основе объектов:

>>> class A:
...     pass
... 
>>> a = A()
>>> b = A()

Класс как модуль

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

>>> class B:
...     n = 5
...     def adder(v):
...             return v + B.n
... 
>>> B.n
5
>>> B.adder(4)
9

Однако в случае классов используется немного иная терминология. Пусть имена, определенные в классе, называются атрибутами этого класса. В примере имена n и adder – это атрибуты класса B. Атрибуты-переменные часто называют полями или свойствами. Свойством является n. Атрибуты-функции называются методами. Методом в классе B является adder. Количество свойств и методов в классе может быть любым.

Класс как создатель объектов

Приведенный выше класс позволяет создавать объекты, но мы не можем применить к объекту метод adder():

>>> l = B()
>>> l.n
5
>>> l.adder(100)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: adder() takes 1 positional 
argument but 2 were given

В сообщении об ошибке говорится, что adder() принимает только один аргумент, а было передано два. Откуда мог взяться второй аргумент, если в скобках было указано только одно число 100?

На самом деле классы – это не модули. Они обладают своими особенностями. Класс создает объекты, которые являются его наследниками. Это значит, что если у объекта нет собственного поля n, то интерпретатор ищет его уровнем выше, то есть в классе. Таким образом, если мы добавляем объекту поле с таким же именем как в классе, то оно перекрывает, то есть переопределяет, поле класса:

>>> l.n = 10
>>> l.n
10
>>> B.n
5

Здесь l.n и B.n – это разные переменные. Первая находится в пространстве имен объекта l. Вторая – в пространстве класса B. Если бы мы не добавили поле n к объекту l, то интерпретатор бы поднялся выше по дереву наследования и пришел бы в класс, где бы и нашел это поле.

Что касается методов, то они также наследуются объектами класса. В данном случае у объекта l нет своего собственного метода adder, значит, он ищется в классе B.

Однако в случае с методами не так все просто, как с полями. В Python по умолчанию один и тот же метод, вызванный от имени класса (например, B.meth()) и от экземпляра этого класса (например, l.meth()), вызывается по-разному. В последнем случае l.meth() невидимо для нас преобразуется в B.meth(l).

Но если метод meth() определен как непринимающий никаких аргументов, а они в него передаются, это приводит к ошибке:

>>> class A:
...     def meth():
...             print(1)
... 
>>> a = A()
>>> a.meth()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: meth() takes 0 positional arguments 
but 1 was given
>>> A.meth()
1

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

Понятно, что передаваемый экземпляр, это объект, к которому применяется метод. Выражение l.adder(100) выполняется интерпретатором следующим образом:

  1. Ищу атрибут adder() у объекта l. Не нахожу.

  2. Тогда иду искать в класс B, так как он создал объект l.

  3. Здесь нахожу искомый метод. Передаю ему объект, к которому этот метод надо применить, и аргумент, указанный в скобках.

Другими словами, выражение l.adder(100) преобразуется в выражение B.adder(l, 100).

Таким образом, интерпретатор попытался передать в метод adder() класса B два параметра – объект l и число 100. Но мы запрограммировали метод adder() так, что он принимает только один параметр. В Python определения методов не предполагают принятие объекта как само собой подразумеваемое. Принимаемый объект надо указывать явно.

По соглашению в Python для ссылки на объект используется имя self. Вот так должен выглядеть метод adder(), если мы планируем вызывать его через объекты:

>>> class B:
...     n = 5
...     def adder(self, v):
...             return v + self.n
... 

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

Протестируем обновленный метод:

>>> l = B()
>>> m = B()
>>> l.n = 10
>>> l.adder(3)
13
>>> m.adder(4)
9

Здесь от класса B создаются два объекта – l и m. Для объекта l заводится собственное поле n. Объект m, за неимением собственного, наследует n от класса B. Можно в этом убедиться, проверив соответствие:

>>> m.n is B.n
True
>>> l.n is B.n
False

В методе adder() выражение self.n – это обращение к свойству n, переданного объекта, и не важно, на каком уровне наследования оно будет найдено.

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

Изменение полей объекта

В Python объекту можно не только переопределять поля и методы, унаследованные от класса, также можно добавить новые, которых нет в классе:

>>> l.test = "hi"
>>> B.test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'B' has no 
attribute 'test'
>>> l.test
'hi'

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

Поэтому принято присваивать полям, а также получать их значения, путем вызова методов:

>>> class User:
...     def setName(self, n):
...             self.name = n
...     def getName(self):
...             try:
...                     return self.name
...             except:
...                     print("No name")
... 
>>> first = User()
>>> second = User()
>>> first.setName("Bob")
>>> first.getName()
'Bob'
>>> second.getName()
No name

Подобные методы называют сеттерами (set – установить) и геттерами (get – получить).

Атрибут __dict__

В Python у объектов есть встроенные специальные атрибуты. Мы их не определяем, но они есть. Одним из таких атрибутов объекта является свойство __dict__. Его значением является словарь, в котором ключи – это имена свойств экземпляра, а значения – текущие значения свойств.

>>> class B:
...     n = 5
...     def adder(self, v):
...             return v + self.n
... 
>>> w = B()
>>> w.__dict__
{}
>>> w.n = 8
>>> w.__dict__
{'n': 8}

В примере у экземпляра класса B сначала нет собственных атрибутов. Свойство n и метод adder – это атрибуты объекта-класса, а не объекта-экземпляра, созданного от этого класса. Лишь когда мы выполняем присваивание новому полю n экземпляра, у него появляется собственное свойство, что мы наблюдаем через словарь __dict__.

В следующем уроке мы увидим, что свойства экземпляра обычно не назначаются за пределами класса. Это происходит в методах классах путем присваивание через self. Например, self.n = 10.

Атрибут __dict__ используется для просмотра всех текущих свойств объекта. С его помощью можно удалять, добавлять свойства, а также изменять их значения.

>>> w.__dict__['m'] = 100
>>> w.__dict__
{'n': 8, 'm': 100}
>>> w.m
100

Практическая работа

Напишите программу по следующему описанию. Есть класс «Воин». От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.

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

Как работают классы в Python. В этой статье рассмотрим разные аспекты… | by Ilya Lebedev

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

Тип — это объект. Когда выполняется программа, в памяти живут различные значения: числа, строки, словари и так далее. Где-то рядом с ними живёт тип int. Это отдельный кусок памяти, на который ссылаются переменные. Так же как и функции, типы — это first class citizen: они живут в памяти, их можно передавать как аргументы и проводить интроспекцию. Если создать класс, то количество объектов в памяти увеличится:

Тип — это инстанс типа type. Есть целое число (например, 28).
Это — инстанс типа int. А сам тип int — это инстанс типа type:

Тип и класс — это одно и то же. Во втором Python были old-style классы,
у них это было не так. С тех пор утекло много воды и теперь тип и класс — это одно и то же. Вот так в этом можно убедиться:

Раз класс — это инстанс type, то мы можем создать класс, создав
инстанс типа type. Вот так:

Это аналог привычного синтаксиса создания класса:

Функция type умеет не только определять тип объекта, но и создавать
новые типы. Для этого ей нужно передать три аргумента: название типа, базовые типы и атрибуты.

А ещё все типы наследуются от object, даже type. Это можно проверить так:

Важно не путать два вида отношений: инстанцирование и наследование. В примерах выше StringableFloat — наследник float и они оба — инстансы type.

Раз создание класса — это такое же инстацирование, как и создание переменной, то этим управляет один и тот же кусок кода — функция type. Давайте посмотрим, что происходит в этот момент, подробнее.

Заглянем в исходный код вызова type. Если коротко, то в нём вызывается tp_new и tp_init.

tp_new — это конструктор в привычном понимании. Под капотом его реализация (PyType_GenericNew) вызывает tp_alloc, который выделяет память, создаёт объект и сдаёт его сборщику мусора для отслеживания.

tp_init вызывает __init__, если выполнены необходимые условия:
передан правильный объект, есть память и всё такое.

Тут важно, что вызовом __init__ занимается type, его не нужно
напрямую вызывать в __new__.

__init__ вызывается только если __new__ вернул инстанс нужного класса. Если он возвращает что-то другое, __init__ не вызывается:

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

__dict__

Переменные инстанса хранятся внутри инстанса. По сути инстанс и представляет из себя набор этих данных — всё остальное живёт в типе.

Самый распространённый метод хранения данных инстанса — переменная __dict__. Это словарь, который живёт под капотом инстанса класса: ключи — строки с названиями полей, значения — значения соответсвующих переменных. Пример:

Обратите внимание на поведение class_var: она появилась в __dict__ только после того, как была определена у инстанса класса. До этого её в __dict__ не было, хотя она была доступна через a.class_var (и вернула бы единицу).

__dict__ — это не представление данных инстанса в формате словаря, они именно так и хранятся. Это полноценный словарь:

Это значит, что под капотом у него могут происходить хеширование, коллизии, увеличение адресного пространства и реаллокация — как у обычного словаря. Также это позволяет нам делать то же, что с обычным словарём, например удалять элементы. Вернёмся к предыдущему примеру:

У самого типа A тоже есть __dict__ — ведь он инстанс type:

У классов __dict__ — это не словарь, а mappingproxy. Он делает несколько полезных вещей: во-первых, убеждается, что ключи словаря — строки, а во-вторых делает его доступным только для чтения. Действительно, добавить элемент в такой словарь не выйдет:

А ещё это значит, что можно в __dict__ инстанса добавить ключ не-строку:

Зачем нужна такая возможность — непонятно, но у классов её нет.
Это сделано для большей стабильности работы интерпретатора и возможности использовать оптимизации — для них открывается больше простора, если __dict__ у класса доступен только для чтения. Больше можно почитать в баге Bypassing __dict__ readonlyness на python.org (там 2007 год, но в общем всё актуально).

Переменной __dict__ нет в двух случаях: если класс реализован на C
или если у класса объявлены слоты. К первому случаю относятся почти все встроенные типы, а о втором поговорим подробнее.

Если у класса определить __slots__, в котором перечислить все возможные поля инстанса, то пропадает смысл хранить их в словаре. Поэтому в таком случае под капотом у инстансов класса будет не словарь, а список.

Посмотреть, что происходит при использовании слотов можно в функции type_new. Если коротко, то над слотами проводятся проверки на их вменяемость, правильно считаются все ссылки для сборщика мусора и, наконец, создаётся список со значениями для инстанса.

Для демонстрации посмотрим, сколько памяти экономит использование слотов в простом случае. Чтобы посчитать полный размер объекта со всеми вложенными в него объектами (например, вместе с __dict__ и его ключами и значениями), используем модуль pympler:

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

Дескрипторы

Дескрипторы — объекты, которые умеют выполнять произвольный код, когда с ними происходят какие-то действия: доступ, изменение или удаление. Пример:

Обратите внимание, что после попытки удаления u.email он никуда не делся, просто был вызван метод __delete__ дескриптора.

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

Всё это работает благодаря методу __getattribute__: он находит нужный атрибут и проверяет, есть ли у него метод __get__. Если есть — вызывает его, если нет — возвращает сам атрибут.

Именно через механизм дескрипторов осуществляется доступ к атрибутам класса и инстанса. classmethod, staticmethod, property и super тоже работают благодаря дескрипторам.

Порядок поиска атрибутов

Из одного из предыдущих примеров мы узнали, что при доступе к атрибуту инстанса, сначала происходит его поиск в __dict__ инстанса, а потом — в __dict__ класса. Но это с переменными. Теперь давайте разбираться, что происходит с методами. Начнём с примера:

Ага, метод лежит в __dict__ класса. Получается, что когда мы говорим a.method, происходит type(a).__dict__['method']? Давайте проверим:

Получается, что в __dict__ лежит простая функция, а когда мы делаем a.method — это уже атрибут. Что превращает одно в другое?

Как раз это и делается дескриптором: каждый метод оборачивается в него. Таким образом, type(a).__dict__['method'] — дескриптор, а a.method– это уже результат его работы. Проверим нашу догадку:

Получается, что a.method под капотом превращается в type(a).__dict__['method'].__get__(a, None).

А что будет, если мы будем искать метод класса? У него ведь нет инстанса, есть только класс. За это отвечает декоратор classmethod и это — дескриптор, который при доступе прокидывает в первый аргумент функции класс, а не объект. Похожим образом действуют staticmethod и property.

super — это тоже дескриптор. При своём вызове он отправляется в __mro__ типа объекта и вызывает дескриптор нужного метода у подходящего класса. Если нужны детали реализации, их
можно посмотреть в функции super_getattro.

Превращение a.method в type(a).__dict__['method'].__get__(a, None) происходит внутри метода __getattribute__. Если его неправильно перегрузить — всё может сломаться наихудшим образом. Трогать __getattribute__ — почти всегда плохая идея.

До этого у нас был только один класс. А что, если их несколько и какие-то методы перегружены? Кто, когда и как догадается, метод какого именно класса нужно вызывать?

Структура наследования может быть сложной и запутанной. Её линеаризацией занимается алгоритм C3 внутри Python. В результате работы у классов появляется __mro__ — тупл из самого класса
и всех его родителей в том порядке, в котором нужно искать в них методы.

__getattribute__ остаётся только воспользоваться этим туплом и отыскать первый класс, в котором найдётся нужный метод. Этот код реализован на C, но на Python он бы выглядел примерно так:

Напоследок давайте поговорим о __del__, с ним тоже всё не так просто.

Правильное название для __del__ — финализатор, а не деструктор.
Деструктор уничтожает объект, а этим занимается не __del__, а сборщик мусора. __del__ вызывается перед этим. Иногда.

__del__ вызывает непосредственно сборщик мусора перед тем, как удалить объект. Из этого следует, что этот метод может быть вызван в любой момент выполнения программы.

__del__ не вызывается при наличии циклических зависимостей. Поиском и удалением таких объектов занимается циклический сборщик мусора, он не вызывает __del__. А вот начиная с Python 3.4 вызывается даже для циклических зависимостей, подробнее можно почитать в PEP-442. Можно избегать циклических зависимостей с помощью weakref, но чем больше проект — тем сложнее это делать.

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

Если внутри __del__ произошло исключение, то оно не будет прокинуто наружу. Вместо этого трейсбек отправится в stderr, но само исключение будет проигнорировано. Если вы хотите, например, залогировать такое исключение в Sentry, это придётся делать вручную внутри метода __del__ с помощью try.

Проблемы выше позволяют сформировать правило: не используйте __del__. Если вам нужен код, который в любом случае будет вызван, используйте with. Другой вариант — явно его вызвать с помощью try…finally. Тогда поведение финализатора будет предсказуемым, а код — более поддерживаемым.

Перестаньте писать классы / Хабр

Признак того, что объект не должен быть классом — если в нём всего 2 метода, и один из них — инициализация, __init__. Каждый раз видя это, подумайте: «наверное, мне нужна просто одна функция».

Каждый раз когда из написанного класса вы создаёте всего один экземпляр, используете только раз и тут же выбрасываете, следует думать: «ой, надо бы это отрефакторить! Можно сделать проще, намного проще!»

Перевод доклада Джэка Дидриха, одного из ключевых разработчиков языка Питон. Доклад прозвучал 9 марта 2012 на конференции PyCon US.

Все из вас читали Дзэн Питона, наверное много раз. Вот несколько пунктов из него:

  • Простое лучше сложного
  • Плоское лучше вложенного
  • Важна читаемость
  • Если программу трудно объяснить, она плохая
  • Если программу легко объяснить, возможно, она хорошá

Написал этот текст Тим Питерс. Он умнее и вас, и меня. Сколько вы знаете людей, в честь которых назвали алгоритм сортировки? Вот такой человек написал Дзэн Питона. И все пункты гласят: «Не делай сложно. Делай просто.» Именно об этом и пойдёт речь.

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

На своей работе я говорю коллегам: «Я ненавижу код, и хочу чтобы его было как можно меньше в нашем продукте.» Мы продаём функционал, не код. Покупатели у нас не из-за кода, а из-за широкого функционала. Каждый раз, когда код удаляется, это хорошо. Нас четверо, и в последний год мы перестали считать количество строк в продукте, но продолжаем вводить новый функционал.

Классы

Из этого доклада вам в первую очередь нужно запомнить вот этот код. Это крупнейшее злоупотребление классами, встречающееся в природе.

class Greeting(object):
    def __init__(self, word='Hello'):
        self.word = word

    def greet(self, name):
        return '%s, %s!' % (self.word, name)

>>> greeting = Greeting('Hola')
>>> greeting.greet('Jorge')
Hola, Jorge!

Это не класс, хотя он похож на класс. Имя — существительное, «приветствие». Он принимает аргументы и сохраняет их в __init__. Да, выглядит как класс. У него есть метод, читающий состояние объекта и делающий что-то ещё, как в классах. Внизу написано, как этим классом пользуются: создаём экземпляр Приветствия и затем используем это Приветствие чтобы сделать что-то ещё.

Но это не класс, или он не должен быть классом. Признак этого — в нём всего 2 метода, и один из них — инициализация, __init__. Каждый раз видя это, подумайте: «наверное, мне нужна просто одна функция».

Каждый раз когда из написанного класса вы создаёте всего один экземпляр, используете только раз и тут же выбрасываете, следует думать: «ой, надо бы это отрефакторить! Можно сделать проще, намного проще!»

def greet(name):
    ob = Greeting('превед')
    print ob.greet(name)
    return

Эта функция состоит из 4 строк кода. А вот как можно сделать то же самое всего за 2 строки:

def greet(greeting, name):
    return '%s, %s!' % (greeting, name)

import functools
greet = functools.partial(greet, 'превед')
greet('красавчик')

Если вы всё время вызываете функцию с тем же первым аргументом, стандартной библиотеке есть инструмент! functools.partial. Вот посмотрите в код выше: добавляете аргумент, и результат можно вызывать многократно.

Не знаю, у скольких из вас диплом в ИТ, у меня он есть. Я учил такие понятия как

— разделение полномочий
— уменьшение связанности кода
— инкапсуляция
— изоляция реализации

С тех пор как я закончил вуз, 15 лет я этих терминов не употреблял. Слыша эти слова, знайте, вас дурят. Эти термины сами по себе не требуются. Если их используют, люди имеют в виду совершенно разное, что только мешает разговору.

Пример: брюки превращаются…

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

Сторонняя библиотека API, назовём её ShaurMail, включала 1 пакет, 22 модуля, 20 классов и 660 строк кода. Мне пришлось всё это прочитать прежде чем включить в продукт. Но это был их официальный API, поэтому мы пользовались им. Каждый раз когда приходили обновления API, приходилось просматривать диффы, потому что было неизвестно, что они поменяли. Вы посылали патчи — а в обновлении они появились?

660 строк кода, 20 классов — это многовато, если программе нужно только дать список адресов электронной почты, текст письма и узнать, какие письма не доставлены, и кто отписался.

Что такое злоупотребление классами? Часто люди думают, что им понадобится что-то в будущем.… Не понадобится. Напишите всё, когда потребуется. В библиотеке ШаурМаил есть модуль ШаурХэш, в котором 2 строки кода:

class ShaurHash(dict):
    pass

Кто-то решил, что позже понадобится надстройка над словарём. Она не понадобилась, но везде в коде остались строки, как первая:

my_hash = ShaurMail.ShaurHash.ShaurHash(id='cat')

d = dict(id='cat')

d = {'id': 'cat'}

Вторая и третья строки кода — никому не нужно объяснять их. Но там везде повторялась эта мантра «ШаурМаил-ШаурХэш-ШаурХэш». Троекратное повторение слова «Шаур» — ещё один признак излишества. От повторений всем только вред. Вы раздражаете пользователя, заставляя его писать «Шаур» три раза. (Это не настоящее имя компании, а вымышленное.)

Потом они уволили этого парня и наняли того, кто знал, что делает. Вот вторая версия API:

class API:
    def __init__(self, key):
        self.header = dict(apikey=key)

    def call(self, method, params):
        request = urllib2.Request(
            self.url + method[0] + '/' + method[1],
            urllib.urlencode(params),
            self.header
        )
        try:
            response = json.loads(urllib2.urlopen(request).read())
            return response
        except urllib2.HTTPError as error:
            return dict(Error=str(error))

В той было 660 строк, в этой — 15. Всё, что делает этот код — пользуется методами стандартной библиотеки. Он читается целиком, легко, за секунды, и можно сразу понять, что он делает. Кстати, в нём был ещё набор тестов из 20 строк. Вот как надо писать. Когда они обновляли API, я мог прочесть изменения буквально за пару секунд.

Но и здесь можно заметить проблему. В классе два метода, и один из них — __init__. Авторы этого не скрывали. Второй метод — call, «вызвать». Вот как этим API пользоваться:

ShaurMail.API(key='СЕКРЕТНЫЙ КЛЮЧ').call(('mailing', 'statistics'), {'id': 1})

Строка длинная, поэтому мы делаем алиас и вызываем его многократно:

ShaurMail.request = ShaurMail.API(key='СЕКРЕТНЫЙ КЛЮЧ').call
ShaurMail.request(('mailing', 'statistics'), {'id': 1})

Заметьте, мы пользуемся этим классом как функцией. Ею он и должен быть. Если видите подобное, знайте, класс тут не нужен. Поэтому я послал им третью версию API:

ShaurMail_API = url = 'https://api.shaurmail.com/%s/%s'
ShaurMail_API_KEY = 'СЕКРЕТНЫЙ КЛЮЧ'

def request(noun, verb, **params):
    headers = {'apikey': ShaurMail_API_KEY}
    request = urllib2.Request(ShaurMail_API % (noun, verb),
                              urllib.urlencode(params), headers)
    return json.loads(urllib2.urlopen(request).read())

Он вообще не создаёт файлов в нашем проекте, потому что я вставил его в тот модуль, где он используется. Он делает всё, что делал 15-строковый API, и всё, что делал 660-строковый API.

Вот с чего мы начали и к чему пришли:

  • 1 пакет + 20 модулей => 1 модуль
  • 20 классов => 1 класс => 0 классов
  • 130 методов => 2 метода => 1 функция
  • 660 строк кода => 15 строк => 5 строк

Легче пользоваться, легче писать, никому не надо выяснять, что происходит.

Стандартная библиотека

Кто пришёл из языка Java, возможно, считает, что пространства имён нужны для таксономии. Это неверно. Они нужны чтобы предотвратить совпадения имён. Если у вас глубокие иерархии пространств, это никому ничего не даёт. ShaurMail.ShaurHash.ShaurHash — всего лишь лишние слова, которые людям надо помнить и писать.

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

К нашему стыду, вот пример из нашего же кода, и те же грехи видно и здесь:

services.crawler.crawlerexceptions.ArticleNotFoundException

Пакет, в котором модуль из 2 строк, класс исключения и «pass». Чтобы использовать это исключение, надо дважды написать «crawler», дважды слово «exception». Имя ArticleNotFoundException само себя повторяет. Так не надо. Если вы называете исключения, пусть это будет EmptyBeer, BeerError, BeerNotFound, но BeerNotFoundError — это уже много.

Можно просто пользоваться исключениями из стандартной библиотеки. Они понятны всем. Если только вам не нужно выловить какое-то специфическое состояние, LookupError вполне подойдёт. Если вы получили отлуп по почте, всё равно придётся его читать, поэтому неважно, как называется исключение.

Кроме того, исключения в коде обычно идут после raise и except, и всем сразу понятно, что это исключения. Поэтому не нужно добавлять «Exception» в название.

В стандартной библиотеке Питона есть и ржавые детали, но она — очень хороший пример организации кода:

  • 200 000 строк кода
  • 200 модулей верхнего уровня
  • в среднем по 10 файлов в пакете
  • 165 исключений

10 файлов в пакете — это много, но только из-за некоторых сторонних проектов, добавленных в библиотеку, где были пакеты из всего 2 файлов. Если вам вздумается создать новое исключение, подумайте лучше, ведь в стандартной библиотеке обошлись 1 исключением на 1200 строк кода.

Я не против классов в принципе. Классы бывают нужны — когда много меняющихся данных и связанных с ними функций. Однако в каждодневной работе такое бывает нечасто. Регулярно приходится работать со стандартной библиотекой, а там уже есть подходящие классы. За вас их уже написали.

Единственное исключение в библиотеке Питона — модуль heapq. Heap queue, «очередь в куче» — это массив, который всегда отсортирован. В модуле heapq десяток методов, и они все работают с той же «кучей». Первый аргумент всегда остаётся тем же, что значит, здесь действительно напрашивается класс.

heapify(data)
pushleft(data, item)
popleft(data)
pushright(data, item)
popright(data)

и т.д.

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

class Heap(object):
    def __init__(self, data=None, key=lambda x: None):
        self.heap = data or []
        heapq.heapify(self.heap)
        self.key = key

    def pushleft(self, item):
        if self.key:
            item = (self.key(item), item)
        heapq.pushleft(self.heap, item)

    def popleft(self):
        return heapq.popleft(self.heap)[1]
Классы разрастаются как сорняки

Состояние OAuth в Питоне — неважное. Опять же, есть сторонние библиотеки, и прежде чем использовать в своём проекте, их нужно прочесть.

Я пытался использовать сокращатель урлов от Гугла: мне нужно было взять урлы и просто сократить их. У Гугла есть проект, в котором 10 000 строк кода. 115 модулей и 207 классов. Я написал отповедь об этом в Гугле+, но мало кто её видел, а Гвидо (Ван Россум — прим. пер.) прокомментировал: «Я снимаю с себя ответственность за гугловский код API.» 10 000 строк кода — там же обязательно найдётся какая-нибудь дрянь вроде ШаурМэйла. Вот, например, класс Flow («поток»), от которого наследуют другие.

class Flow(object):
    pass


class Storage(object):
    def put(self, data): _abstract()
    def get(self): _abstract()

def _abstract(): raise NotImplementedError

Он пустой. Но у него есть свой модуль, и каждый раз читая наследующий его класс, надо сходить, проверить тот файл и снова убедиться, что тот класс пуст. Кто-то глядел в будущее и решил: «Напишу-ка я 3 строчки кода сейчас, чтобы в будущем эти 3 строчки не менять.» И отнял время у всех, кто читает его библиотеку. Есть ещё класс Хранилище, (Storage) который почти ничего не делает. В нём правильно обрабатываются ошибки, используя стандартные исключения, но им делают алиасы, и опять же нужно ходить читать их код, чтобы выяснить, как это работает.

Чтобы внедрить OAuth3 мне понадобилась неделя. Пару дней заняло чтение десяти тысяч строк кода, после чего я стал искать другие библиотеки. Нашёл python-oauth3. Это вторая версия python-oauth, но она на самом деле не умеет работать с OAuth3, что не сразу удалось выяснить. Впрочем, эта библиотека немного лучше гугловской: только 540 строк и 15 классов.

Я переписал её ещё проще и назвал python-foauth3. 135 строк кода и 3 класса, и то всё равно много, я не достаточно её отрефакторил. Вот один из этих трёх классов:

class Error(Exception):
    pass

Срамота!

Жизнь

Последний пример. Все вы видели игру «Жизнь» Конвэя, даже если не знаете её имени. Есть клетчатое поле, каждый ход вы считаете для каждой клетки соседние, и в зависимости от них она будет либо живой, либо мёртвой. И получаются такие красивые устойчивые узоры, как планер: клетки впереди оживают, а сзади умирают, и планер как будто летит по полю.

Игра «жизнь» очень проста: поле и пара правил. Мы задаём эту задачу на собеседовании, потому что если вы не умеете такого — нам не о чем разговаривать. Многие сразу же говорят «Клетка — существительное. Класс надо.» Какие свойства в этом классе? Место, живая или нет, состояние в следующий ход, всё? Ещё есть соседи. Потом начинают описывать поле. Поле — это множество клеток, поэтому это сетка, у неё метод «подсчитать», который обсчитывает клетки внутри.

class Cell(object):
    def __init__(self, x, y, alive=True):
        self.x = x
        self.y = y
        self.alive = alive
        self.next = None

    def neigbors(self):
        for i, j in itertools.product(range(-1, 2), repeat=2):
            if (i, j) != (0, 0):
                yield (self.x + i, self.y + j)


class Board(object):
    def __init__(self):
        self.cells = {} # { (x, y): Cell() }

    def advance(self):
        for (x, y), cell in self.cells.items():
            alive_neighbors = len(cell.neighbors)
            cell.next = (alive_neighbors == 3 or (alive_neighbors == 2 and cell.alive))

На этом месте надо сказать «стоп»: у нас есть класс Поле, в котором 2 метода: __init__ и «сделать ход». В нём одно свойство — словарь, значит со словарём и надо работать. Заметьте, что не надо хранить соседей точки, они уже и так есть в словаре. Живая точка или нет — это просто булево значение, поэтому будем хранить координаты только живых клеток. А раз в словаре хранятся только True, нужен не словарь а просто множество (set) координат. Наконец, новое состояние не нужно, можно просто заново создать список живых клеток.

def neigbors(point):
    x, y = point
    for i, j in itertools.product(range(-1, 2), repeat=2):
        if any((i, j)):
            yield (x + i, y + j)

def advance(board):
    newstate = set()
    recalc = board | set(itertools.chain(*map(neighbors, board)))
    for point in recalc:
        count = sum((neigh in board)
                for neigh in neighbors(point))
        if count == 3 or (count == 2 and point in board):
            newstate.add(point)

    return newstate

glider = set([(0, 0), (1, 0), (2, 0), (0, 1), (1, 2)])
for i in range(1000):
    glider = advance(glider)
print glider

Получается очень простая, сжатая реализация игры. Двух классов тут не надо. Внизу — координаты планера, их вставляют в поле, и планер летит. Всё. Это полная реализация игры «жизнь».

Резюме

1. Если вы видите класс с двумя методами, включая __init__, это не класс.
2. Не создавайте новых исключений, если они не нужны (а они не нужны).
3. Упрощайте жёстче.

От переводчика: в комментариях я вижу, что многие восприняли доклад как полное отрицание ООП. Это ошибка. Пункт 1 из итогов чётко говорит, что такое не класс. Классы нужны, но суть доклада — в том, что не нужно ими злоупотреблять.

построение классов и определение объектов в Python 3

Python — это объектно-ориентированный язык программирования. Одним из наиболее важных понятий в ООП является различие между классами и объектами:

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

Классы похожи на образец или прототип для создания объектов. Они определяются с помощью ключевого слова class.

Давайте определим класс Shark, который имеет две связанные с ним функции:

shark.py

classShark:
defswim(self):
        print("The shark is swimming.")

defbe_awesome(self):
        print("The shark is being awesome.")

Поскольку эти функции объявлены под классом Shark, они называются методами. Методы — это особый вид функции, который определен внутри класса.

Аргументом этих функций является слово self, которое является ссылкой на объекты, созданные на основе этого класса. Чтобы ссылаться на экземпляры (или объекты) класса, self всегда будет первым параметром.

Определение этого класса создает шаблон для объекта Shark, который мы сможем определить позже. То есть, если вы запустите программу, приведенную выше, ничего не будет возвращено.

Объект является экземпляром класса. Мы можем взять класс Shark, определенный выше, и использовать его для создания объекта или экземпляра класса.

Создадим объект Shark с именем sammy:

Мы инициализировали объект sammy как экземпляр класса, установив его равным Shark().

Теперь используем два метода с объектом Shark sammy:

sammy = Shark()
sammy.swim()
sammy.be_awesome()

Объект sammy использует два метода — swim() и be_awesome(). Мы вызвали их с помощью оператора «.», который ссылается на атрибут объекта. В этом случае атрибут является методом, и он вызывается с круглыми скобками.

Поскольку ключевое слово self было параметром методов класса Shark, объект sammy передается методам. Параметр self позволяет методам ссылаться на атрибуты объекта.

Но когда мы вызываем методы, в круглых скобках ничего не передается. Объект sammy автоматически передается с помощью оператора «.» (точки).

Добавим объект в программе:

shark.py

classShark:
defswim(self):
        print("The shark is swimming.")

defbe_awesome(self):
        print("The shark is being awesome.")


defmain():
    sammy = Shark()
sammy.swim()
sammy.be_awesome()

if __name__ == "__main__":
    main()

Запустим программу, чтобы узнать, что она делает:

python shark.py

Вывод
The shark is swimming.
The shark is being awesome.

Объект sammy вызывает два метода в функции main(), указывая запустить эти методы.

Для инициализации данных используется метод конструктора. Он запускается, когдасоздается объект класса. Он также известен, как метод __init__. Это будет первое определение класса:

classShark:
def__init__(self):
        print("This is the constructor method.")

Если в наш пример добавить к классу Shark указанный метод__init__, программа выведет следующее без внесения изменений в экземпляр sammy:

Вывод
This is the constructor method.
The shark is swimming.
The shark is being awesome.

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

Создадим метод конструктора, который использует переменную name. Ее мы применим для присвоения имен объектам. Мы передадим name в качестве параметра и зададим self.name, равное name:

shark.py

classShark:
def__init__(self, name):
        self.name = name

Можно изменить строки в функциях, чтобы ссылаться на имена:

shark.py

classShark:
def__init__(self, name):
        self.name = name

defswim(self):
# Ссылка на имя
        print(self.name + " is swimming.")

defbe_awesome(self):
# Ссылка на имя
        print(self.name + " is being awesome.")

Также мы можем установить имя объекта Shark sammy равным «Sammy», передав его, как параметр класса Shark:

shark.py

classShark:
def__init__(self, name):
        self.name = name

defswim(self):
        print(self.name + " is swimming.")

defbe_awesome(self):
        print(self.name + " is being awesome.")


defmain():
# Устанавливаем имя объекта Shark
    sammy = Shark("Sammy")
sammy.swim()
sammy.be_awesome()

if __name__ == "__main__":
    main()

Теперь запустим программу:

python shark.py

Вывод
Sammy is swimming.
Sammy is being awesome.

Имя, которое мы передали объекту, выводится. Мы определили метод __init__ с именем параметра (вместе с ключевым словом self) и передали переменную внутри метода.

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

Если нужно было бы добавить еще один параметр, например возраст, мы могли сделать это, передав его методу __init__:

classShark:
def__init__(self, name, age):
        self.name = name
self.age = age

При создании объекта sammy можно передать возраст Сэмми:

sammy = Shark("Sammy", 5)

Чтобы использовать параметр age, также нужно будет создать метод в классе. Методы конструктора позволяют инициализировать определенные атрибуты объекта.

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

shark.py

classShark:
def__init__(self, name):
        self.name = name

defswim(self):
        print(self.name + " is swimming.")

defbe_awesome(self):
        print(self.name + " is being awesome.")

defmain():
    sammy = Shark("Sammy")
sammy.be_awesome()
    stevie = Shark("Stevie")
stevie.swim()

if __name__ == "__main__":
  main()

Мы создали второй объект Shark с именем stevie, и передали ему имя «Stevie». В этом примере используются методы be_awesome() с sammy и метод swim() со stevie.

Запустим программу:

python shark.py

Вывод
Sammy is being awesome.
Stevie is swimming.

Результат показывает, что мы используем два разных объекта: объект sammy и объект stevie. Они оба относятся к классу Shark.

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

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

Данная публикация является переводом статьи «How To Construct Classes and Define Objects in Python 3» , подготовленная редакцией проекта.

Классы — Intermediate Python

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

Большинство начинающих, а иногда и опытных Python разработчиков не понимают до конца различия между переменными экземпляра и переменными класса. Это приводит к некорректному использованию этих различных типов переменных. Давайте разберемся.

Основное различие:

  • Переменные экземпляров предназначены для данных уникальных для каждого

    объекта

  • Переменные класса — для общих для всех экземпляров класса данных

Посмотрим на пример:

class Cal(object):

pi = 3.142

def __init__(self, radius):

self.radius = radius

def area(self):

return self.pi * (self.radius ** 2)

a = Cal(32)

a.area()

a.pi

a.pi = 43

a.pi

b = Cal(44)

b.area()

b.pi

b.pi = 50

b.pi

С использованием изменяемых переменных класса редко бывают проблемы. Поэтому разработчики обычно не стараются изучить тему подробнее — все и так работает! Если вы тоже уверены в надежности использования таких переменных, то следующий пример для вас:

class SuperClass(object):

superpowers = []

def __init__(self, name):

self.name = name

def add_superpower(self, power):

self.superpowers.append(power)

foo = SuperClass('foo')

bar = SuperClass('bar')

foo.name

bar.name

foo.add_superpower('fly')

bar.superpowers

foo.superpowers

Красота неправильного использования изменяемых переменных класса. Для предотвращения подобных ошибок — не храните изменяемые структуры данных в переменных класса или понимайте зачем вам это нужно.

Классы нового стиля были представлены в Python 2.1, но не так много разработчиков знает о них даже сейчас! Отчасти это связано с сохранением поддержки классов старого стиля для обратной совместимости. Давайте рассмотрим разницу между двумя стилями:

  • Классы старого стиля ничему не наследуют

  • Классы нового стиля наследуют object

Базовый пример:

class OldClass():

def __init__(self):

print('Я старый класс')

class NewClass(object):

def __init__(self):

print('Я новый модный класс')

old = OldClass()

new = NewClass()

Наследование от object дает новым классам доступ к определенной магии. Например, вы можете использовать оптимизацию __slots__, вызов super() и дескрипторы. Резюме? Всегда используйте классы нового стиля.

Примечание: в Python 3 все классы нового стиля. Не важно наследует ли ваш класс object или нет. Тем не менее, хорошей идеей будет явно прописывать наследование.

Классы в Python известны за свои магические методы, их отличительная черта — двойное нижнее подчеркивание с двух сторон от имени. Давайте рассмотрим несколько из них.

Это конструктор класса. Конструктор вызывается каждый раз при создании экземпляра класса. Например:

class GetTest(object):

def __init__(self):

print('Приветствую!')

def another_method(self):

print('Я другой метод, который не вызывается автоматически')

a = GetTest()

a.another_method()

Как вы видите __init__ вызывается при создании экземпляра класса. Вы также можете передавать аргументы конструктору для инициализации экземпляра:

class GetTest(object):

def __init__(self, name):

print('Приветствую! {0}'.format(name))

def another_method(self):

print('Я другой метод, который не вызывается автоматически')

a = GetTest('Yasoob')

b = GetTest()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: __init__() takes exactly 2 arguments (1 given)

Надеюсь в общих чертах логика работы __init__ понятна.

Реализация метода __getitem__ в классе позволяет использовать на его экземплярах [] оператор. Пример:

class GetTest(object):

def __init__(self):

self.info = {

'name':'Yasoob',

'country':'Pakistan',

'number':12345812

}

def __getitem__(self,i):

return self.info[i]

foo = GetTest()

foo['name']

foo['number']

Без реализации __getitem__ вы бы получили следующую ошибку:

>>> foo['name']

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: 'GetTest' object has no attribute '__getitem__'

классов Python


Классы / объекты Python

Python — это объектно-ориентированный язык программирования.

Почти все в Python представляет собой объект со своими свойствами и методами.

Класс похож на конструктор объекта или «план» для создания объектов.


Создать класс

Чтобы создать класс, используйте ключевое слово class :

Пример

Создайте класс MyClass со свойством x:

класс MyClass:
x = 5

Попробуй сам »

Создать объект

Теперь мы можем использовать класс MyClass для создания объектов:

Пример

Создайте объект с именем p1 и распечатайте значение x:

p1 = MyClass ()
печать (p1.х)

Попробуй сам »

Функция __init __ ()

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

Чтобы понять значение классов, мы должны понимать встроенный __init __ () функция.

Все классы имеют функцию __init __ (), которая всегда выполняется, когда занятие инициируется.

Используйте функцию __init __ () для присвоения значений свойствам объекта или другим операции, которые необходимо выполнить, когда объект создается:

Пример

Создайте класс с именем Person, используйте функцию __init __ () для присвоения значений для имени и возраста:

class Person:
def __init __ (я, имя, возраст):
себя.name = name
self.age = age

p1 = Person («Джон», 36)

печать (p1.name)
print (p1.age)

Попробуй сам »

Примечание: Функция __init __ () вызывается автоматически каждый раз, когда класс используется для создания нового объекта.



Методы объекта

Объекты также могут содержать методы. Методы в объектах — это функции, которые принадлежат объекту.

Создадим метод в классе Person:

Пример

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

class Person:
def __init __ (я, имя, возраст):
себя.name = name
self.age = age

def myfunc (self):
print («Привет, меня зовут» + self.name)

p1 = Person («Джон», 36)
p1.myfunc ()

Попробуй сам »

Примечание: Параметр self является ссылкой на текущий экземпляр класса и используется для доступа к переменным, принадлежащим этому классу.


Самостоятельный параметр

Параметр self является ссылкой на текущий экземпляр класса и используется для доступа к переменным, принадлежащим этому классу.

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

Пример

Используйте слова mysillyobject и abc вместо self :

класс Person:
def __init __ (mysillyobject, name, age):
mysillyobject.name = name
mysillyobject.age = age

def myfunc (abc):
print («Привет, меня зовут» + abc.имя)

p1 = Человек («Джон», 36)
p1.myfunc ()

Попробуй сам »

Изменить свойства объекта

Вы можете изменять свойства объектов следующим образом:


Удалить свойства объекта

Вы можете удалять свойства объектов с помощью дель ключевое слово:


Удалить объекты

Вы можете удалять объекты с помощью ключевого слова del :


Пропуск Заявление

определения класса не могут быть пустыми, но если у вас по какой-то причине есть определение класса без содержимого, введите оператор pass , чтобы избежать ошибки.



Объектно-ориентированное программирование (ООП) в Python 3 — Real Python

Объектно-ориентированное программирование (ООП) — это метод структурирования программы путем объединения связанных свойств и поведения в отдельные объекты . В этом руководстве вы изучите основы объектно-ориентированного программирования на Python.

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

Объект содержит данные, такие как необработанные или предварительно обработанные материалы на каждом этапе сборочной линии, и поведение, такое как действие, которое выполняет каждый компонент сборочной линии.

Из этого руководства вы узнаете, как:

  • Создайте класс , который похож на чертеж для создания объекта
  • Используйте классы для создания новых объектов
  • Модельные системы с наследованием классов

Примечание: Это руководство адаптировано из главы «Объектно-ориентированное программирование (ООП)» в книге Основы Python: Практическое введение в Python 3 .

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

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

Объектно-ориентированное программирование — это парадигма программирования, которая предоставляет средства структурирования программ таким образом, чтобы свойства и поведение объединялись в отдельные объекты .

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

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

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

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

Определите класс в Python

Примитивные структуры данных, такие как числа, строки и списки, предназначены для представления простых частей информации, таких как стоимость яблока, название стихотворения или ваши любимые цвета, соответственно. Что, если вы хотите изобразить что-то более сложное?

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

Один из способов сделать это — представить каждого сотрудника в виде списка:

  kirk = ["Джеймс Кирк", 34, "Капитан", 2265]
spock = ["Спок", 35, "Научный сотрудник", 2254]
mccoy = ["Леонард Маккой", "Главный врач", 2266]
  

У этого подхода есть ряд проблем.

Во-первых, это может затруднить управление большими файлами кода. Если вы укажете kirk [0] на несколько строк от того места, где объявлен список kirk , запомните ли вы, что элемент с индексом 0 — это имя сотрудника?

Во-вторых, это может привести к ошибкам, если не у всех сотрудников одинаковое количество элементов в списке.В приведенном выше списке mccoy возраст отсутствует, поэтому mccoy [1] вернет «Главный врач» вместо возраста доктора Маккой.

Отличный способ сделать этот тип кода более управляемым и более удобным в обслуживании — это использовать классы .

Классы и экземпляры

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

В этом руководстве вы создадите класс Dog , в котором будет храниться некоторая информация о характеристиках и поведении отдельной собаки.

Класс — это образец того, как что-то должно быть определено. На самом деле он не содержит никаких данных. Класс Dog указывает, что имя и возраст необходимы для определения собаки, но он не содержит имени или возраста какой-либо конкретной собаки.

В то время как класс является планом, экземпляр представляет собой объект, созданный из класса и содержащий реальные данные.Экземпляр класса Dog больше не является чертежом. Это настоящая собака с таким именем, как Майлз, которому четыре года.

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

Как определить класс

Все определения классов начинаются с ключевого слова class , за которым следует имя класса и двоеточие.Любой код с отступом под определением класса считается частью тела класса.

Вот пример класса Dog :

Тело класса Dog состоит из одного оператора: ключевого слова pass . Pass часто используется как заполнитель, указывающий, куда в конечном итоге пойдет код. Это позволяет запускать этот код, не вызывая ошибки Python.

Примечание: Имена классов Python записываются в нотации CapitalizedWords по соглашению.Например, класс для определенной породы собак, такой как джек-рассел-терьер, будет записан как JackRussellTerrier .

Класс Dog сейчас не очень интересен, поэтому давайте немного его украсим, определив некоторые свойства, которые должны иметь все объекты Dog . Мы можем выбирать из ряда свойств, включая имя, возраст, цвет шерсти и породу. Для простоты мы будем использовать имя и возраст.

Свойства, которые должны иметь все объекты Dog , определены в методе .__init __ () . Каждый раз, когда создается новый объект Dog , .__ init __ () устанавливает начальное состояние объекта, присваивая значения свойствам объекта. То есть .__ init __ () инициализирует каждый новый экземпляр класса.

Вы можете указать .__ init __ () любое количество параметров, но первым параметром всегда будет переменная с именем self . Когда создается новый экземпляр класса, он автоматически передается в параметр self в .__init __ () , чтобы для объекта можно было определить новые атрибуты .

Давайте обновим класс Dog с помощью метода .__ init __ () , который создает атрибуты .name и .age :

  класс Собака:
    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст
  

Обратите внимание, что подпись метода .__ init __ () имеет отступ в четыре пробела. В теле метода есть восемь пробелов.Этот отступ жизненно важен. Он сообщает Python, что метод .__ init __ () принадлежит классу Dog .

В теле .__ init __ () есть два оператора, использующие переменную self :

  1. self.name = name создает атрибут с именем name и присваивает ему значение параметра name .
  2. self.age = age создает атрибут с именем age и присваивает ему значение параметра age .

Атрибуты, созданные в .__ init __ () , называются атрибутами экземпляра . Значение атрибута экземпляра зависит от конкретного экземпляра класса. Все объекты Dog имеют имя и возраст, но значения атрибутов name и age будут различаться в зависимости от экземпляра Dog .

С другой стороны, атрибуты класса являются атрибутами, которые имеют одинаковое значение для всех экземпляров класса.Вы можете определить атрибут класса, присвоив значение имени переменной вне .__ init __ () .

Например, следующий класс Dog имеет атрибут класса под названием разновидностей со значением "Canis knownis" :

  класс Собака:
    # Атрибут класса
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст
  

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

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

Теперь, когда у нас есть класс Dog , давайте создадим собак!

Создание экземпляра объекта в Python

Откройте интерактивное окно IDLE и введите следующее:

>>>
  >>> класс Собака:
...     проходить
  

Это создает новый класс Dog без атрибутов или методов.

Создание нового объекта из класса называется созданием экземпляра объекта. Вы можете создать новый объект Dog , введя имя класса, а затем открывающие и закрывающие круглые скобки:

>>>
  >>> Собака ()
<__ main__.Dog объект в 0x106702d30>
  

Теперь у вас есть новый объект Dog по адресу 0x106702d30 .Эта забавно выглядящая строка букв и цифр представляет собой адрес памяти , который указывает, где в памяти вашего компьютера хранится объект Dog . Обратите внимание, что адрес, который вы видите на экране, будет другим.

Теперь создайте второй объект Dog :

>>>
  >>> Собака ()
<__ main__.Dog объект по адресу 0x0004ccc90>
  

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

Чтобы увидеть это по-другому, введите следующее:

>>>
  >>> a = Собака ()
>>> b = Собака ()
>>> а == б
Ложь
  

В этом коде вы создаете два новых объекта Dog и назначаете их переменным a и b . Когда вы сравниваете a и b с помощью оператора == , результатом будет False . Несмотря на то, что a и b являются экземплярами класса Dog , они представляют два различных объекта в памяти.

Атрибуты класса и экземпляра

Теперь создайте новый класс Dog с атрибутом класса .species и двумя атрибутами экземпляра с именем .name и .age :

. >>>
  >>> класс Собака:
... разновидности = "Canisiliaris"
... def __init __ (я, имя, возраст):
... self.name = имя
... self.age = возраст
  

Чтобы создать экземпляры объектов этого класса Dog , необходимо указать значения для name и age .Если вы этого не сделаете, Python выдаст TypeError :

. >>>
  >>> Собака ()
Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
    Собака()
TypeError: __init __ () отсутствует 2 обязательных позиционных аргумента: 'name' и 'age'
  

Чтобы передать аргументы в параметры name и age , поместите значения в круглые скобки после имени класса:

>>>
  >>> buddy = Dog ("Приятель", 9)
>>> miles = Dog ("Мили", 4)
  

Это создает два новых экземпляра Dog — один для девятилетней собаки по имени Бадди и один для четырехлетней собаки по имени Майлз.

Метод .__ init __ () класса Dog имеет три параметра, так почему в этом примере ему передаются только два аргумента?

Когда вы создаете экземпляр объекта Dog , Python создает новый экземпляр и передает его первому параметру .__ init __ () . Это по существу удаляет параметр self , поэтому вам нужно беспокоиться только о параметрах name и age .

После создания экземпляров Dog вы можете получить доступ к их атрибутам экземпляров, используя точечную нотацию :

>>>
  >>> дружище.название
'Приятель'
>>> buddy.age
9

>>> miles.name
'Майлз'
>>> miles.age
4
  

Таким же образом можно получить доступ к атрибутам класса:

>>>
  >>> buddy.species
'Canis familis'
  

Одним из самых больших преимуществ использования классов для организации данных является то, что экземпляры гарантированно будут иметь ожидаемые атрибуты. Все экземпляры Dog имеют атрибуты .species , .name и .age , поэтому вы можете использовать эти атрибуты с уверенностью, зная, что они всегда будут возвращать значение.

Хотя наличие атрибутов гарантировано, их значения могут быть изменены динамически:

>>>
  >>> buddy.age = 10
>>> buddy.age
10

>>> miles.species = "Felis silvestris"
>>> miles.species
'Felis silvestris'
  

В этом примере вы изменяете атрибут .age объекта buddy на 10 . Затем вы меняете атрибут .species объекта miles на "Felis silvestris" , который является разновидностью кошки.Это делает Майлза довольно странной собакой, но это настоящий Python!

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

Методы экземпляра

Методы экземпляра — это функции, которые определены внутри класса и могут быть вызваны только из экземпляра этого класса. Как и .__ init __ () , первым параметром метода экземпляра всегда является self .

Откройте новое окно редактора в IDLE и введите следующий класс Dog :

  класс Собака:
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст

    # Метод экземпляра
    def описание (self):
        return f "{self.name} {self.age} лет"

    # Другой метод экземпляра
    def говорить (сам, звук):
        return f "{self.name} говорит {звук}"
  

Этот класс Dog имеет два метода экземпляра:

  1. .description () возвращает строку, отображающую имя и возраст собаки.
  2. .speak () имеет один параметр с именем sound и возвращает строку, содержащую имя собаки и звук, который издает собака.

Сохраните измененный класс Dog в файл с именем dog.py и нажмите F5 , чтобы запустить программу. Затем откройте интерактивное окно и введите следующее, чтобы увидеть методы вашего экземпляра в действии:

>>>
  >>> miles = Dog ("Мили", 4)

>>> миль.описание()
"Майлзу 4 года"

>>> miles.speak ("Гав-гав")
'Майлз говорит Гав-Гав'

>>> miles.speak ("Bow Wow")
"Майлз говорит:" Вау! "
  

В приведенном выше классе Dog .description () возвращает строку, содержащую информацию об экземпляре Dog miles . При написании собственных классов рекомендуется иметь метод, возвращающий строку, содержащую полезную информацию об экземпляре класса. Однако .description () — не самый питонский способ сделать это.

Когда вы создаете объект list , вы можете использовать print () для отображения строки, которая выглядит как список:

>>>
  >>> names = ["Флетчер", "Дэвид", "Дэн"]
>>> печать (имена)
["Флетчер", "Дэвид", "Дэн"]
  

Давайте посмотрим, что произойдет, если вы напечатаете () объект миль :

>>>
  >>> печать (мили)
<__ main__.Объект "Собака" по адресу 0x00aeff70>
  

Когда вы печатаете (миль) , вы получаете загадочное сообщение о том, что миль — это объект Dog по адресу памяти 0x00aeff70 . Это сообщение бесполезно. Вы можете изменить то, что печатается, определив специальный метод экземпляра с именем .__ str __ () .

В окне редактора измените имя метода .description () класса Dog на .__str __ () :

  класс Собака:
    # Остальные части класса Dog оставьте как есть

    # Замените .description () на __str __ ()
    def __str __ (сам):
        return f "{self.name} {self.age} лет"
  

Сохраните файл и нажмите F5 . Теперь, когда вы напечатаете (миль) , вы получите гораздо более удобный результат:

>>>
  >>> miles = Dog ("Мили", 4)
>>> печать (мили)
"Майлзу 4 года"
  

Такие методы, как .__init __ () и .__ str __ () называются методами dunder , потому что они начинаются и заканчиваются двойным подчеркиванием. Есть много ужасных методов, которые вы можете использовать для настройки классов в Python. Хотя это слишком сложная тема для начинающей книги по Python, понимание dunder-методов является важной частью освоения объектно-ориентированного программирования на Python.

В следующем разделе вы увидите, как расширить свои знания и создать классы из других классов.

Проверьте свое понимание

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

Создайте класс Car с двумя атрибутами экземпляра:

  1. .color , в котором название цвета автомобиля хранится в виде строки
  2. . Пробег , в котором количество миль на автомобиле сохраняется в виде целого числа

Затем создайте экземпляры двух объектов Car — синего автомобиля с пробегом 20 000 миль и красного автомобиля с пробегом 30 000 миль — и распечатайте их цвета и пробег.Ваш результат должен выглядеть так:

  У синей машины 20 000 миль.
У красной машины 30 000 миль.
  

Вы можете развернуть блок ниже, чтобы увидеть решение:

Сначала создайте класс Car с атрибутами экземпляра .color и .m:

  класс Автомобиль:
    def __init __ (сам, цвет, пробег):
        self.color = цвет
        собственный пробег = пробег
  

Цвет и пробег параметры .__init __ () присваиваются self.color и self.m, что создает два атрибута экземпляра.

Теперь вы можете создать два экземпляра Car :

  blue_car = Автомобиль (цвет = "синий", пробег = 20_000)
red_car = Автомобиль (цвет = "красный", пробег = 30_000)
  

Экземпляр blue_car создается путем передачи значения «синий» в параметр цвет и 20_000 в параметр пробег .Аналогичным образом создается red_car со значениями «красный» и 30_000 .

Чтобы распечатать цвет и пробег каждого объекта Car , вы можете перебрать кортеж , содержащий оба объекта:

  на машину в (blue_car, red_car):
    print (f "У машины {car.color} {car.m900 :,} миль")
  

Строка f в приведенном выше цикле для вставляет атрибуты .color и .m gather в строку и использует спецификатор формата :, для печати пробега, сгруппированного по тысячам и разделенных запятой.

Окончательный результат выглядит так:

  У синей машины 20 000 миль.
У красной машины 30 000 миль.
  

Когда будете готовы, можете переходить к следующему разделу.

Наследование от других классов в Python

Наследование — это процесс, при котором один класс перенимает атрибуты и методы другого. Вновь сформированные классы называются дочерними классами , а классы, производные от дочерних классов, называются родительскими классами .

Дочерние классы могут переопределять или расширять атрибуты и методы родительских классов. Другими словами, дочерние классы наследуют все атрибуты и методы родительского объекта, но могут также определять атрибуты и методы, которые уникальны для них самих.

Хотя аналогия не идеальна, вы можете думать о наследовании объектов как о генетическом наследовании.

Возможно, вы унаследовали цвет волос от матери. Это атрибут, с которым вы родились. Допустим, вы решили покрасить волосы в фиолетовый цвет.Предполагая, что у вашей матери нет фиолетовых волос, вы просто переопределили атрибут цвета волос, который вы унаследовали от своей мамы.

Вы также в некотором смысле наследуете свой язык от своих родителей. Если ваши родители говорят по-английски, вы тоже будете говорить по-английски. А теперь представьте, что вы решили выучить второй язык, например, немецкий. В этом случае вы расширили свои атрибуты, потому что вы добавили атрибут, которого нет у ваших родителей.

Пример парка собак

Представьте на мгновение, что вы находитесь в собачьем парке.В парке много собак разных пород, и все они ведут себя по-разному.

Предположим, теперь вы хотите смоделировать парк собак с помощью классов Python. Класс Dog , который вы написали в предыдущем разделе, может различать собак по имени и возрасту, но не по породе.

Вы можете изменить класс Dog в окне редактора, добавив атрибут .breed :

  класс Собака:
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст, порода):
        себя.name = имя
        self.age = возраст
        self.breed = порода
  

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

Нажмите F5 , чтобы сохранить файл. Теперь вы можете смоделировать парк собак, создав несколько экземпляров разных собак в интерактивном окне:

>>>
  >>> miles = Dog ("Майлз", 4, "Джек-Рассел-терьер")
>>> buddy = Собака («Бадди», 9, «Такса»)
>>> jack = Собака («Джек», 3, «Бульдог»)
>>> jim = Dog («Джим», 5, «Бульдог»)
  

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

Используя только класс Dog , вы должны предоставить строку для аргумента sound из .speak () каждый раз, когда вы вызываете его в экземпляре Dog :

>>>
  >>> buddy.speak ("Яп")
'Бадди говорит Яп'

>>> jim.speak («Гав»)
'Джим говорит Гав'

>>> домкрат.говорить ("Гав")
'Джек говорит Гав'
  

Передача строки при каждом вызове .speak () является повторяющейся и неудобной. Более того, строка, представляющая звук, который издает каждый экземпляр Dog , должна определяться его атрибутом .breed , но здесь вам нужно вручную передавать правильную строку в .speak () каждый раз, когда он вызывается.

Вы можете упростить работу с классом Dog , создав дочерний класс для каждой породы собак.Это позволяет расширить функциональность, которую наследует каждый дочерний класс, включая указание аргумента по умолчанию для .speak () .

Родительские классы и дочерние классы

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

Для справки, вот полное определение класса Dog :

  класс Собака:
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст):
        себя.name = имя
        self.age = возраст

    def __str __ (сам):
        return f "{self.name} {self.age} лет"

    def говорить (сам, звук):
        return f "{self.name} говорит {звук}"
  

Помните, чтобы создать дочерний класс, вы создаете новый класс с его собственным именем, а затем помещаете имя родительского класса в круглые скобки. Добавьте следующее в файл dog.py , чтобы создать три новых дочерних класса класса Dog :

  класс JackRussellTerrier (Собака):
    проходить

класс Такса (Собака):
    проходить

класс Бульдог (Собака):
    проходить
  

Нажмите F5 , чтобы сохранить и запустить файл.Определив дочерние классы, вы можете создать экземпляры собак определенных пород в интерактивном окне:

>>>
  >>> miles = JackRussellTerrier ("Мили", 4)
>>> buddy = Такса ("Бадди", 9)
>>> jack = Бульдог ("Джек", 3)
>>> jim = Бульдог ("Джим", 5)
  

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

>>>
  >>> miles.species
'Canis familis'

>>> дружище.название
'Приятель'

>>> печать (домкрат)
Джеку 3 года

>>> jim.speak («Гав»)
'Джим говорит Гав'
  

Чтобы определить, к какому классу принадлежит данный объект, вы можете использовать встроенный type () :

>>>
  >>> тип (мили)
<класс '__main __. JackRussellTerrier'>
  

Что делать, если вы хотите определить, является ли миль также экземпляром класса Dog ? Вы можете сделать это с помощью встроенного isinstance () :

>>>
  >>> isinstance (мили, собака)
Правда
  

Обратите внимание, что isinstance () принимает два аргумента: объект и класс.В приведенном выше примере isinstance () проверяет, является ли миль экземпляром класса Dog и возвращает True .

Объекты miles , buddy , jack и jim являются экземплярами Dog , но miles не являются экземплярами Bulldog , а jack не являются экземплярами Dachshund :

>>>
  >>> isinstance (мили, бульдог)
Ложь

>>> isinstance (Джек, Такса)
Ложь
  

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

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

Расширение функциональности родительского класса

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

Чтобы переопределить метод, определенный в родительском классе, вы определяете метод с тем же именем в дочернем классе.Вот как это выглядит для класса JackRussellTerrier :

  класс JackRussellTerrier (Собака):
    def Speak (self, sound = "Arf"):
        return f "{self.name} говорит {звук}"
  

Теперь .speak () определен в классе JackRussellTerrier с аргументом по умолчанию для звука , установленным на "Arf" .

Обновите dog.py с новым классом JackRussellTerrier и нажмите F5 , чтобы сохранить и запустить файл.Теперь вы можете вызвать .speak () в экземпляре JackRussellTerrier без передачи аргумента в sound :

>>>
  >>> miles = JackRussellTerrier ("Мили", 4)
>>> miles.speak ()
'Майлз говорит Арф'
  

Иногда собаки лают по-разному, поэтому, если Майлз злится и рычит, вы все равно можете позвонить по номеру .speak () с другим звуком:

>>>
  >>> miles.speak ("Гррр")
'Майлз говорит Грр'
  

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

Например, в окне редактора измените строку, возвращаемую функцией .speak () в классе Dog :

  класс Собака:
    # Остальные атрибуты и методы оставьте как есть

    # Измените строку, возвращаемую .speak ()
    def говорить (сам, звук):
        return f "{self.name} лай: {звук}"
  

Сохраните файл и нажмите F5 . Теперь, когда вы создаете новый экземпляр Bulldog с именем jim , jim.Speak () возвращает новую строку:

>>>
  >>> jim = Бульдог ("Джим", 5)
>>> jim.speak («Гав»)
'Джим лает: Гав'
  

Однако вызов .speak () в экземпляре JackRussellTerrier не покажет новый стиль вывода:

>>>
  >>> miles = JackRussellTerrier ("Мили", 4)
>>> miles.speak ()
'Майлз говорит Арф'
  

Иногда имеет смысл полностью переопределить метод родительского класса.Но в этом случае мы не хотим, чтобы класс JackRussellTerrier потерял любые изменения, которые могут быть внесены в форматирование выходной строки Dog.speak () .

Для этого вам все равно нужно определить метод .speak () для дочернего класса JackRussellTerrier . Но вместо явного определения выходной строки вам нужно вызвать .speak () класса Dog внутри дочернего класса .speak () , используя те же аргументы, которые вы передали JackRussellTerrier.говорить () .

Вы можете получить доступ к родительскому классу из метода дочернего класса, используя super () :

  класс JackRussellTerrier (Собака):
    def Speak (self, sound = "Arf"):
        вернуть супер (). говорить (звук)
  

Когда вы вызываете super (). Speak (звук) внутри JackRussellTerrier , Python ищет в родительском классе Dog метод .speak () и вызывает его с переменной sound .

Обновите dog.py с новым классом JackRussellTerrier . Сохраните файл и нажмите F5 , чтобы вы могли протестировать его в интерактивном окне:

>>>
  >>> miles = JackRussellTerrier ("Мили", 4)
>>> miles.speak ()
Майлз лает: Арф
  

Теперь, когда вы вызываете miles.speak () , вы увидите результат, отражающий новое форматирование в классе Dog .

Примечание: В приведенных выше примерах иерархия классов очень проста.Класс JackRussellTerrier имеет единственный родительский класс Dog . В реальных примерах иерархия классов может быть довольно сложной.

super () делает гораздо больше, чем просто ищет метод или атрибут в родительском классе. Он просматривает всю иерархию классов в поисках соответствующего метода или атрибута. Если вы не будете осторожны, super () может дать удивительные результаты.

Проверьте свое понимание

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

Создайте класс GoldenRetriever , который наследуется от класса Dog .Присвойте аргументу sound для GoldenRetriever.speak () значение по умолчанию «Bark» . Используйте следующий код для родительского класса Dog :

  класс Собака:
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст

    def __str __ (сам):
        return f "{self.name} {self.age} лет"

    def говорить (сам, звук):
        return f "{self.name} говорит {звук}"
  

Вы можете развернуть блок ниже, чтобы увидеть решение:

Создайте класс с именем GoldenRetriever , который наследуется от класса Dog и переопределяет .Speak () метод:

  класс GoldenRetriever (Собака):
    def Speak (self, sound = "Лай"):
        вернуть супер (). говорить (звук)
  

Параметр sound в GoldenRetriever.speak () получает значение по умолчанию «Лай» . Затем super () используется для вызова метода .speak () родительского класса с тем же аргументом, переданным в sound , что и метод .speak () класса GoldenRetriever .

Заключение

Из этого руководства вы узнали об объектно-ориентированном программировании (ООП) на Python. Большинство современных языков программирования, таких как Java, C # и C ++, следуют принципам ООП, поэтому полученные здесь знания будут применимы независимо от того, куда приведет ваша карьера программиста.

В этом руководстве вы узнали, как:

  • Определите класс , который является своего рода схемой для объекта
  • Создать экземпляр объекта из класса
  • Используйте атрибуты и методы для определения свойств и поведения объекта
  • Используйте наследование для создания дочерних классов из родительского класса
  • Ссылка на метод родительского класса с помощью super ()
  • Проверить, наследуется ли объект от другого класса, используя isinstance ()

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

классов и объектов I Учебные пособия и примечания | Python

Классы и методы Python

Python — это «объектно-ориентированный язык программирования». Это означает, что почти весь код реализован с помощью специальной конструкции, называемой классами. Программисты используют классы, чтобы связывать вещи вместе. Это делается с помощью ключевого слова «класс», которое представляет собой группу объектно-ориентированных конструкций.

По окончании этого урока вы сможете:

  1. Определите, что такое класс
  2. Опишите, как создать класс
  3. Определите, что такое метод
  4. Опишите, как создать экземпляр объекта
  5. Опишите, как создавать атрибуты экземпляра в Python

Что такое класс?

Класс — это шаблон кода для создания объектов.У объектов есть переменные-члены и поведение, связанное с ними. В python класс создается по ключевому слову class .

Объект создается с помощью конструктора класса. Затем этот объект будет называться экземпляром класса. В Python мы создаем экземпляры следующим образом

  Экземпляр = класс (аргументы)
  

Как создать класс

Самый простой класс можно создать с помощью ключевого слова class. Например, давайте создадим простой пустой класс без каких-либо функций.

  >>> класс Змейка:
...     проходить
...
>>> snake = Змея ()
>>> печать (змейка)
<__ main __. Snake object at 0x7f315c573550>
  

Атрибутов и методов в классе:

Класс сам по себе бесполезен, если с ним не связаны некоторые функции. Функциональные возможности определяются путем установки атрибутов, которые действуют как контейнеры для данных и функций, связанных с этими атрибутами. Эти функции называются методами.

Атрибуты:

Вы можете определить следующий класс с именем Snake.У этого класса будет атрибут с именем .

  >>> класс Змейка:
... name = "python" # установить атрибут `name` класса
...
  

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

  >>> # создайте экземпляр класса Snake и назначьте его переменной snake
>>> snake = Змея ()

>>> # доступ к имени атрибута класса внутри класса Snake.
>>> печать (snake.name)
питон
  

Методы

Когда есть атрибуты, которые «принадлежат» классу, вы можете определить функции, которые будут обращаться к атрибуту класса. Эти функции называются методами. Когда вы определяете методы, вам нужно всегда предоставлять первый аргумент методу с ключевым словом self.

Например, вы можете определить класс Snake , который имеет один атрибут name и один метод change_name . Имя изменения метода примет аргумент new_name вместе с ключевым словом self .

  >>> класс Змейка:
... name = "питон"
...
... def change_name (self, new_name): # обратите внимание, что первым аргументом является self
... self.name = new_name # доступ к атрибуту класса с ключевым словом self
...
  

Теперь вы можете создать экземпляр этого класса Snake с переменной snake , а затем изменить имя с помощью метода change_name .

  >>> # создать экземпляр класса
>>> snake = Змея ()

>>> # выводим текущее имя объекта
>>> печать (snake.name)
питон

>>> # измените имя с помощью метода change_name
>>> snake.change_name ("анаконда")
>>> печать (snake.name)
анаконда
  

Атрибуты экземпляра в python и метод инициализации

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

  класс Змея:

    def __init __ (я, имя):
        self.name = имя

    def change_name (self, new_name):
        self.name = новое_имя
  

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

  >>> # создаются две переменные
>>> python = Snake ("питон")
>>> anaconda = Змея ("анаконда")

>>> # выводим имена двух переменных
>>> print (python.название)
питон
>>> печать (anaconda.name)
анаконда
  

Предоставил: Джойдип Бхаттачарджи

классов и объектов Python [с примерами]

Объекты и классы Python

Python — это объектно-ориентированный язык программирования. В отличие от процедурно-ориентированного программирования, где основной упор делается на функции, объектно-ориентированное программирование делает упор на объекты.

Объект — это просто набор данных (переменных) и методов (функций), которые воздействуют на эти данные.Точно так же класс является планом для этого объекта.

Мы можем думать о классе как об эскизе (прототипе) дома. Он содержит все подробности о этажах, дверях, окнах и т. Д. На основе этих описаний мы строим дом. Дом — это объект.

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


Определение класса в Python

Как и определения функций в Python начинаются с ключевого слова def, определения классов начинаются с ключевого слова class.

Первая строка внутри класса называется docstring и содержит краткое описание класса. Хотя это не обязательно, это настоятельно рекомендуется.

Вот простое определение класса.

  класс MyNewClass:
    '' 'Это строка документации. Я создал новый класс ''
    проездной  

Класс создает новое локальное пространство имен, в котором определены все его атрибуты. Атрибуты могут быть данными или функциями.

В нем также есть специальные атрибуты, которые начинаются с двойного подчеркивания __ .Например, __doc__ дает нам строку документации этого класса.

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

  класс Человек:
    "Это человек класса"
    возраст = 10

    def greet (сам):
        print ('Привет')


# Вывод: 10
печать (Person.age)

# Вывод: <функция Person.greet>
печать (Person.greet)

# Вывод: «Это класс человека»
печать (Человек.__doc__)  

Выход

  10
<функция Person.greet по адресу 0x7fc78c6e8160>
Это человек класса  

Создание объекта в Python

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

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

  >>> harry = Person ()  

Это создаст новый экземпляр объекта с именем harry .Мы можем получить доступ к атрибутам объектов, используя префикс имени объекта.

Атрибуты могут быть данными или методом. Методы объекта — это соответствующие функции этого класса.

Это означает, что, поскольку Person.greet является объектом функции (атрибутом класса), Person.greet будет объектом метода.

  класс Человек:
    "Это человек класса"
    возраст = 10

    def greet (сам):
        print ('Привет')


# создать новый объект класса Person
harry = Человек ()

# Вывод: <функция Person.приветствовать>
печать (Person.greet)

# Вывод: <связанный метод Person.greet из <__ main __. Объект Person >>
печать (harry.greet)

# Вызов метода объекта greet ()
# Вывод: Привет
harry.greet ()  

Выход

  <функция Person.greet at 0x7fd288e4e160>
<связанный метод Person.greet объекта <__ main __. Person в 0x7fd288e9fa30 >>
Привет  

Возможно, вы заметили параметр self в определении функции внутри класса, но мы назвали этот метод просто harry.greet () без аргументов. Это все еще работало.

Это потому, что всякий раз, когда объект вызывает свой метод, сам объект передается в качестве первого аргумента. Итак, harry.greet () переводится в Person.greet (harry) .

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

По этим причинам первым аргументом функции в классе должен быть сам объект.Это условно называется self . Его можно назвать иначе, но мы настоятельно рекомендуем следовать соглашению.

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


Конструкторы в Python

Функции класса, начинающиеся с двойного подчеркивания __ , называются специальными функциями, поскольку они имеют особое значение.

Особый интерес представляет функция __init __ () .Эта специальная функция вызывается всякий раз, когда создается новый объект этого класса.

Этот тип функций также называется конструкторами в объектно-ориентированном программировании (ООП). Обычно мы используем его для инициализации всех переменных.

  класс ComplexNumber:
    def __init __ (self, r = 0, i = 0):
        self.real = r
        self.imag = я

    def get_data (сам):
        print (f '{self.real} + {self.imag} j')


# Создаем новый объект ComplexNumber
num1 = Комплексное число (2, 3)

# Вызвать метод get_data ()
# Выход: 2 + 3j
число1.получить данные()

# Создаем еще один объект ComplexNumber
# и создайте новый атрибут attr
num2 = Комплексное число (5)
num2.attr = 10

# Вывод: (5, 0, 10)
print ((num2.real, num2.imag, num2.attr))

# но объект c1 не имеет атрибута attr
# AttributeError: объект 'ComplexNumber' не имеет атрибута 'attr'
print (num1.attr)  

Выход

  2 + 3j
(5, 0, 10)
Отслеживание (последний вызов последний):
  Файл «<строка>», строка 27, в <модуле>
    печать (число1.attr)
AttributeError: объект «ComplexNumber» не имеет атрибута «attr»  

В приведенном выше примере мы определили новый класс для представления комплексных чисел. Он имеет две функции: __init __ () для инициализации переменных (по умолчанию — ноль) и get_data () для правильного отображения числа.

Интересная вещь, которую следует отметить в предыдущем шаге, заключается в том, что атрибуты объекта могут быть созданы на лету. Мы создали новый атрибут attr для объекта num2 и также прочитали его.Но это не создает этот атрибут для объекта num1 .


Удаление атрибутов и объектов

Любой атрибут объекта можно удалить в любое время с помощью оператора del . Попробуйте выполнить следующие действия в оболочке Python, чтобы увидеть результат.

  >>> число1 = Комплексное число (2,3)
>>> del num1.imag
>>> num1.get_data ()
Отслеживание (последний вызов последний):
...
AttributeError: объект 'ComplexNumber' не имеет атрибута 'imag'

>>> del ComplexNumber.получить данные
>>> num1.get_data ()
Отслеживание (последний вызов последний):
...
AttributeError: объект «ComplexNumber» не имеет атрибута «get_data»  

Мы можем даже удалить сам объект, используя оператор del.

  >>> c1 = Комплексное число (1,3)
>>> дель c1
>>> c1
Отслеживание (последний вызов последний):
...
NameError: имя 'c1' не определено  

На самом деле все сложнее. Когда мы выполняем c1 = ComplexNumber (1,3) , в памяти создается новый объект-экземпляр, и имя c1 связывается с ним.

По команде del c1 эта привязка удаляется, а имя c1 удаляется из соответствующего пространства имен. Однако объект продолжает существовать в памяти, и если к нему не привязано другое имя, он автоматически уничтожается.

Это автоматическое уничтожение объектов, на которые нет ссылок, в Python также называется сборкой мусора.

При удалении объектов в Python удаляется привязка имени.

(Учебник) Классы Python — DataCamp

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

Классы подобны плану для объектов, описывающих возможное поведение и утверждающих, что каждый объект определенного типа может иметь. Например, если вы скажете: «У каждого клиента будет номер телефона и адрес электронной почты, и он сможет размещать и отменять заказы», ​​вы просто определили класс! Таким образом, вы можете единым образом говорить о клиентах.Тогда конкретный объект Customer — это просто реализация этого класса с определенным значением состояния.

Поиск классов Python

В Python все является объектом. Числа, строки, DataFrames и даже функции являются объектами. В частности, все, с чем вы имеете дело в Python, имеет под капотом класс, связанный с ним план. Благодаря существованию этих унифицированных интерфейсов вы можете использовать, например, любой DataFrame таким же образом.

Вы можете вызвать type () для любого объекта Python, чтобы узнать его класс.Например, класс массива numpy на самом деле называется ndarray (для n-мерного массива).

  импортировать numpy как np
a = np.array ([1,2,3,4])
печать (тип (а))
  
  numpy.ndarray
  

Классы содержат информацию о состоянии и поведении. Информация о состоянии в Python содержится в атрибутах, а информация о поведении — в методах.

Атрибуты и методы

Возьмем массив numpy : вы уже использовали некоторые из его методов и атрибутов!

Например, каждый массив numpy имеет атрибут «shape», к которому вы можете получить доступ, указав имя массива, за которым следует точка и форма.

Состояние

<--> Атрибуты
  импортировать numpy как np
a = np.array ([1,2,3,4])
  
  # атрибут формы
форма
  
  (4,)
  

Он также имеет такие методы, как max и reshape , которые также доступны через точку.

Поведение

<--> Методы
  импортировать numpy как np
a = np.array ([1,2,3,4])
  
  # метод изменения формы
a.reshape (2,2)
  
  массив ([[1, 2],
       [3, 4]])
  

Создание вашего первого класса

В этом примере вы создадите пустой класс Employee .Затем вы создадите объект emp класса Employee , вызвав Employee () .

Попробуйте напечатать атрибут .name объекта emp в консоли. Что происходит?

  # Создать пустой класс Employee
класс Сотрудник:
    проходить
# Создаем объект emp класса Employee
emp = Сотрудник ()
  

Попробуйте сами.

Чтобы узнать больше об объектно-ориентированном программировании на Python, посмотрите это видео из нашего курса Объектно-ориентированное программирование на Python.

Этот контент взят из курса DataCamp «Объектно-ориентированное программирование на Python» Александры Ярош.

классов и объектов Python — GeeksforGeeks

Класс — это определяемый пользователем план или прототип, из которого создаются объекты. Классы предоставляют средства объединения данных и функциональности вместе. Создание нового класса создает новый тип объекта, позволяя создавать новые экземпляры этого типа. К каждому экземпляру класса могут быть прикреплены атрибуты для поддержания его состояния.Экземпляры классов также могут иметь методы (определяемые их классом) для изменения их состояния.

Чтобы понять необходимость создания класса, давайте рассмотрим пример, допустим, вы хотите отслеживать количество собак, которые могут иметь разные атрибуты, такие как порода, возраст. Если используется список, первым элементом может быть порода собаки, а вторым элементом — ее возраст. Предположим, что есть 100 разных собак, тогда как вы узнаете, какой элемент должен быть каким? Что, если бы вы захотели добавить этим собакам другие свойства? Здесь не хватает организации, и это как раз необходимость занятий.

Класс

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

Некоторые точки класса Python:

  • Классы создаются с помощью ключевого слова class.
  • Атрибуты — это переменные, принадлежащие классу.
  • Атрибуты всегда являются общедоступными, и к ним можно получить доступ с помощью оператора точки (.). Например.: Myclass.Myattribute
  Синтаксис определения класса: 

class ClassName:
    # Заявление-1
    .
    .
    .
    # Statement-N 

Определение класса —


В приведенном выше примере ключевое слово class указывает, что вы создаете класс, за которым следует имя класса (в данном случае Dog).

Объекты класса

Объект — это экземпляр класса. Класс похож на план, а экземпляр — это копия класса с фактическими значениями .Это уже не идея, это настоящая собака, как собака породы мопс, которой исполнилось семь лет. У вас может быть много собак, чтобы создавать множество разных экземпляров, но без класса в качестве проводника вы потерялись бы, не зная, какая информация требуется.
Объект состоит из:

  • Состояние: Он представлен атрибутами объекта. Он также отражает свойства объекта.
  • Поведение: Представляется методами объекта. Он также отражает реакцию объекта на другие объекты.
  • Идентификатор: Он дает уникальное имя объекту и позволяет одному объекту взаимодействовать с другими объектами.

Объявление объектов (также называется созданием экземпляра класса)

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

Пример:

Объявление объекта —

Python3

класс Собака:

0

0

attr1 = «млекопитающее»

attr2 = «собака»

def fun ( self ):

печать ( «Я» , сам .attr1)

print ( «Я» , self .attr2)

Rodger = Dog ()

print (Rodger.attr1)

Rodger.fun ()

Вывод:


 млекопитающее
Я млекопитающее
Я собака 

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

Методы класса self

  • должны иметь дополнительный первый параметр в определении метода. Мы не указываем значение для этого параметра при вызове метода, Python предоставляет его.
  • Если у нас есть метод, который не принимает аргументов, то у нас все равно должен быть один аргумент.
  • Это похоже на этот указатель в C ++ и эту ссылку в Java.

Когда мы вызываем метод этого объекта как myobject.method (arg1, arg2), он автоматически преобразуется Python в MyClass.method (myobject, arg1, arg2) — это все, о чем идет речь в специальном self.

Метод __init__

Метод __init__ аналогичен конструкторам в C ++ и Java. Конструкторы используются для инициализации состояния объекта. Как и методы, конструктор также содержит набор операторов (то есть инструкций), которые выполняются во время создания объекта. Он запускается, как только создается экземпляр объекта класса.Этот метод полезен для любой инициализации, которую вы хотите выполнить с вашим объектом.

Python3

класс Личность:

def __init __ ( self , имя) 9: 9005 .name = name

def say_hi ( self ):

print ( 'Привет , меня зовут , self .имя)

p = Человек ( 'Nikhil' )

p.say_hi ()

Выход:

62

902 меня зовут Nikhil

Переменные класса и экземпляра

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

Определение переменной экземпляра с помощью конструктора.

Python3

класс Собака:

животное = '16

0

def __init __ ( self , порода, окрас):

self .порода = порода

сам . цвет = окрас

Роджер = Собака ( "Мопс" , «коричневый» )

Buzo = Dog ( «Бульдог» , «черный» )

печать ( 'Детали Rodger: ' )

print ( ' Rodger - это ' , Rodger.животное)

принт ( 'Порода:' , Rodger.breed)

принт ( 'Цвет:' , Rodger.color)

print ( '\ nBuzo детали:' )

print ( 'Buzo - это' , Buzo.animal)

print ( 'Порода: ' , Бузо.порода)

print ( 'Color:' , Buzo.color)

print ( "\ nПеременная класса с использованием имени класса" )

print (Dog.animal)

Вывод:

 Детали Роджера:
Роджер - собака
Порода: Мопс
Коричневый цвет

Подробности Buzo:
Бузо - собака
Порода: Бульдог
Черный цвет

Доступ к переменной класса с использованием имени класса
dog 

Определение переменной экземпляра обычным методом.

Python3

класс Собака:

животное = '16

0

def __init __ ( self , порода):

self .порода = порода

def setColor ( self , окрас):

self .color = цвет

def getColor ( self ):

return self .цвет

Роджер = Собака ( "мопс" )

Rodger.setColor ( "коричневый" )

печать (Роджер .getColor ())

Вывод:

 коричневый 

Внимание компьютерщик! Укрепите свои основы с помощью курса Python Programming Foundation и изучите основы.

Для начала подготовьтесь к собеседованию. Расширьте свои концепции структур данных с помощью курса Python DS . А чтобы начать свое путешествие по машинному обучению, присоединяйтесь к курсу Машинное обучение - базовый уровень


Открытый ресурс для студентов и преподавателей

 from math import sqrt

из случайного импорта randint

класс Ракета (): # Rocket имитирует ракетный корабль для игры, # или физическое моделирование. def __init __ (self, x = 0, y = 0): # Каждая ракета имеет позицию (x, y).self.x = x self.y = y def move_rocket (self, x_increment = 0, y_increment = 1): # Переместите ракету в соответствии с заданными параметрами. # По умолчанию ракета перемещается на одну единицу вверх. self.x + = x_increment self.y + = y_increment def get_distance (self, other_rocket): # Вычисляет расстояние от этой ракеты до другой ракеты, # и возвращает это значение. расстояние = sqrt ((self.x-other_rocket.x) ** 2+ (self.y-other_rocket.y) ** 2) расстояние возврата класс Шаттл (Ракета): # Shuttle имитирует космический шаттл, что на самом деле # просто многоразовая ракета. def __init __ (self, x = 0, y = 0, flight_completed = 0): super () .__ init __ (х, у) self.flights_completed = flight_completed

# Создайте несколько шаттлов и ракет со случайными позициями.

# Шаттлы совершают произвольное количество полетов.

челноков = []

для x в диапазоне (0,3):

х = рандинт (0,100)

y = randint (1100)

flight_completed = randint (0,10)

челноков.append (Shuttle (x, y, flight_completed))

ракет = []

для x в диапазоне (0,3):

х = рандинт (0,100)

y = randint (1100)

ракет. Приложение (Ракета (x, y))

# Показать количество выполненных полетов для каждого шаттла.

для индекса, челнока в перечислении (челноки):

print ("Шаттл% d выполнил% d рейсов.