Содержание

🔵 Классы в Python

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

[‘__add__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__doc__’, ‘__eq__’,
‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__getitem__’, ‘__getnewargs__’,
‘__getslice__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__len__’, ‘__lt__’,
‘__mod__’, ‘__mul__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’,
‘__rmod__’, ‘__rmul__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’,
‘_formatter_field_name_split’, ‘_formatter_parser’, ‘capitalize’, ‘center’, ‘count’,
‘decode’, ‘encode’, ‘endswith’, ‘expandtabs’, ‘find’, ‘format’, ‘index’, ‘isalnum’,
‘isalpha’, ‘isdigit’, ‘islower’, ‘isspace’, ‘istitle’, ‘isupper’, ‘join’, ‘ljust’,
‘lower’, ‘lstrip’, ‘partition’, ‘replace’, ‘rfind’, ‘rindex’, ‘rjust’, ‘rpartition’,
‘rsplit’, ‘rstrip’, ‘split’, ‘splitlines’, ‘startswith’, ‘strip’, ‘swapcase’, ‘title’,
‘translate’, ‘upper’, ‘zfill’]



[‘__add__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__doc__’, ‘__eq__’,

‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__getitem__’, ‘__getnewargs__’,

‘__getslice__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__len__’, ‘__lt__’,

‘__mod__’, ‘__mul__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’,

‘__rmod__’, ‘__rmul__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’,

‘_formatter_field_name_split’, ‘_formatter_parser’, ‘capitalize’, ‘center’, ‘count’,

‘decode’, ‘encode’, ‘endswith’, ‘expandtabs’, ‘find’, ‘format’, ‘index’, ‘isalnum’,

‘isalpha’, ‘isdigit’, ‘islower’, ‘isspace’, ‘istitle’, ‘isupper’, ‘join’, ‘ljust’,

‘lower’, ‘lstrip’, ‘partition’, ‘replace’, ‘rfind’, ‘rindex’, ‘rjust’, ‘rpartition’,

‘rsplit’, ‘rstrip’, ‘split’, ‘splitlines’, ‘startswith’, ‘strip’, ‘swapcase’, ‘title’,

‘translate’, ‘upper’, ‘zfill’]

В примере мы видим строку, присвоенную переменной х. Это может выглядеть как большой объем, но дело в том, что у этой строки много методов. Если вы используете ключевое слово dir, вы получите список всех методов, которые можно присвоить строке. Мы видим 71 метод! Технически, мы не можем вызвать методы, которые начинаются с подчеркивание, так что это сужает список до 38 методов, но это все еще очень много! Что это значит? Это значит что, строка основана на классе, а переменная х – и есть экземпляр этого класса. В Пайтоне мы можем создавать собственные классы. Начнем!

Создание Класса

Создание класса в Пайтоне – это очень просто. Вот простой пример:

# Python 2.x syntax

class Vehicle(object):
«»»docstring»»»

def __init__(self):
«»»Constructor»»»
pass



# Python 2.x syntax

 

class Vehicle(object):

    «»»docstring»»»

    

    def __init__(self):

        «»»Constructor»»»

        pass

Этот класс не делает ничего конкретного, тем не менее, это очень хороший инструмент для изучения. Например, чтобы создать класс, мы используем ключевое слово class, за которым следует наименование класса. В Пайтоне, конвенция указывает на то, что наименование класса должно начинаться с заглавной буквы. Далее нам нужно открыть круглые скобки, за которыми следует слово object и закрытые скобки. «object» — то, на чем основан класс, или наследуется от него. Это называется базовым классом или родительским классом. Большая часть классов в Пайтоне основаны на объекте. У классов есть особый метод, под названием __init__.

Этот метод вызывается всякий раз, когда вы создаете (или создаете экземпляр) объект на основе этого класса. Метод __init__ вызывается единожды, и не может быть вызван снова внутри программы. Другое определение метода __init__ — это конструктор, кстати, этот термин редко встречается в Пайтоне. Вы можете подумать, почему я называю это методом, а не функцией? Функция меняет свое имя на «method», когда она находится внутри класса. Обратите внимание на то, что каждый метод должен иметь как минимум один аргумент, что в случае с обычной функцией уже не вяжется. В Python 3 нам не нужно прямо указывать, что мы наследуем у объекта. Вместо этого, мы можем написать это следующим образом:

# Python 3.x syntax

class Vehicle:
«»»docstring»»»

def __init__(self):
«»»Constructor»»»
pass



# Python 3.x syntax

 

class Vehicle:

    «»»docstring»»»

 

    def __init__(self):

        «»»Constructor»»»

        pass

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

class Vehicle(object):
«»»docstring»»»

def __init__(self, color, doors, tires):
«»»Constructor»»»
self.color = color
self.doors = doors
self.tires = tires

def brake(self):
«»»
Stop the car
«»»
return «Braking»

def drive(self):
«»»
Drive the car
«»»
return «I’m driving!»


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

class Vehicle(object):

    «»»docstring»»»

 

    def __init__(self, color, doors, tires):

        «»»Constructor»»»

        self.color = color

        self.doors = doors

        self.tires = tires

    

    def brake(self):

        «»»

        Stop the car

        «»»

        return «Braking»

    

    def drive(self):

        «»»

        Drive the car

        «»»

        return «I’m driving!»

В данном примере мы добавили три атрибута и два метода. Эти три атрибута являются:

self.color = color
self.doors = doors
self.tires = tires



self.color = color

self.doors = doors

self.tires = tires

Атрибуты описывают автомобиль. У него есть цвет, определенное количество дверей и колес. Также у него есть два метода. Метод описывает, что делает класс. В нашем случае, автомобиль может двигаться и останавливаться. Вы могли заметить, что все методы, включая первый, имеют интересный аргумент, под названием self. Давайте рассмотрим его внимательнее.

Мы собрали ТОП Книг для Python программиста которые помогут быстро изучить язык программирования Python.
Список книг: Книги по Python

Что такое self?

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

class Vehicle(object):
«»»docstring»»»

def __init__(self, color, doors, tires):
«»»Constructor»»»
self.color = color
self.doors = doors
self.tires = tires

def brake(self):
«»»
Stop the car
«»»
return «Braking»

def drive(self):
«»»
Drive the car
«»»
return «I’m driving!»

if __name__ == «__main__»:
car = Vehicle(«blue», 5, 4)
print(car.color)

truck = Vehicle(«red», 3, 6)
print(truck.color)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

class Vehicle(object):

    «»»docstring»»»

 

    def __init__(self, color, doors, tires):

        «»»Constructor»»»

        self.color = color

        self.doors = doors

        self.tires = tires

    

    def brake(self):

        «»»

        Stop the car

        «»»

        return «Braking»

    

    def drive(self):

        «»»

        Drive the car

        «»»

        return «I’m driving!»

 

if __name__ == «__main__»:

    car = Vehicle(«blue», 5, 4)

    print(car.color)

    

    truck = Vehicle(«red», 3, 6)

    print(truck.color)

Условия оператора if в данном примере это стандартный способ указать Пайтону на то, что вы хотите запустить код, если он выполняется как автономный файл. Если вы импортировали свой модуль в другой скрипт, то код, расположенный ниже проверки if не заработает. В любом случае, если вы запустите этот код, вы создадите два экземпляра класса автомобиля (Vehicle): класс легкового и класс грузового. Каждый экземпляр будет иметь свои собственные атрибуты и методы. Именно по этому, когда мы выводи цвета каждого экземпляра, они и отличаются друг от друга. Причина в том, что этот класс использует аргумент self, чтобы указать самому себе, что есть что. Давайте немного изменим класс, чтобы сделать методы более уникальными:

class Vehicle(object):
«»»docstring»»»

def __init__(self, color, doors, tires, vtype):
«»»Constructor»»»
self.color = color
self.doors = doors
self.tires = tires
self.vtype = vtype

def brake(self):
«»»
Stop the car
«»»
return «%s braking» % self.vtype

def drive(self):
«»»
Drive the car
«»»
return «I’m driving a %s %s!» % (self.color, self.vtype)


if __name__ == «__main__»:
car = Vehicle(«blue», 5, 4, «car»)
print(car.brake())
print(car.drive())

truck = Vehicle(«red», 3, 6, «truck»)
print(truck.drive())
print(truck.brake())


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

class Vehicle(object):

    «»»docstring»»»

    

    def __init__(self, color, doors, tires, vtype):

        «»»Constructor»»»

        self.color = color

        self.doors = doors

        self.tires = tires

        self.vtype = vtype

    

    def brake(self):

        «»»

        Stop the car

        «»»

        return «%s braking» % self.vtype

    

    def drive(self):

        «»»

        Drive the car

        «»»

        return «I’m driving a %s %s!» % (self.color, self.vtype)

 

 

if __name__ == «__main__»:

    car = Vehicle(«blue», 5, 4, «car»)

    print(car.brake())

    print(car.drive())

 

    truck = Vehicle(«red», 3, 6, «truck»)

    print(truck.drive())

    print(truck.brake())

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

car braking
I’m driving a blue car!
I’m driving a red truck!
truck braking



car braking

I’m driving a blue car!

I’m driving a red truck!

truck braking

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

Подклассы

Настоящая сила классов становится очевидной, когда вопрос касается подклассов. Вы, возможно, еще не поняли это, но мы уже создали подкласс, когда создавали класс, основанный на объекте. Другими словами, «подклассифицировали» объект. Так как объект – это не очень интересная тема, предыдущие примеры не уделили должного внимания такому сильному инструменту как подкласс. Давайте подклассифицируем наш класс Vehicle и узнаем, как все это работает.

class Car(Vehicle):
«»»
The Car class
«»»

#———————————————————————-
def brake(self):
«»»
Override brake method
«»»
return «The car class is breaking slowly!»


if __name__ == «__main__»:
car = Car(«yellow», 2, 4, «car»)
car.brake()
‘The car class is breaking slowly!’
car.drive()
«I’m driving a yellow car!»


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

class Car(Vehicle):

    «»»

    The Car class

    «»»

 

    #———————————————————————-

    def brake(self):

        «»»

        Override brake method

        «»»

        return «The car class is breaking slowly!»

 

 

if __name__ == «__main__»:

    car = Car(«yellow», 2, 4, «car»)

    car.brake()

    ‘The car class is breaking slowly!’

    car.drive()

    «I’m driving a yellow car!»

В этом примере, мы подклассифицировали класс Vehicle. Вы могли заметить, что мы не использовали методы __init__ и drive. Причина в том, что когда мы хотим сделать из класса подкласс, мы уже имеем все атрибуты и методы, только если мы не переопределяем их. Таким образом, вы могли заметить, что мы переопределяем метод brake и указываем ему делать кое-что другое. Другие методы остаются такими же, какими они и были до этого. Так что, когда вы указываете автомобилю тормозить, он использует оригинальный метод, и мы узнали, что мы водим желтый автомобиль. Когда мы используем значения родительского класса по умолчанию – мы называем это наследование.

Мы собрали ТОП Книг для Python программиста которые помогут быстро изучить язык программирования Python.
Список книг: Книги по Python

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

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

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

Подведем итоги

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

python-scripts.com

методы и атрибуты классов ~ PythonRu

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

Создание классов

Оператор class создает новое определение класса. Имя класса сразу следует за ключевым словом class, после которого ставиться двоеточие:

class ClassName:
   """Необязательная строка документации класса"""  
   class_suite
  • У класса есть строка документации, к которой можно получить доступ через ClassName.__doc__.
  • class_suite состоит из частей класса, атрибутов данных и функции.

Пример создания класса
Создание простого класса на Python

class Employee:  
    """Базовый класс для всех сотрудников"""  
    emp_count = 0  
  
    def __init__(self, name, salary):  
        self.name = name  
        self.salary = salary  
        Employee.emp_count += 1  
  
    def display_count(self):  
        print('Всего сотрудников: %d' % Employee.empCount)  
  
    def display_employee(self):  
        print('Имя: {}. Зарплата: {}'.format(self.name, self.salary))  
  • Переменная emp_count — переменная класса, значение которой разделяется между экземплярами этого класса. Получить доступ к этой переменной можно через Employee.emp_count из класса или за его пределами.
  • Первый метод __init__() — специальный метод, который называют конструктором класса или методом инициализации. Его вызывает Python при создании нового экземпляра этого класса.
  • Объявляйте другие методы класса, как обычные функции, за исключением того, что первый аргумент для каждого метода self. Python добавляет аргумент self в список для вас; и тогда вам не нужно включать его при вызове этих методов.

Создание экземпляров класса

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


emp1 = Employee("Андрей", 2000)  

emp2 = Employee("Мария", 5000) 

Доступ к атрибутам

Получите доступ к атрибутам класса, используя оператор . после объекта класса. Доступ к классу можно получить используя имя переменой класса:

emp1.display_employee()  
emp2.display_employee()  
print("Всего сотрудников: %d" % Employee.emp_count)

Теперь, систематизируем все.

class Employee:  
    """Базовый класс для всех сотрудников"""  
    emp_count = 0  
  
    def __init__(self, name, salary):  
        self.name = name  
        self.salary = salary  
        Employee.emp_count += 1  
  
    def display_count(self):  
        print('Всего сотрудников: %d' % Employee.emp_count)
        
    def display_employee(self):  
        print('Имя: {}. Зарплата: {}'.format(self.name, self.salary))  
  
  

emp1 = Employee("Андрей", 2000)  

emp2 = Employee("Мария", 5000)  
emp1.display_employee()  
emp2.display_employee()  
print("Всего сотрудников: %d" % Employee.emp_count)

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

Имя: Андрей. Зарплата: 2000
Имя: Мария. Зарплата: 5000
Всего сотрудников: 2

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

emp1.age = 7  
emp1.age = 8  
del emp1.age  

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

  • getattr(obj, name [, default]) — для доступа к атрибуту объекта.
  • hasattr(obj, name) — проверить, есть ли в obj атрибут name.
  • setattr(obj, name, value) — задать атрибут. Если атрибут не существует, он будет создан.
  • delattr(obj, name) — удалить атрибут
hasattr(emp1, 'age')  
getattr(emp1, 'age')  
setattr(emp1, 'age', 8)  
delattr(empl, 'age')  

Встроенные атрибуты класса

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

Тест на знание python

Что выведет этот код?

Продолжить

Что выведет этот код?

Продолжить

Что выведет этот код?

Продолжить

Что выведет этот код?

Продолжить

Что выведет этот код?

Продолжить

Продолжить

{{title}}

{{image}}

{{content}}

{{/global}}

Поделиться результатами через

  • __dict__ — словарь, содержащий пространство имен класса.
  • __doc__ — строка документации класса. None если, документация отсутствует.
  • __name__ — имя класса.
  • __module__ — имя модуля, в котором определяется класс. Этот атрибут __main__ в интерактивном режиме.
  • __bases__ — могут быть пустые tuple, содержащие базовые классы, в порядке их появления в списке базового класса.

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

class Employee:  
    """Базовый класс для всех сотрудников"""  
    emp_count = 0  
  
    def __init__(self, name, salary):  
        self.name = name  
        self.salary = salary  
        Employee.empCount += 1  
  
    def display_count(self):  
        print('Всего сотрудников: %d' % Employee.empCount)  
  
    def display_employee(self):  
        print('Имя: {}. Зарплата: {}'.format(self.name, self.salary))  
  
  
print("Employee.__doc__:", Employee.__doc__)  
print("Employee.__name__:", Employee.__name__)  
print("Employee.__module__:", Employee.__module__)  
print("Employee.__bases__:", Employee.__bases__)  
print("Employee.__dict__:", Employee.__dict__) 

Когда этот код выполняется, он возвращает такой результат:

Employee.__doc__: Базовый класс для всех сотрудников
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'>,)
Employee.__dict__: {'__module__': '__main__', '__doc__': 'Базовый класс для всех сотрудников', 'emp_count': 0, '__init__': <function Employee.__init__ at 0x03C7D7C8>, 'display_count': <function Employee.display_count at 0x03FA6AE0>, 'display_employee': <function Employee.display_employee at 0x03FA6B28>, '__dict__': <attribute '__dict__' of 'Employee' objects>, '__weakref__': <attribute '__weakref__' of 'Employee' objects>}

Уничтожение объектов (сбор мусора)

Python автоматически удаляет ненужные объекты (встроенные типы или экземпляры классов), чтобы освободить пространство памяти. С помощью процесса ‘Garbage Collection’ Python периодически восстанавливает блоки памяти, которые больше не используются.

Сборщик мусора Python запускается во время выполнения программы и тогда, когда количество ссылок на объект достигает нуля. С изменением количества обращений к нему, меняется количество ссылок.

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

a = 40      # создали объект <40>
b = a       # увеличивает количество ссылок  <40> 
c = [b]     # увеличивает количество ссылок <40> 

del a       # уменьшает количество ссылок <40>
b = 100     # уменьшает количество ссылок <40> 
c[0] = -1   # уменьшает количество ссылок <40>

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

class Point:  
    def __init__(self, x=0, y=0):  
        self.x = x  
        self.y = y  
  
    def __del__(self):  
        class_name = self.__class__.__name__  
        print('{} уничтожен'.format(class_name))  
  
  
pt1 = Point()  
pt2 = pt1  
pt3 = pt1  
print(id(pt1), id(pt2), id(pt3))  
del pt1  
del pt2  
del pt3

Когда вышеуказанный код выполняется и выводит следующее:

17692784 17692784 17692784
Point уничтожен

В идеале вы должны создавать свои классы в отдельном модуле. Затем импортировать их в основной модуль программы с помощью import SomeClass.

Наследование класса

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

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

class  SubClassName  (ParentClass1[,  ParentClass2,  ...]):
	"""Необязательная строка документации класса""" 
	class_suite

Пример наследования класса в Python

class Parent:  
    parent_attr = 100  
  
    def __init__(self):  
        print('Вызов родительского конструктора')  
  
    def parent_method(self):  
        print('Вызов родительского метода')  
  
    def set_attr(self, attr):  
        Parent.parent_attr = attr  
  
    def get_attr(self):  
        print('Атрибут родителя: {}'.format(Parent.parent_attr))  
  
  
class Child(Parent):  
    def __init__(self):  
        print('Вызов конструктора класса наследника')  
  
    def child_method(self):  
        print('Вызов метода класса наследника')  
  
  
c = Child()  
c.child_method()  
c.parent_method()  
c.set_attr(200)  
c.get_attr()  

Когда этот код выполняется, он выводит следующий результат:

Вызов конструктора класса наследника
Вызов метода класса наследника
Вызов родительского метода
Атрибут родителя: 200

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

class A:        # объявите класс A
....

class B:         # объявите класс B
....

class C(A, B):   # C наследуется от A и B
....

Вы можете использовать функции issubclass() или isinstance() для проверки отношений двух классов и экземпляров.

  • Логическая функция issubclass(sub, sup) возвращает значение True, если данный подкласс sub действительно является подклассом sup.
  • Логическая функция isinstance(obj, Class) возвращает True, если obj является экземпляром класса Class или является экземпляром подкласса класса.

Переопределение методов

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

class Parent:  
    def my_method(self):  
        print('Вызов родительского метода')  
  
  
class Child(Parent):  
    def my_method(self):  
        print('Вызов метода наследника')  
  
  
c = Child()  
c.my_method()  

Когда этот код выполняется, он производит следующий результат:

Вызов метода наследника

Популярные базовые методы

В данной таблице перечислены некоторые общие функции. Вы можете переопределить их в своих собственных классах.

Метод, описание и пример вызова
1 __init__(self [, args...]) — конструктор (с любыми необязательными аргументами)
obj = className(args)
2 __del__(self) — деструктор, удаляет объект
del obj
3 __repr__(self) — оценочное строковое представление
repr(obj)
4 __str__(self) — печатное строковое представление
str(obj)

Пример использования __add__
Предположим, вы создали класс Vector для представления двумерных векторов. Что происходит, когда вы используете дополнительный оператор для их добавления? Скорее всего, Python будет против.

Однако вы можете определить метод __add__ в своем классе для добавления векторов и оператор + будет вести себя так как нужно.

class Vector:   
    def __init__(self, a, b):   
        self.a = a   
        self.b = b   
    
    def __str__(self):    
        return 'Vector ({}, {})'.format(self.a, self.b)  
    
    def __add__(self, other):   
        return Vector(self.a + other.a, self.b + other.b)   
     
     
v1 = Vector(2, 10)   
v2 = Vector(5, -2)    
print(v1 + v2)

При выполнении этого кода, мы получим:

Vector(7, 8)

Приватные методы и атрибуты класса

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

class JustCounter:  
    __secret_count = 0  
  
    def count(self):  
        self.__secret_count += 1  
	    print(self.__secret_count)  
  
  
counter = JustCounter()  
counter.count()  
counter.count()  
print(counter.__secret_count)

При выполнении данного кода, имеем следующий результат:

Traceback (most recent call last):
   File "test.py", line 12, in <module>
      print(counter.__secret_count)
AttributeError: 'JustCounter' object has no attribute '__secret_count'

Вы можете получить доступ к таким атрибутам, так object._className__attrName. Если вы замените свою последнюю строку следующим образом, то она будет работать.

.........................
print(counter._JustCounter__secretCount)

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

1
2
2

pythonru.com

Python. Урок 14. Классы и объекты

Данный урок посвящен объектно-ориентированному программированию в Python. Разобраны такие темы как создание объектов и классов, работа с конструктором, наследование и полиморфизм в Python.

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

Выделяют три основных “столпа” ООП- это инкапсуляция, наследование и полиморфизм.

Инкапсуляция

Под инкапсуляцией понимается объединение в рамках одной сущности (класса) определенных данных и методов для работы с ними (и не только). Например, можно определить класс “холодильник”, который будет содержать следующие данные: производитель, объем, количество камер хранения, потребляемая мощность и т.п., и методы: открыть/закрыть холодильник, включить/выключить. При этом класс становится новым типом данных в рамках разрабатываемой программы. Можно создавать переменные этого нового типа, такие переменные называются объекты.

Наследование

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

Примером базового класса, демонстрирующего наследование, можно определить класс “автомобиль”, имеющий атрибуты: масса, мощность двигателя, объем топливного бака и методы: завести и заглушить. У такого класса может быть потомок – “грузовой автомобиль”, он будет содержать те же атрибуты и методы, что и класс “автомобиль”, и дополнительные свойства: количество осей, мощность компрессора и т.п..

Полиморфизм

Полиморфизм позволяет одинаково обращаться с объектами, имеющими однотипный интерфейс, независимо от внутренней реализации объекта. Например с объектом класса “грузовой автомобиль” можно производить те же операции, что и с объектом класса “автомобиль”, т.к. первый является наследником второго, при этом обратное утверждение неверно (во всяком случае не всегда). Другими словами полиморфизм предполагает разную реализацию методов с одинаковыми именами. Это очень полезно при наследовании, когда в классе наследнике можно переопределить методы класса родителя.

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

Создание класса в Python начинается с инструкции class. Вот так будет выглядеть минимальный класс.

class C:
    pass

Класс состоит из объявления (инструкция class), имени класса (нашем случае это имя C) и тела класса, которое содержит атрибуты и методы (в нашем минимальном классе есть только одна инструкция pass).

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

имя_объекта = имя_класса()

Класс может содержать атрибуты и методы. Ниже представлен класс, содержащий атрибуты color (цвет), width (ширина), height (высота).

class Rectangle:
    color = “green”
    width = 100
    height = 100

Доступ к атрибуту класса можно получить следующим образом.

имя_объекта.атрибут

rect1 = Rectangle()
print(rect1.color)

Добавим к нашему классу метод. Метод – это функция находящаяся внутри класса, выполняющая определенную работу, которая, чаще всего, предполагает доступ к атрибутам созданного объекта. Например, нашему классу Rectangle, можно добавить метод, считающий площадь прямоугольника. Для того, чтобы метод в классе знал, с каким объектом он работает (это нужно для того, чтобы получить доступ к атрибутам: ширина (width) и высота (height)), первым аргументом ему следует передать параметр self, через который он может получить доступ к своим данным.

class Rectangle:
   color = "green"
   width = 100
   height = 100
   def square(self):
       return self.width * self.height

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

class Rectangle:
   color = "green"
   width = 100
   height = 100
   def square(self):
       return self.width*self.height

rect1 = Rectangle()
print(rect1.color)
print(rect1.square())
rect2 = Rectangle()
rect2.width = 200
rect2.color = "brown"
print(rect2.color)
print(rect2.square())

Конструктор класса

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

 __init__(self)

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

class Rectangle:
   def __init__(self, color="green", width=100, height=100):
       self.color = color
       self.width = width
       self.height = height

   def square(self):
       return self.width * self.height


rect1 = Rectangle()
print(rect1.color)
print(rect1.square())
rect1 = Rectangle("yellow", 23, 34)
print(rect1.color)
print(rect1.square())

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

Синтаксически создание класса с указанием его родителя/ей выглядит так:

class имя_класса(имя_родителя1, [имя_родителя2,…, имя_родителя_n])

Доработаем наш пример так, чтобы в нем присутствовало наследование.

class Figure:
   def __init__(self, color):
       self.color = color

   def get_color(self):
       return self.color


class Rectangle(Figure):
   def __init__(self, color, width=100, height=100):
       super().__init__(color)
       self.width = width
       self.height = height

   def square(self):
       return self.width*self.height


rect1 = Rectangle("blue")
print(rect1.get_color())
print(rect1.square())
rect2 = Rectangle("red", 25, 70)
print(rect2.get_color())
print(rect2.square())

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

class Figure:
   def __init__(self, color):
       self.color = color
      
   def get_color(self):
       return self.color
      
   def info(self):
       print("Figure")
       print("Color: " + self.color)


class Rectangle(Figure):
   def __init__(self, color, width=100, height=100):
       super().__init__(color)
       self.width = width
       self.height = height

   def square(self):
       return self.width * self.height

   def info(self):
       print("Rectangle")
       print("Color: " + self.color)
       print("Width: " + str(self.width))
       print("Height: " + str(self.height))
       print("Square: " + str(self.square()))

fig1 = Figure("green")
print(fig1.info())
rect1 = Rectangle("red", 24, 45)
print(rect1.info())

Таким образом наследник класса может расширять и модифицировать функционал класса родителя.

P.S.

Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. На нашем сайте вы можете найти вводные уроки по этой теме. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.

<<< Python. Урок 13. Модули и пакеты   Python. Урок 15. Итераторы и генераторы>>>

devpractice.ru

Программирование на Python: Часть 6. Классы

Сергей Яковлев
Опубликовано 02.06.2017

Серия контента:

Этот контент является частью # из серии # статей:

https://www.ibm.com/developerworks/ru/library/?series_title_by=**auto**

Следите за выходом новых статей этой серии.

Этот контент является частью серии:

Следите за выходом новых статей этой серии.

Мы переходим к одной из самых интересных тем цикла — объектно-ориентированному программированию (ООП) в Python. С точки зрения ООП, класс представляет собой коллекцию данных. Использование классов дает нам прежде всего преимущества абстрактного подхода в программировании.

  1. Полиморфизм: в разных объектах одна и та же операция может выполнять различные функции. Слово «полиморфизм» имеет греческую природу и означает «имеющий многие формы». Простым примером полиморфизма может служить функция count(), выполняющая одинаковое действие для различных типов обьектов: 'abc'.count('a') и [1, 2, 'a'].count('a'). Оператор плюс полиморфичен при сложении чисел и при сложении строк.
  2. Инкапсуляция: можно скрыть ненужные внутренние подробности работы объекта от окружающего мира. Это второй основной принцип абстракции. Он основан на использовании атрибутов внутри класса. Атрибуты могут иметь различные состояния в промежутках между вызовами методов класса, вследствие чего сам объект данного класса также получает различные состояния — state.
  3. Наследование: можно создавать специализированные классы на основе базовых. Это позволяет нам избегать написания повторного кода.
  4. Композиция: объект может быть составным и включать в себя другие объекты.

Объектно-ориентированный подход в программировании подразумевает следующий алгоритм действий.

  1. Описывается проблема с помощью обычного языка с использованием понятий, действий, прилагательных.
  2. На основе понятий формулируются классы.
  3. На основе действий проектируются методы.
  4. Реализуются методы и атрибуты.

Мы получили скелет — объектную модель. На основе этой модели реализуется наследование. Для проверки модели:

  1. пишется так называемый use cases — сценарий возможного поведения модели, где проверяется вся функциональность;
  2. функционал при этом может быть исправлен либо добавлен.

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

Механизм классов языка Python представляет собой смесь механизмов классов C++ и Modula-3. Наиболее важные особенности классов в питоне:

  1. множественное наследование;
  2. производный класс может переопределить любые методы базовых классов;
  3. в любом месте можно вызвать метод с тем же именем базового класса;
  4. все атрибуты класса в питоне по умолчанию являются public, т.е. доступны отовсюду; все методы — виртуальные, т.е. перегружают базовые.

Сегодня мы рассмотрим следующие аспекты объектно-ориентированного программирования.

  1. Что такое класс.
  2. Атрибуты класса.
  3. self.
  4. Наследование.
  5. ООП в действии: пример создания классов.

1. Что такое класс

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

 class имя_класса:
 инструкция 1
 .
 инструкция №

Каждая такая запись генерирует свой объект класса. Отличие от C++ в том, что в плюсах описание класса — это лишь объявление, а в питоне — это создание объекта. Есть также другой тип объекта — инстанс класса, который генерируется при вызове:

 инстанс_класса = имя_класса()

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

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

Объекты классов поддерживают два вида операций:

  • доступ к атрибутам;
  • создание экземпляра класса.

2. Атрибуты класса

Атрибуты класса бывают двух видов:

  • атрибуты данных;
  • атрибуты-методы.

Атрибуты данных обычно записываются сверху. Память для атрибутов выделяется в момент их первого присваивания — либо снаружи, либо внутри метода. Методы начинаются со служебного слова def.

Доступ к атрибутам выполняется по схеме obj.attrname.

Пример.

class Simple:
 u'Простой класс'
 var = 87
 def f(x):
 return 'Hello world'

Здесь Simple.var и Simple.f — пользовательские атрибуты. Есть также стандартные атрибуты:

>>> print Simple.__doc__

Целое число

>>> print Simple.var.__doc__
int(x[, base]) -> integer
...

Создание экземпляра класса похоже на то, как будто мы делаем вызов функций:

 smpl = Simple()

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

 class Simple:
 def __init__(self):
 self.list = []

При создании объекта smpl будет создан пустой список list. Конструктору можно передать аргументы:

 class Simple:
 def __init__(self, count, str):
 self.list = []
 self.count = count
 self.str = str
 
>>> s = Simple(1,'22')
>>> s.count, s.str
1 22

Атрибут данных можно сделать приватным (private) — т.е. недоступным снаружи — для этого слева нужно поставить два символа подчеркивания:

class Simple:
 u'Простой класс с приватным атрибутом'
 __private_attr = 10 
 def __init__(self, count, str):
 self.__private_attr = 20
 print self.__private_attr
 
s = Simple(1,'22')
print s.__private_attr

Последняя строка вызовет исключение — атрибут __private_attr годен только для внутреннего использования.

Методы необязательно определять внутри тела класса:

def method_for_simple(self, x, y):
 return x + y

class Simple:
 f = method_for_simple
 
>>> s = Simple()
>>> print s.f(1,2)
3

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

class Customer:
 pass
custom = Customer()
custom.name = 'Вася'
custom.salary = 100000

3. self

Обычно первый аргумент в имени метода — self. Как говорит автор языка Гвидо Ван Россум, это не более чем соглашение: имя self не имеет абсолютно никакого специального значения.

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

class Simple:
 def __init__(self):
 self.list = []
 def f1(self):
 self.list.append(123)
 def f2(self):
 self.f1()
 
>>> s = Simple()
>>> s.f2()
>>> print s.list
[123]

Self — это аналог «this» в C++.

4. Наследование

Определение производного класса выглядит следующим образом:

 class Derived(Base):

Если базовый класс определен не в текущем модуле:

 class Derived(module_name.Base):

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

 Base.method()

В питоне существует ограниченная поддержка множественного наследования:

 class Derived(Base1,Base2,Base3):

Поиск атрибута производится в следующем порядке:

  1. в Derived;
  2. в Base1, затем рекурсивно в базовых классах Base1;
  3. в Base2, затем рекурсивно в базовых классах Base2
  4. и т.д.

5. Пример

Создадим два класса: Person — хранит общую информацию о людях — имя, профессия, зарплата; класс Manager — специализированный производный класс. В классе Person мы создадим свою версию для стандартной встроенной функции str, которая есть по умолчанию в любом питоновском классе — для этого она будет иметь префикс с двумя символами подчеркивания слева и справа. Когда мы попытаемся распечатать инстанс класса, будет вызвана __str__.

# -*- coding: utf-8 -*-
class Person:
 def __init__(self, name, job=None, pay=0):
 self.name = name
 self.job = job
 self.pay = pay
 def lastName(self):
 return self.name.split()[-1]
 def giveRaise(self, percent):
 self.pay = int(self.pay * (1 + percent))
 def __str__(self):
 return '[Person: %s, %s]' % (self.name, self.pay)

class Manager(Person):
 def __init__(self, name, pay): 
 Person.__init__(self, name, 'mgr', pay) 
 def giveRaise(self, percent, bonus=100):
 Person.giveRaise(self, percent + bonus)

Создаем первый инстанс класса Person:

>>> ivan = Person('Иван Petrov')

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

>>> john = Person('John Sidorov', job='dev', pay=100000)

Вызываем перегруженную функцию __str__;

>>> print(ivan)
>>> print(john)

Выводим фамилию:

>>> print(ivan.lastName(), john.lastName())

Начисляем премиальные:

>>> john.giveRaise(.10)

И получаем:

>>> print(john)

Создаем инстанс класса Manager:

>>> tom = Manager('Tom Jones', 50000)

Начисляем мегапремиальные:

>>> tom.giveRaise(.10)

Выводим:

print(tom.lastName())
print(tom)

Вывод:

[Person: Иван Petrov, 0]
[Person: John Sidorov, 100000]
('Petrov', 'Sidorov')
[Person: John Sidorov, 110000]
Jones
[Person: Tom Jones, 5055000]

Заключение

Основные свойства ООП — полиморфизм, наследование, инкапсуляция. Класс — это пользовательский тип, состоящий из методов и атрибутов. Инстанс класса создается путем вызова имени класса как функции с параметрами. Объект состоит из атрибутов и методов. Атрибут — это переменная, метод — это функция. Отличия метода от функции в том, что у него есть первый параметр — self. Полиморфизм позволяет нам работать с различными типами объектов так, что нам не нужно задумываться о том, к какому типу они принадлежат. Объекты могут скрывать (инкапсулировать) свое внутреннее состояние. Это достигается за счет того, что доступ к атрибутам осуществляется не напрямую, а через методы. Класс может быть производным от одного или нескольких классов. Производный класс наследует все методы базового класса. Базовых классов может быть несколько. Объектный дизайн должен быть прозрачным, понятным и описан, что называется, в ‘терминах человеческого языка’.

В следующей статье мы расскажем о работе со специальными методами и атрибутами классов. Код примеров проверялся на версии питона 2.6.

< Предыдущая статья. Следующая статья >

Ресурсы для скачивания

Подпишите меня на уведомления к комментариям

www.ibm.com

Классы в Python 3 и объекты — примеры наследования и что означает self

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

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

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

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

Благодаря такой особенности:

  • Улучшается восприятие поставленной задачи при работе над проектом;
  • Сокращается количество строк кода;
  • Уменьшается сложность написания кода.

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

Рассмотрим основные принципы ООП:

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

Создание класса и объекта

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

class Example:
    pass
example = Example()

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

Определив новый класс, можно создавать сколько угодно объектов на его основе. Как уже было сказано выше, такая структура данных может включать в себя некие свойства, то есть переменные, которыми будет наделен каждый экземпляр класса. Ниже приведен простой пример класса и объекта Python 3. В примере описывается класс под названием Data со строкой word и числом number.

class Data:
    word = "Python"
    number = 3
data = Data()
print(data.word + " " + str(data.number))

Python 3

Если создать объект, основанный на классе Data, то он получит обе переменные, а также их значения, которые были определены изначально. Таким образом, был сгенерирован объект data. Получить доступ к его полям с именами word и number можно с помощью оператора точки, вызвав его через экземпляр класса. Функция print поможет вывести значения полей объекта data на экран. Не стоит забывать и о том, что число следует привести к строчному виду для того чтобы обработать его в методе print вместе с текстовым значением.

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

class Data:
    def sayHello(self):
        print("Hello World!")
data = Data()
data.sayHello()

Hello World!

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

Аргумент self

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

class Dog:
    name = "Charlie"
    noise = "Woof!"
    def makeNoise(self):
        print(self.name + " says: " + self.noise + " " + self.noise)
dog = Dog()
dog.makeNoise()

Charlie says: Woof! Woof!

Вверху представлен класс Dog, описывающий собаку. Он обладает полями name (имя) со стартовым значением «Charlie» и noise (шум), содержащим звук, который издает животное. Метод makeNoise заставляет собаку лаять, выдавая соответствующее сообщение на экран. Для этого в функции print используется получение доступа к полям name и noise. Далее необходимо создать экземпляр класса Dog и вызвать на нем makeNoise.

Конструктор

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

class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed
dog = Dog("Max", "German Shepherd")
print(dog.name + " is "+ dog.breed)

Max is German Shepherd

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

Таким образом, класс Dog содержит два поля: name (имя) и breed (порода). Конструктор принимает параметры для изменения этих свойств во время инициализации нового объекта под названием dog. Каждый класс содержит в себе по крайней мере один конструктор, если ни одного из них не было задано явно. Однако в том случае, когда программист добавляет в свой класс конструктор с некими параметрами, конструктор, не обладающий параметрами, работать не будет. Чтобы им воспользоваться, нужно явно прописать его в классе.

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

Деструктор

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

class Data:
    def __del__(self):
        print "The object is destroyed"
data = Data()
del(data)

The object is destroyed

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

Наследование

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

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

При наследовании классов в Python обязательно следует соблюдать одно условие: класс-наследник должен представлять собой более частный случай класса-родителя. В следующем примере показывается как класс Person (Человек) наследуется классом Worker (Работник). При описании подкласса в Python, имя родительского класса записывается в круглых скобках.

class Person:
    name = "John"
class Worker(Person):
    wage = 2000
human = Worker()
print(human.name + " earns $" + str(human.wage))

John earns $2000

Person содержит поле name (имя), которое передается классу Worker, имеющему свойство wage (заработная плата). Все условия наследования соблюдены, так как работник является человеком и также обладает именем. Теперь, создав экземпляр класса Worker под названием human, можно получить свободный доступ к полям из родительской структуры данных.

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

Наследовать можно не только один класс, но и несколько одновременно, обретая тем самым их свойства и методы. В данном примере класс Dog (Собака) выступает в роли подкласса для Animal (Животное) и Pet (Питомец), поскольку может являться и тем, и другим. От Animal Dog получает способность спать (метод sleep), в то время как Pet дает возможность играть с хозяином (метод play). В свою очередь, оба родительских класса унаследовали поле name от Creature (Создание). Класс Dog также получил это свойство и может его использовать.

class Creature:
    def __init__(self, name):
        self.name = name
class Animal(Creature):
    def sleep(self):
        print(self.name + " is sleeping")
class Pet(Creature):
    def play(self):
        print(self.name + " is playing")
class Dog(Animal, Pet):
    def bark(self):
        print(self.name + " is barking")
beast = Dog("Buddy")
beast.sleep()
beast.play()
beast.bark()

Buddy is sleeping
Buddy is playing
Buddy is barking

В вышеописанном примере создается объект класса Dog, получающий имя в конструкторе. Затем по очереди выполняются методы sleep (спать), play (играть) и bark (лаять), двое из которых были унаследованы. Способность лаять является уникальной особенностью собаки, поскольку не каждое животное или домашний питомец умеет это делать.

Абстрактные методы

Поскольку в ООП присутствует возможность наследовать поведение родительского класса, иногда возникает необходимость в специфической реализации соответствующих методов. В качестве примера можно привести следующий код, где классы Dog (Собака) и Cat (Кошка) являются потомками класса Animal (Животное). Как и положено, они оба наследуют метод makeNoise (шуметь), однако в родительском классе для него не существует реализации.

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

class Animal:
    def __init__(self, name):
        self.name = name
    def makeNoise(self):
        pass
class Dog(Animal):
    def makeNoise(self):
        print(self.name + " says: Woof!")
class Cat(Animal):
    def makeNoise(self):
        print(self.name + " says: Meow!")
dog = Dog("Baxter")
cat = Cat("Oliver")
dog.makeNoise()
cat.makeNoise()

Baxter says: Woof!
Oliver says: Meow!

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

Статические методы

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

class Math:
    @staticmethod
    def inc(x):
        return x + 1
    @staticmethod
    def dec(x):
        return x - 1
print(Math.inc(10), Math.dec(10))

(11, 9)

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

Ограничение доступа

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

В такой ситуации помогает еще одна особенность ООП под названием инкапсуляция. Она предписывает применение приватных свойств класса, к которым отсутствует доступ за его пределами. Для управления содержимым объекта необходимо использовать специальные методы, именуемые getter (возвращает значение) и setter (устанавливает значение).

class Cat:
    __name = "Kitty"
    def get_name(self):
        return self.__name
    def set_name(self, name):
        self.__name = name
cat = Cat()
print(cat.get_name())
cat.set_name("Misty")
print(cat.get_name())

Kitty
Misty

Чтобы ограничить видимость полей, следует задать для них имя, начинающееся с двойного подчеркивания. В примере, продемонстрированном выше, класс Cat (Кошка) имеет закрытое свойство __name (имя), а также специальные методы get_name и set_name. Отличительной чертой такого подхода является возможность установить определенные рамки для вводимых значений. Например, можно запретить ввод отрицательного числа или пустой строки.

Свойства классов

С помощью специального механизма свойств класса можно внести корректировки в работу с оператором точки, присвоив ему собственные функции. В следующем примере представлен класс с приватным полем x, для которого написаны getter и setter. С помощью присвоения полю x специального значения функции property, получающей в качестве аргументов имена методов, можно настроить работу оператора точки согласно своим нуждам.

class Data:
    def __init__(self, x):
        self.__set_x(x)
    def __get_x(self):
        print("Get X")
        return self.__x
    def __set_x(self, x):
        self.__x = x
        print("Set X")
    x = property(__get_x, __set_x)
data = Data(10)
print(data.x)
data.x = 20
print(data.x)

Set X
Get X
10
20

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

Перегрузка операторов

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

В данном примере создается класс Point (точка), обладающий двумя полями: x и y. Для сравнения двух разных объектов такого типа можно написать специальный метод либо же просто перегрузить соответствующий оператор. Для этого потребуется переопределить функцию __eq__ в собственном классе, реализовав новое поведение в ее теле.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
print(Point(2, 5) == Point(2, 5))
print(Point(3, 8) == Point(4, 6))

True
False

Переопределенный метод возвращает результат сравнения двух полей у различных объектов. Благодаря этому появилась возможность сравнивать две разных точки, пользуясь всего лишь обычным оператором. Результат его работы выводится при помощи метода print.

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

Заключение

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

all-python.ru

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

Признак того, что объект не должен быть классом — если в нём всего 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 из итогов чётко говорит, что такое не класс. Классы нужны, но суть доклада — в том, что не нужно ими злоупотреблять.

habr.com

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

Сегодня мы поговорим об объектно-ориентированном программировании и о его применении в 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'

pythonworld.ru