Разное

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

Содержание

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

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

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

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

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

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

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

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

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

Полиморфизм

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

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

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

class C: 
    pass

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

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

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

Статические и динамические атрибуты класса

Как уже было сказано выше, класс может содержать атрибуты и методы. Атрибут может быть статическим и динамическим (уровня объекта класса). Суть в том, что для работы со статическим атрибутом, вам не нужно создавать экземпляр класса, а для работы с динамическим – нужно. Пример:

class Rectangle:
    default_color = "green"

    def __init__(self, width, height):
        self.width = width
        self.height = height

В представленном выше классе, атрибут default_color – это статический атрибут, и доступ к нему, как было сказано выше, можно получить не создавая объект класса Rectangle.

>>> Rectangle.default_color
'green'

width и height – это динамические атрибуты, при их создании было использовано ключевое слово self. Пока просто примите это как должное, более подробно про self будет рассказано ниже. Для доступа к width и height предварительно нужно создать объект класса Rectangle:

>>> rect = Rectangle(10, 20)
>>> rect.width
10
>>> rect.height
20

Если обратиться через класс, то получим ошибку:

>>> Rectangle.width
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Rectangle' has no attribute 'width'

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

Проверим ещё раз значение атрибута default_color:

>>> Rectangle.default_color
'green'

Присвоим ему новое значение:

>>> Rectangle.default_color = "red"
>>> Rectangle.default_color
'red'

Создадим два объекта класса Rectangle и проверим, что default_color у них совпадает:

>>> r1 = Rectangle(1,2)
>>> r2 = Rectangle(10, 20)
>>> r1.default_color
'red'
>>> r2.default_color
'red'

Если поменять значение default_color через имя класса Rectangle, то все будет ожидаемо: у объектов r1 и r2 это значение изменится, но если поменять его через экземпляр класса, то у экземпляра будет создан атрибут с таким же именем как статический, а доступ к последнему будет потерян:

Меняем default_color через r1:

>>> r1.default_color = "blue"
>>> r1.default_color
'blue'

При этом у r2 остается значение статического атрибута:

>>> r2.default_color
'red'
>>> Rectangle.default_color
'red'

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

Методы класса

Добавим к нашему классу метод. Метод – это функция, находящаяся внутри класса и выполняющая определенную работу.

Методы бывают статическими, классовыми (среднее между статическими и обычными) и уровня класса (будем их называть просто словом метод). Статический метод создается с декоратором @staticmethod, классовый – с декоратором @classmethod, первым аргументом в него передается cls, обычный метод создается без специального декоратора, ему первым аргументом передается self:

class MyClass:

    @staticmethod
    def ex_static_method():
        print("static method")

    @classmethod
    def ex_class_method(cls):
        print("class method")

    def ex_method(self):
        print("method")

Статический и классовый метод можно вызвать, не создавая экземпляр класса, для вызова ex_method() нужен объект:

>>> MyClass.ex_static_method()
static method

>>> MyClass.ex_class_method()
class method

>>> MyClass.ex_method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ex_method() missing 1 required positional argument: 'self'

>>> m = MyClass()
>>> m.ex_method()
method

Конструктор класса и инициализация экземпляра класса

В Python разделяют конструктор класса и метод для инициализации экземпляра класса. Конструктор класса это метод __new__(cls, *args, **kwargs) для инициализации экземпляра класса используется метод __init__(self). При этом, как вы могли заметить __new__ – это классовый метод, а __init__ таким не является. Метод __new__ редко переопределяется, чаще используется реализация от базового класса object (см. раздел Наследование), __init__ же наоборот является очень удобным способом задать параметры объекта при его создании.

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

class Rectangle:

    def __new__(cls, *args, **kwargs):
        print("Hello from __new__")
        return super().__new__(cls)

    def __init__(self, width, height):
        print("Hello from __init__")
        self.width = width
        self.height = height


>>> rect = Rectangle(10, 20)
Hello from __new__
Hello from __init__

>>> rect.width
10

>>> rect.height
20

Что такое self?

До этого момента вы уже успели познакомиться с ключевым словом self. self – это ссылка на текущий экземпляр класса, в таких языках как Java, C# аналогом является ключевое слово this. Через self вы получаете доступ к атрибутам и методам класса внутри него:

class Rectangle:

    def __init__(self, width, height):
        self.width = width
        self.height = height

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

В приведенной реализации метод area получает доступ к атрибутам width и height для расчета площади. Если бы в качестве первого параметра не было указано self, то при попытке вызвать area программа была бы остановлена с ошибкой.

Уровни доступа атрибута и метода

Если вы знакомы с языками программирования Java, C#, C++ то, наверное, уже задались вопросом: “а как управлять уровнем доступа?”. В перечисленных языка вы можете явно указать для переменной, что доступ к ней снаружи класса запрещен, это делается с помощью ключевых слов (private, protected и т.д.). В Python таких возможностей нет, и любой может обратиться к атрибутам и методам вашего класса, если возникнет такая необходимость. Это существенный недостаток этого языка, т.к. нарушается один из ключевых принципов ООП – инкапсуляция. Хорошим тоном считается, что для чтения/изменения какого-то атрибута должны использоваться специальные методы, которые называются getter/setter, их можно реализовать, но ничего не помешает изменить атрибут напрямую. При этом есть соглашение, что метод или атрибут, который начинается с нижнего подчеркивания, является скрытым, и снаружи класса трогать его не нужно (хотя сделать это можно).

Внесем соответствующие изменения в класс Rectangle:

class Rectangle:

    def __init__(self, width, height):
        self._width = width
        self._height = height

    def get_width(self):
        return self._width

    def set_width(self, w):
        self._width = w

    def get_height(self):
        return self._height

    def set_height(self, h):
        self._height = h

    def area(self):
        return self._width * self._height

В приведенном примере для доступа к _width и _height используются специальные методы, но ничего не мешает вам обратиться к ним (атрибутам) напрямую.

>>> rect = Rectangle(10, 20)

>>> rect.get_width()
10

>>> rect._width
10

Если же атрибут или метод начинается с двух подчеркиваний, то тут напрямую вы к нему уже не обратитесь (простым образом). Модифицируем наш класс Rectangle:

class Rectangle:

    def __init__(self, width, height):
        self.__width = width
        self.__height = height

    def get_width(self):
        return self.__width

    def set_width(self, w):
        self.__width = w

    def get_height(self):
        return self.__height

    def set_height(self, h):
        self.__height = h

    def area(self):
        return self.__width * self.__height

Попытка обратиться к __width напрямую вызовет ошибку, нужно работать только через get_width():

>>> rect = Rectangle(10, 20)

>>> rect.__width
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Rectangle' object has no attribute '__width'

>>> rect.get_width()
10

Но на самом деле это сделать можно, просто этот атрибут теперь для внешнего использования носит название: _Rectangle__width:

>>> rect._Rectangle__width
10

>>> rect._Rectangle__width = 20

>>> rect.get_width()
20

Свойства

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

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

Сделаем реализацию класса Rectangle с использованием свойств:

class Rectangle:

    def __init__(self, width, height):
        self.__width = width
        self.__height = height

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, w):
        if w > 0:
            self.__width = w
        else:
            raise ValueError

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, h):
        if h > 0:
            self.__height = h
        else:
            raise ValueError

    def area(self):
        return self.__width * self.__height

Теперь работать с width и height можно так, как будто они являются атрибутами:

>>> rect = Rectangle(10, 20)

>>> rect.width
10

>>> rect.height
20

Можно не только читать, но и задавать новые значения свойствам:

>>> rect.width = 50

>>> rect.width
50

>>> rect.height = 70

>>> rect.height
70

Если вы обратили внимание: в setter’ах этих свойств осуществляется проверка входных значений, если значение меньше нуля, то будет выброшено исключение ValueError:

>>> rect.width = -10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 28, in width
    raise ValueError
ValueError

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

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

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

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

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

class Figure:

    def __init__(self, color):
        self.__color = color

    @property
    def color(self):
        return self.__color

    @color.setter
    def color(self, c):
        self.__color = c


class Rectangle(Figure): 

    def __init__(self, width, height, color):
        super().__init__(color)
        self.__width = width
        self.__height = height

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, w):
        if w > 0:
            self.__width = w
        else:
            raise ValueError

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, h):
        if h > 0:
            self.__height = h
        else:
            raise ValueError 

    def area(self):
        return self.__width * self.__height

Родительским классом является Figure, который при инициализации принимает цвет фигуры и предоставляет его через свойства. Rectangle – класс наследник от Figure. Обратите внимание на его метод __init__: в нем первым делом вызывается конструктор (хотя это не совсем верно, но будем говорить так) его родительского класса:

super().__init__(color)

super – это ключевое слово, которое используется для обращения к родительскому классу.

Теперь у объекта класса Rectangle помимо уже знакомых свойств width и height появилось свойство color:

>>> rect = Rectangle(10, 20, "green")

>>> rect.width
10

>>> rect.height
20

>>> rect.color
'green'

>>> rect.color = "red"

>>> rect.color
'red'

Полиморфизм

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

class Figure:

    def __init__(self, color):
        self.__color = color

    @property
    def color(self):
        return self.__color

    @color.setter
    def color(self, c):
        self.__color = c

    def info(self):
       print("Figure")
       print("Color: " + self.__color)


class Rectangle(Figure):

    def __init__(self, width, height, color):
        super().__init__(color)
        self.__width = width
        self.__height = height

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, w):
        if w > 0:
            self.__width = w
        else:
            raise ValueError

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, h):
        if h > 0:
            self.__height = h
        else:
            raise ValueError

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

    def area(self):
        return self.__width * self.__height

Посмотрим, как это работает

>>> fig = Figure("orange")

>>> fig.info()
Figure
Color: orange

>>> rect = Rectangle(10, 20, "green")

>>> rect.info()
Rectangle
Color: green
Width: 10
Height: 20
Area: 200

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

P.S.

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

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

ОСновы работы с классами в python 3 ~ PythonRu

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

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

Процедурно-ориентированный

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

Объектно-ориентированный

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

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

Синтаксис для написания нового класса:

class ClassName:
    'Краткое описание класса (необязательно)'
    
  • Для создания класса пишется ключевое слово class, его имя и двоеточие (:). Первая строчка в теле класса описывает его. (По желанию) получить доступ к этой строке можно с помощью ClassName.__doc__
  • В теле класса допускается объявление атрибутов, методов и конструктора.

Атрибут:

Атрибут — это элемент класса. Например, у прямоугольника таких 2: ширина (width) и высота (height).

Метод:

  • Метод класса напоминает классическую функцию, но на самом деле — это функция класса. Для использования ее необходимо вызывать через объект.
  • Первый параметр метода всегда self (ключевое слово, которое ссылается на сам класс).

Конструктор:

  • Конструктор — уникальный метод класса, который называется __init__.
  • Первый параметр конструктора во всех случаях self (ключевое слово, которое ссылается на сам класс).
  • Конструктор нужен для создания объекта.
  • Конструктор передает значения аргументов свойствам создаваемого объекта.
  • В одном классе всегда только один конструктор.
  • Если класс определяется не конструктором, Python предположит, что он наследует конструктор родительского класса.

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:


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())

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

Подписывайтесь на телеграм каналы

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

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

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

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

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)

Например:

from person import Person
 

aimee = Person("Aimee", 21, "Female")
aimee.showInfo()
print(" --------------- ")
 

alice = Person( "Alice" )
alice.showInfo()
 
print(" --------------- ")
 

tran = Person("Tran", 37)
tran.showInfo()

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

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

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

Оператор == нужен, чтобы узнать, ссылаются ли два объекта на одно и то же место в памяти. Он вернет True, если это так. Оператор != вернет True, если сравнить 2 объекта, которые ссылаются на разные места в памяти.

from rectangle import Rectangle


r1 = Rectangle(20, 10)
r2 = Rectangle(20 , 10)
r3 = r1
 

test1 = r1 == r2 

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

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

Атрибуты

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

  1. Атрибуты
  2. Переменные класса

Стоит разобрать на практике:

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.age = 21
 
print("player1.name = ", player1.name)
print("player1.age = ", player1.age)
 
print("player2.name = ", player2.name)
print("player2.age = ", player2.age)

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

from player import Player 
 
 
player1 = Player("Tom", 20)
player2 = Player("Jerry", 20)
 

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)
 

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'

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

Обычно получать доступ к атрибутам объекта можно с помощью оператора «точка» (например, player1.name). Но Python умеет делать это и с помощью функции.

Функция Описание
getattr (obj, name[,default]) Возвращает значение атрибута или значение по умолчанию, если первое не было указано
hasattr (obj, name) Проверяет атрибут объекта — был ли он передан аргументом «name»
setattr (obj, name, value) Задает значение атрибута. Если атрибута не существует, создает его
delattr (obj, name) Удаляет атрибут
from player import Player 
 
 
player1 = Player("Tom", 20)
 

print("getattr(player1,'name') = " , getattr(player1,"name"))

print("setattr(player1,'age', 21): ")

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

hasAddress =  hasattr(player1, "address")
print("hasattr(player1, 'address') ? ", hasAddress)
 

print("Create attribute 'address' for object 'player1'")
setattr(player1, 'address', "USA")
print("player1.address = ", player1.address)
 

delattr(player1, "address")

Вывод:

getattr(player1,'name') =  Tom
setattr(player1,'age', 21): 
player1.age =  21
hasattr(player1, 'address') ?  False
Create attribute 'address' for object 'player1'
player1.address =  USA

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

Объекты класса — дочерние элементы по отношению к атрибутам самого языка Python. Таким образом они заимствуют некоторые атрибуты:

Атрибут Описание
__dict__ Предоставляет данные о классе коротко и доступно, в виде словаря
__doc__ Возвращает строку с описанием класса, или None, если значение не определено
__class__ Возвращает объект, содержащий информацию о классе с массой полезных атрибутов, включая атрибут __name__
__module__ Возвращает имя «модуля» класса или __main__, если класс определен в выполняемом модуле.
class Customer:
    'Это класс Customer'
    def __init__(self, name, phone, address):        
        self.name = name
        self.phone = phone
        self.address = address
 
  
john = Customer("John",1234567, "USA")
 
print ("john.__dict__ = ", john.__dict__)
print ("john.__doc__ = ", john.__doc__)
print ("john.__class__ = ", john.__class__)
print ("john.__class__.__name__ = ", john.__class__.__name__) 
print ("john.__module__ = ", john.__module__)  

Вывод:

john.__dict__ =  {'name': 'John', 'phone': 1234567, 'address': 'USA'}
john.__doc__ =  Это класс Customer
john.__class__ =  <class '__main__.Customer'>
john.__class__.__name__ =  Customer
john.__module__ =  __main__

Переменные класса

Переменные класса в Python — это то же самое, что Field в других языках, таких как Java или С#. Получить к ним доступ можно только с помощью имени класса или объекта.

Для получения доступа к переменной класса лучше все-таки использовать имя класса, а не объект. Это поможет не путать «переменную класса» и атрибуты.

У каждой переменной класса есть свой адрес в памяти. И он доступен всем объектам класса.

from player import Player 
 
 
player1 = Player("Tom", 20)
player2 = Player("Jerry", 20)
 

print ("Player.minAge = ", Player.minAge)
 

print("player1.minAge = ", player1.minAge) 
print("player2.minAge = ", player2.minAge)
 
print(" ------------ ") 

print("Assign new value to minAge via class name, and print..")
 

Player.minAge = 19
 
print("Player.minAge = ", Player.minAge) 
print("player1.minAge = ", player1.minAge) 
print("player2.minAge = ", player2.minAge)

Вывод:

Player.minAge =  18
player1.minAge =  18
player2.minAge =  18
 ------------ 
Assign new value to minAge via class name, and print..
Player.minAge =  19
player1.minAge =  19
player2.minAge =  19

Составляющие класса или объекта

В Python присутствует функция dir, которая выводит список всех методов, атрибутов и переменных класса или объекта.

from player import Player
 
 

print(dir(Player))
print("\n\n")
 
player1 = Player("Tom", 20)
player1.address ="USA"


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']

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

Классы и объекты

Последнее обновление: 06.07.2018

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

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

С точки зрения кода класс объединяет набор функций и переменных, которые выполняют определенную задачу. Функции класса еще называют методами.
Они определяют поведение класса. А переменные класса называют атрибутами- они хранят состояние класса

Класс определяется с помощью ключевого слова class:


class название_класса:
    методы_класса

Для создания объекта класса используется следующий синтаксис:


название_объекта = название_класса([параметры])

Например, определим простейший класс Person, который будет представлять человека:


class Person:
    name = "Tom"

    def display_info(self):
        print("Привет, меня зовут", self.name)

person1 = Person()
person1.display_info()         # Привет, меня зовут Tom

person2 = Person()
person2.name = "Sam"
person2.display_info()         # Привет, меня зовут Sam

Класс Person определяет атрибут name, который хранит имя человека, и метод display_info, с помощью которого выводится информация о человеке.

При определении методов любого класса следует учитывать, что все они должны принимать в качестве первого параметра ссылку на текущий объект, который согласно условностям называется
self (в ряде языков программирования есть своего рода аналог — ключевое слово this). Через эту ссылку внутри класса мы можем обратиться к методам или атрибутам этого же класса.
В частности, через выражение self.name можно получить имя пользователя.

После определения класс Person создаем пару его объектов — person1 и person2. Используя имя объекта, мы можем обратиться к его методам и атрибутам.
В данном случае у каждого из объектов вызываем метод display_info(), который выводит строку на консоль, и у второго объекта также изменяем атрибут name.
При этом при вызове метода display_info не надо передавать значение для параметра self.

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

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


person1 = Person()
person2 = Person()

Однако мы можем явным образом определить в классах конструктор с помощью специального метода, который называется __init().
К примеру, изменим класс Person, добавив в него конструктор:


class Person:

    # конструктор
    def __init__(self, name):
        self.name = name  # устанавливаем имя

    def display_info(self):
        print("Привет, меня зовут", self.name)


person1 = Person("Tom")
person1.display_info()         # Привет, меня зовут Tom
person2 = Person("Sam")
person2.display_info()         # Привет, меня зовут Sam

В качестве первого параметра конструктор также принимает ссылку на текущий объект — self. Нередко в конструкторах устанавливаются атрибуты класса.
Так, в данном случае в качестве второго параметра в конструктор передается имя пользователя, которое устанавливается для атрибута self.name.
Причем для атрибута необязательно определять в классе переменную name, как это было в предыдущей версии класса Person. Установка значения
self.name = name уже неявно создает атрибут name.


person1 = Person("Tom")
person2 = Person("Sam")

В итоге мы получим следующий консольный вывод:


Привет, меня зовут Tom
Привет, меня зовут Sam

Деструктор

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


person1 = Person("Tom")
del person1		# удаление из памяти
# person1.display_info()  # Этот метод работать не будет, так как person1 уже удален из памяти

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

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


class Person:
    # конструктор
    def __init__(self, name):
        self.name = name  # устанавливаем имя

    def __del__(self):
        print(self.name,"удален из памяти")
    def display_info(self):
        print("Привет, меня зовут", self.name)


person1 = Person("Tom")
person1.display_info()  # Привет, меня зовут Tom
del person1     # удаление из памяти
person2 = Person("Sam")
person2.display_info()  # Привет, меня зовут Sam

Консольный вывод:


Привет, меня зовут Tom
Tom удален из памяти
Привет, меня зовут Sam
Sam удален из памяти

Определение классов в модулях и подключение

Как правило, классы размещаются в отдельных модулях и затем уже импортируются в основой скрипт программы. Пусть у нас будет
в проекте два файла: файл main.py (основной скрипт программы) и classes.py (скрипт с определением классов).

В файле classes.py определим два класса:


class Person:

    # конструктор
    def __init__(self, name):
        self.name = name  # устанавливаем имя

    def display_info(self):
        print("Привет, меня зовут", self.name)


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

    def move(self, speed):
        print(self.name, "едет со скоростью", speed, "км/ч")

В дополнение к классу Person здесь также определен класс Auto, который представляет машину и который имеет метод move и атрибут name. Подключим эти классы и используем их в
скрипте main.py:


from classes import Person, Auto

tom = Person("Tom")
tom.display_info()

bmw = Auto("BMW")
bmw.move(65)

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


import classes

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

В итоге мы получим следующий консольный вывод:


Привет, меня зовут Tom
BMW едет со скоростью 65 км/ч

Классы в 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 и наглядно продемонстрированы наиболее важные принципы объектно-ориентированного программирования. Работа с классами позволяет представить все данные в программе в виде взаимодействующих между собой объектов, обладающих некими свойствами и поведением.

Заметки об объектной системе языка Python ч.1 / Хабр

Несколько заметок об объектной системе python’a. Рассчитаны на тех, кто уже умеет программировать на python. Речь идет только о новых классах (new-style classes) в python 2.3 и выше. В этой статье рассказывается, что такое объекты и как происходит поиск атрибутов.

Объекты

Все данные в питоне — это объекты. Каждый объект имеет 2 специальных атрибута __class__ и __dict__.

  • __class__ — определяет класс или тип, экзмепляром которого является объект. Тип (или класс объекта) определяет его поведение; он есть у всех объектов, в том числе и встроенных. Тип и класс — это разные названия одного и того же. x.__class__ <==> type(x).
  • __dict__ словарь, дающий доступ к внутреннему пространству имен, он есть почти у всех объектов, у многих встроенных типов его нет.

Примеры.

>>> def foo(): pass
... 
>>> foo.__class__
<type 'function'>
>>> foo.__dict__
{}
>>> (42).__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__dict__'
>>> (42).__class__
<type 'int'>
>>> class A(object):
...     qux = 'A'
...     def __init__(self, name):
...         self.name=name
...     def foo(self):
...         print 'foo'
... 
>>> a = A('a')

У a тоже есть __dict__ и __class__:

>>> a.__dict__   {'name': 'a'}
>>> a.__class__  
<class '__main__.A'>
>>> type(a)
<class '__main__.A'>
>>> a.__class__ is type(a)
True

Класс и тип — это одно и то же.

>>> a.__class__ is type(a) is A
True

a.__dict__ — это словарь, в котором находятся внутренние (или специфичные для объекта) атрибуты, в данном случае ‘name’. А в a.__class__ класс (тип).

И, например, в методах класса присваивание self.foo = bar практически идентично self.__dict__[‘foo’] = bar или сводится к аналогичному вызову.

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

Пример. Переопределим класс объекта a:

>>> class B(object):
...     qux = 'B'
...     def __init__(self):
...         self.name = 'B object'
...     def bar(self):
 ...         print 'bar'
... 
>>> a.__dict__
{'name': 'a'}
>>> a.foo()
foo
>>> a.__class__
<class '__main__.A'>
>>> a.__class__ = B
>>> a.__class__
<class '__main__.B'>

Смотрим, что поменялось.

Значение a.name осталось прежним, т.е. __init__ не вызывался при смене класса.

>>> a.__dict__
{'name': 'a'}


Доступ к классовым переменным и методам «прошлого» класса A пропал:
>>> a.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'B' object has no attribute 'foo'


А вот классовые переменные и методы класса B доступы:
>>> a.bar()
bar
>>> a.qux
'B'

Работа с атрибутам объекта: установка, удаление и поиск, равносильна вызову встроенных функций settattr, delattr, getattr:

a.x = 1 <==> setattr(a, ‘x’, 1)

del a.x <==> delattr(a, ‘x’)

a.x <==> getattr(a, ‘x’)

При этом стоит стоит понимать, что setattr и delattr влияют и изменяют только сам объект (точнее a.__dict__), и не изменяют класс объекта.

qux — является классовой переменной, т.е. она «принадлежит» классу B, а не объекту a:

>>> a.qux
'B'
>>> a.__dict__
{'name': 'a'}

Если мы попытаемся удалить этот атрибут, то получим ошибку, т.к. delattr будет пытаться удалить атрибут из a.__dict__

>>> delattr(a, 'qux')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: qux
>>> del a.qux
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: qux
>>> a.qux
'B'

>>>

Далее, если мы попытаемся изменить (установить) атрибут, setattr поместит его в __dict__, специфичный для данного, конкретного объекта.

>>> b = B()
>>> b.qux
'B'
>>> a.qux = 'myB'
>>> a.qux
'myB'
>>> a.__dict__
{'qux': 'myB', 'name': 'a'}
>>> b.qux
'B'
>>> 

Ну и раз есть ‘qux’ в __dict__ объекта, его можно удалить с помощью delattr:

>>> del a.qux

После удаления, a.qux будет возвращать значение классовой переменной:

>>> a.qux
'B'
>>> a.__dict__
{'name': 'a'}

Итак:

  • класс для объекта — это значение специального атрибута __class__ и его можно менять. (Хотя в официальной документации говорится, что никаких гарантий нет, но на самом деле можно)
  • почти каждый объект имеет свое пространство имен (атрибутов), доступ (не всегда полный), к которому осуществляется с помощью специального атрибута __dict__
  • класс фактичеки влияет только на поиск атрибутов, которых нет в __dict__, как-то: методы класса, дескрипторы, магические методы, классовые переменные и прочее.
Объекты и классы

Классы — это объекты, и у них тоже есть специальные атрибуты __class__ и __dict__.

>>> class A(object):
...     pass
... 

У класса тип type.

>>> A.__class__
<type 'type'>

Правда __dict__ у классов не совсем словарь

>>> A.__dict__
<dictproxy object at 0x1111e88>

Но __dict__ ответственен за доступ к внутреннему пространству имен, в котором хранятся методы, дескрипторы, переменные, свойства и прочее:

>>> dict(A.__dict__)
{'__module__': '__main__', 'qux': 'A', '__dict__': <attribute '__dict__' of 'A' objects>, 'foo': <function foo at 0x7f7797a25c08>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
>>> A.__dict__.keys()
['__module__', 'qux', '__dict__', 'foo', '__weakref__', '__doc__']<

В классах помимо __class__ и __dict__, имеется еще несколько специальных атрибутов: __bases__ — список прямых родителей, __name__ — имя класса. [1]

Классы можно считать эдакими расширениями обычных объектов, которые реализуют интерфейс типа. Множество всех классов (или типов) принадлежат множеству всех объектов, а точнее является его подмножеством. Иначе говоря, любой класс является объектом, но не всякий объект является классом. Договоримся называть обычными объектами(regular objects) те объекты, которые классами не являются.

Небольшая демонстрация, которая станет лучше понятна чуть позже.

Класс является объектом.
>>> class A(object):
...     pass
... 

>>> isinstance(A, object)
True

Число — это тоже объект.

>>> isinstance(42, object)
True

Класс — это класс (т.е. тип).

>>> isinstance(A, type)
True


А вот число классом (типом) не является. (Что такое type будет пояснено позже)

>>> isinstance(42, type)
False

>>>

Ну и a — тоже обычный объект.

>>> a = A()
>>> isinstance(a, A)
True
>>> isinstance(a, object)
True
>>> isinstance(a, type)
False

И у A всего один прямой родительский класс — object.

>>> A.__bases__
(<type 'object'>,)

Часть специальных параметров можно даже менять:

>>> A.__name__
'A'
>>> A.__name__ = 'B'
>>> A
<class '__main__.B'>

С помощью getattr получаем доступ к атрибутам класса:

>>> A.qux
'A'
>>> A.foo
<unbound method A.foo>
>>> 

Поиск атрибутов в обычном объекте

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

Пример.

>>> class A(object):
...     qux = 'A'
...     def __init__(self, name):
...         self.name=name
...     def foo(self):
...         print 'foo'
... 
>>> a = A()
>>> b = A()

Т.к. в обычных объектах a и b нет в __dict__ атрибута ‘qux’, то поиск продолжается во внутреннем словаре __dict__ их типа (класса), а потом по __dict__ словарям родителей в определенном порядке:

>>> b.qux
'A'
>>> A.qux
'A'

Меняем атрибут qux у класса A. И соответственно должны поменяться значения, которые возвращают экземпляры класса A — a и b:

>>> A.qux='B'
>>> a.qux
'B'
>>> b.qux
'B'
>>> 

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

>>> A.quux = lambda self: 'i have quux method'
>>> A.__dict__['quux']
<function <lambda> at 0x7f7797a25b90>
>>> A.quux
<unbound method A.<lambda>>

И доступ к нему появится у экземпляров:

>>> a.quux()
'i have quux method'

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

>>> del A.qux

Она удалиться из __dict__

>>> A.__dict__['qux']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'qux'

И доступ у экземляров пропадет.

>>> a.qux
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'qux'
>>> 

У классов почти такой же поиск атрибутов, как и у обычных объектов, но есть отличия: поиск начинается с собственного __dict__ словаря, а потом идет поиск по __dict__ словарям суперклассов (которые хранятся в __bases__) по опредленному алгоритму, а затем по классу в __class__ и его суперклассах. (Подробнее об этом позже).

Cсылки

  • Unifying types and classes in Python — главный документ, объясняющий что, как и зачем в новых классах.
  • Making Types Look More Like Classes — PEP 252, описывающий отличие старых классов от новых.
  • Built-in functions — детальное описание работы всех встроенных функций.
  • Data model — детальное описание модели данных python’а.
  • Python types and objects — объяснение объектной модели python на простых примерах с картинками.
Примечания

[1] О __module__ и __doc__ для простоты изложения пока забудем. Полный список атрибутов класса можно посмотреть в документации

Читать дальше

Заметки об объектной системе языка Python ч.2
Заметки об объектной системе языка Python ч.3

методы и атрибуты классов ~ 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 хранит встроенные атрибуты, и предоставляет к ним доступ через оператор ., как и любой другой атрибут

  • __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

Создание классов и объектов. Урок 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 – получить).

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

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

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

oop — Что такое объект в Python?

Переполнение стека

  1. Около
  2. Продукты

  3. Для команд
  1. Переполнение стека
    Общественные вопросы и ответы

  2. Переполнение стека для команд
    Где разработчики и технологи делятся частными знаниями с коллегами

  3. Вакансии
    Программирование и связанные с ним технические возможности карьерного роста

  4. Талант
    Нанимайте технических специалистов и создавайте свой бренд работодателя

  5. Реклама
    Обратитесь к разработчикам и технологам со всего мира

  6. О компании

Загрузка…

.

Общие структуры объектов — документация Python 3.8.6

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

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

PyObject

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

PyVarObject

Это расширение PyObject , которое добавляет ob_size
поле. Это используется только для объектов, которые имеют некоторое представление о длине .
Этот тип не часто появляется в API Python / C.
Доступ к участникам должен осуществляться с помощью макросов
Py_REFCNT , Py_TYPE и Py_SIZE .

PyObject_HEAD

Это макрос, используемый при объявлении новых типов, представляющих объекты
без переменной длины.Макрос PyObject_HEAD расширяется до:

См. Документацию по PyObject выше.

PyObject_VAR_HEAD

Это макрос, используемый при объявлении новых типов, представляющих объекты
с длиной, которая варьируется от экземпляра к экземпляру.
Макрос PyObject_VAR_HEAD расширяется до:

См. Документацию к PyVarObject выше.

Py_TYPE (о)

Этот макрос используется для доступа к члену ob_type объекта Python.Расширяется до:

 (((PyObject *) (o)) -> тип_объекта)
 
Py_REFCNT (о)

Этот макрос используется для доступа к члену ob_refcnt Python
объект.
Расширяется до:

 (((PyObject *) (o)) -> ob_refcnt)
 
Py_SIZE (о)

Этот макрос используется для доступа к члену ob_size объекта Python.
Расширяется до:

 (((PyVarObject *) (o)) -> размер_объекта)
 
PyObject_HEAD_INIT (тип)

Это макрос, который расширяется до значений инициализации для нового
PyObject типа.Этот макрос расширяется до:

 _PyObject_EXTRA_INIT
1, тип,
 
PyVarObject_HEAD_INIT (тип, размер)

Это макрос, который расширяется до значений инициализации для нового
PyVarObject типа , включая поле ob_size .
Этот макрос расширяется до:

 _PyObject_EXTRA_INIT
1, тип, размер,
 
PyCFunction

Тип функций, используемых для реализации большинства вызываемых Python в C.Функции этого типа принимают два параметра PyObject * и возвращают
одно такое значение. Если возвращаемое значение NULL , исключение должно иметь
был установлен. Если не NULL , возвращаемое значение интерпретируется как возвращаемое.
значение функции, представленное в Python. Функция должна возвращать новый
ссылка.

PyCFunctionWithKeywords

Тип функций, используемых для реализации вызываемых Python в C
с подписью METH_VARARGS | МЕТОД_КЛЮЧЕВЫЕ СЛОВА .

_PyCFunctionFast

Тип функций, используемых для реализации вызываемых Python в C
с подписью METH_FASTCALL .

_PyCFunctionFastWithKeywords

Тип функций, используемых для реализации вызываемых Python в C
с подписью METH_FASTCALL | МЕТОД_КЛЮЧЕВЫЕ СЛОВА .

PyMethodDef

Структура, используемая для описания метода типа расширения.Эта структура имеет
четыре поля:

Поле

C Тип

Значение

мл_наименование

постоянный символ *

наименование метода

мл_мет

PyCFunction

указатель на C
реализация

.Объекты типа

— документация Python 2.7.18

Возможно, одной из самых важных структур объектной системы Python является
структура, определяющая новый тип: структура PyTypeObject . Тип
объекты могут обрабатываться с помощью любого из PyObject _ * () или
PyType _ * () функций, но не предлагают много интересного для большинства
Приложения Python. Эти объекты имеют фундаментальное значение для поведения объектов, поэтому
они очень важны для самого интерпретатора и для любого модуля расширения
который реализует новые типы.

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

Typedefs: unaryfunc, binaryfunc, ternaryfunc, запрос, принуждение, intargfunc,
intintargfunc, intobjargproc, intintobjargproc, objobjargproc, деструктор,
freefunc, printfunc, getattrfunc, getattrofunc, setattrfunc, setattrofunc,
cmpfunc, reprfunc, hashfunc

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

 typedef struct _typeobject {
    PyObject_VAR_HEAD
    char * tp_name; / * Для печати в формате «<модуль>. <Имя>» * /
    int tp_basicsize, tp_itemsize; / * Для размещения * /

    / * Способы реализации стандартных операций * /

    деструктор tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    cmpfunc tp_compare;
    reprfunc tp_repr;

    / * Наборы методов для стандартных классов * /

    PyNumberMethods * tp_as_number;
    PySequenceMethods * tp_as_sequence;
    PyMappingMethods * tp_as_mapping;

    / * Более стандартные операции (здесь для бинарной совместимости) * /

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    / * Функции для доступа к объекту как к буферу ввода / вывода * /
    PyBufferProcs * tp_as_buffer;

    / * Флаги для определения наличия дополнительных / расширенных функций * /
    long tp_flags;

    char * tp_doc; / * Строка документации * /

    / * Назначенное значение в версии 2.0 * /
    / * вызываем функцию для всех доступных объектов * /
    traverseproc tp_traverse;

    / * удаляем ссылки на содержащиеся объекты * /
    запрос tp_clear;

    / * Назначенное значение в версии 2.1 * /
    / * богатые сравнения * /
    richcmpfunc tp_richcompare;

    / * активатор слабых ссылок * /
    long tp_weaklistoffset;

    / * Добавлено в версии 2.2 * /
    / * Итераторы * /
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    / * Дескриптор атрибута и подклассы * /
    struct PyMethodDef * tp_methods;
    struct PyMemberDef * tp_members;
    struct PyGetSetDef * tp_getset;
    struct _typeobject * tp_base;
    PyObject * tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    long tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; / * Процедура низкоуровневой свободной памяти * /
    запрос tp_is_gc; / * Для PyObject_IS_GC * /
    PyObject * tp_bases;
    PyObject * tp_mro; / * порядок разрешения методов * /
    PyObject * tp_cache;
    PyObject * tp_subclasses;
    PyObject * tp_weaklist;

} PyTypeObject;
 

Структура объекта типа расширяет структуру PyVarObject
ob_size поле используется для динамических типов (создается type_new () ,
обычно вызывается из оператора класса). Обратите внимание, что PyType_Type (
metatype) инициализирует tp_itemsize , что означает, что его экземпляры (т.е.
type) должен иметь поле ob_size .

PyObject * PyObject._ob_next
PyObject * PyObject._ob_prev

Эти поля присутствуют, только если определен макрос Py_TRACE_REFS .Их инициализация до NULL выполняется PyObject_HEAD_INIT
макрос. Для статически размещенных объектов эти поля всегда остаются NULL .
Для динамически выделяемых объектов эти два поля используются для связывания объекта.
в двусвязный список из всех живых объектов в куче. Это можно было бы использовать
для различных целей отладки; в настоящее время единственное использование — распечатать объекты
которые все еще живы в конце запуска, когда переменная среды
PYTHONDUMPREFS установлен.

Эти поля не наследуются подтипами.

Py_ssize_t PyObject.ob_refcnt

Это счетчик ссылок на объект типа, инициализированный как 1
PyObject_HEAD_INIT макрос. Обратите внимание, что для статически выделенных объектов типа
экземпляры типа (объекты, чьи ob_type указывают на тип) делают
, а не , считаются ссылками. Но для динамически выделяемых объектов типа
экземпляры и считаются ссылками.

Это поле не наследуется подтипами.

Изменено в версии 2.5: это поле было типом int . Это может потребовать изменений
в вашем коде для правильной поддержки 64-битных систем.

PyTypeObject * PyObject.ob_type

Это тип типа, другими словами, его метатип. Инициализируется
аргумент макроса PyObject_HEAD_INIT , и его значение обычно должно быть
и PyType_Type .Однако для динамически загружаемых модулей расширения, которые должны
быть пригодным для использования в Windows (по крайней мере), компилятор жалуется, что это недопустимый
инициализатор. Следовательно, по соглашению NULL передается в
PyObject_HEAD_INIT макроса и явно инициализировать это поле в
запуск функции инициализации модуля, прежде чем делать что-либо еще. Эта
обычно делается так:

 Foo_Type.ob_type = & PyType_Type;
 

Это должно быть сделано до создания любых экземпляров типа. PyType_Ready () проверяет, является ли ob_type NULL , и если да,
инициализирует его: в Python 2.2 он установлен на

.

3. Модель данных — документация Python 2.7.18

3.1. Объекты, значения и типы

Объекты — это абстракция данных Python. Все данные в программе Python
представлен объектами или отношениями между объектами. (В некотором смысле и в
соответствие модели фон Неймана «компьютер с хранимой программой», код также
представлены объектами.)

У каждого объекта есть идентификатор, тип и значение. идентичность объекта никогда
изменяется после создания; вы можете думать об этом как об адресе объекта в
объем памяти.Оператор « is » сравнивает идентичность двух объектов; то
id () Функция возвращает целое число, представляющее его идентификатор (в настоящее время
реализован как его адрес). Тип объекта также неизменен.
Тип объекта определяет операции, которые поддерживает объект (например, «выполняет
имеет длину? »), а также определяет возможные значения для объектов этого
тип. Функция type () возвращает тип объекта (который является объектом
сам). Значение некоторых объектов может изменяться.Объекты, ценность которых может
изменение считается изменчивым ; объекты, ценность которых неизменна после того, как они
создаются, называются неизменяемыми . (Значение неизменяемого объекта-контейнера
содержащий ссылку на изменяемый объект, может измениться, когда значение последнего
изменено; однако контейнер по-прежнему считается неизменным, потому что
набор содержащихся в нем объектов не может быть изменен. Итак, неизменность не
строго то же самое, что и неизменное значение, оно более тонкое.) An
изменчивость объекта определяется его типом; например, числа, строки
и кортежи неизменяемы, а словари и списки изменчивы.

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

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

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

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

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

Типы влияют практически на все аспекты поведения объекта. Даже важность
В некотором смысле затрагивается идентичность объекта: для неизменяемых типов операции,
вычисление новых значений может фактически вернуть ссылку на любой существующий объект с
один и тот же тип и значение, а для изменяемых объектов это не допускается. Например.,
после a = 1; b = 1 , a и b могут или не могут относиться к одному и тому же объекту
со значением один, в зависимости от реализации, но после c = []; d =
[]
, c и d гарантированно относятся к двум разным, уникальным, новым
создал пустые списки.(Обратите внимание, что c = d = [] назначает один и тот же объект обоим
c и d .)

3.2. Стандартная иерархия типов

Ниже приведен список типов, встроенных в Python. Модули расширения
(написано на C, Java или других языках, в зависимости от реализации) может
определить дополнительные типы. В будущих версиях Python могут быть добавлены типы к типу
иерархия (например, рациональные числа, эффективно хранимые массивы целых чисел и т. д.).

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

Нет

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

Не реализовано

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

Ellipsis

Этот тип имеет одно значение.Это единственный объект с этим значением. Эта
Доступ к объекту осуществляется через встроенное имя Ellipsis . Он используется для
указывают на наличие синтаксиса ... в срезе. Его истинное значение
правда.

чисел.Номер

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

Python различает целые числа, числа с плавающей запятой и комплексные числа.
номера:

числа. Интегральное

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

Есть три типа целых чисел:

Простые целые числа

Представляют числа в диапазоне от -2147483648 до 2147483647.
(Диапазон может быть больше на машинах с большим естественным размером слова,
но не меньше.) Когда результат операции выпадет наружу
в этом диапазоне результат обычно возвращается как длинное целое число (в некоторых
случаев, вместо этого возникает исключение OverflowError ). Для
Для операций сдвига и маски предполагается, что целые числа имеют
двоичная, двоичная запись с дополнением, использующая 32 или более бит и не скрывающая
бит от пользователя (то есть все 4294967296 различных битовых шаблонов
соответствуют разным значениям).

Длинные целые числа

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

Логические значения

Они представляют истинные значения False и True. Два объекта
представляющие значения False и True — единственные логические объекты.
Тип Boolean — это подтип простых целых чисел и логических значений.
ведут себя как значения 0 и 1 соответственно почти во всех контекстах,
за исключением того, что при преобразовании в строку строки
«Ложь» или «Истина» возвращаются соответственно.

Правила для целочисленного представления предназначены для
осмысленная интерпретация

.

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

Ваш адрес email не будет опубликован.