Атрибуты класса 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 есть два похожих понятия, которые на самом деле отличаются:
- Атрибуты
- Переменные класса
Стоит разобрать на практике:
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 и вообще?
Допустим, у вас есть класс Person
class Person:
name = "Samuel"
age = 50
country = "India"
def method1(self):
print("Method 1")
print(dir(Person))
Вывод приведенной выше программы выглядит следующим образом:
['__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__',
'age', 'country', 'method1', 'name']
Что вы можете видеть из приведенного выше вывода, так это то, что он возвращает отсортированный список допустимых атрибутов для этого объекта. Если вы посмотрите на классного человека :
имя, возраст, страна
-это атрибуты, а метод1 -это метод этого класса. Следовательно, когда вы используете dir(Person), он будет отображать все атрибуты этого класса.
Механизм dir() по умолчанию ведет себя по-разному с различными типами объектов, поскольку он пытается получить наиболее релевантную, а не полную информацию:
- Если объект является объектом модуля, список содержит имена атрибутов модуля.
- Если объект является объектом типа или класса, список содержит имена его атрибутов и рекурсивно атрибуты его баз.
- В противном случае список содержит имена атрибутов объекта, имена атрибутов его класса и рекурсивно атрибуты базовых классов его класса.
Например,
a = [1,2,3,45]
print(dir(a))
Это приведет к печати:
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__',
'__imul__', '__init__', '__init_subclass__', '__iter__', '__le__',
'__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__reversed__', '__rmul__',
'__setattr__', '__setitem__', '__sizeof__', '__str__',
'__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend',
'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Здесь вы можете увидеть несколько других имен атрибутов insert, pop, remove и т. Д. Если вы сверитесь с предыдущим списком, их не было. Это потому, что разные типы объектов имеют разные атрибуты , и с помощью этих атрибутов вы можете использовать эти объекты в разных формах.
Вы можете использовать a.pop(2)
для удаления элемента и т. Д.
лен , я полагаю, относится к len()
Как правило, len()
-это открытый интерфейс, который вы используете для получения длины объекта. Метод __len__
-это реализация, которую должен реализовать объект, поддерживающий концепцию длины. len()
звонков __len__()
Я думаю, что вижу некоторые функции-члены в возвращаемом списке атрибутов, len , как я предполагаю, относится к len(), но каковы все эти другие перечисленные вещи?
Перейдите по следующей ссылке, https://docs.python.org/3.6/ ссылка/модель данных.html#объект. реж . Большинство из них перечислены здесь. Там также описаны обычаи.
Класс и объект в Python
1- Объектно-ориентированный Python
Python — это procedural-oriented (процедурно-ориентированный) язык программирования, а также язык object oriented (объектно-ориентированного) программирования.
Процедурно-ориентированный (Procedural-oriented)
Процедурная ориентированность выражается в использовании функций в Python. Вы можете определить функции, и эти функции могут быть использованы в других модулях в программе Python.
Объектно-ориентированный (Object Oriented)
«Объектно-ориентированный» в Python выражается в использовании класса, вы можете определить класс, класс является прототипом (prototype) для создания объектов (object/instance).
2- Создать класс в Python
Синтаксис для создания класса:
** class syntax **
class ClassName:
'Brief description of the class (Optional)'
# Code ...
- Для определения класса используется ключевое слово class, за которым следует имя класса и двоеточие (:). Первая строка в теле класса представляет собой строку (string), которая кратко описывает этот класс. (Необязательно), вы можете получить доступ к этой строке через ClassName .__ doc__
- В теле класса вы можете объявлять атрибуты (attributes), методы (methods) и конструкторы (constructors).
Атрибуты (Attribute):
Атрибут является членом класса. Например, прямоугольник имеет два атрибута, включая width (ширину) и height (высоту).
Методы (Method):
- Метод класса похож на обычную функцию, но это функция класса, чтобы использовать его, вам нужно вызвать объект.
- Первым параметром метода всегда является self (ключевое слово, относящееся к самому классу).
Конструктор (Сonstructor):
- Constructor — это специальный метод класса, который всегда называется __init__.
- Первым параметром конструктора всегда является self (ключевое слово ссылается на сам класс).
- Constructor используется для создания объекта.
- Constructor присваивает значения параметра свойствам объекта, который будет создан.
- Вы можете определить максимум один коструктор (constructor) в классе.
- Если класс е определен конструктором (constructor), то Python по умолчанию считает его унаследованным от коструктора (constructor) родительского класса.
rectangle.py
# Класс симулирующий прямоугольник.
class Rectangle :
'This is Rectangle class'
# Метод, используемый для создания объект (Contructor).
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:
testRectangle.py
from rectangle import Rectangle
# Создать 2 объекта: r1 & r2
r1 = Rectangle(10,5)
r2 = Rectangle(20,11)
print ("r1.width = ", r1.width)
print ("r1.height = ", r1.height)
print ("r1.getWidth() = ", r1.getWidth())
print ("r1.getArea() = ", r1.getArea())
print ("-----------------")
print ("r2.width = ", r2.width)
print ("r2.height = ", r2.height)
print ("r2.getWidth() = ", r2.getWidth())
print ("r2.getArea() = ", r2.getArea())
Что произойдёт когда вы создаёте объект из класса?
Когда вы создаете объект класса Rectangle, конструктор (constructor) этого класса будет вызван для создания объекта, а атрибутам объекта будет присвоено значение из параметра. Это похоже на приведенную ниже иллюстрацию:
3- Параметры по умолчанию в Constructor
В отличие от других языков, класс в Python имеет только один конструктор (constructor). Тем не менее, Python позволяет параметру иметь значение по умолчанию.
Примечание: Все необходимые параметры (required parameters) должны предшествовать всем параметрам со значениями по умолчанию.
person.py
class Person :
# Параметр age и gender имеют значения по умолчанию.
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)
Например:
testPerson.py
from person import Person
# Создать объект Person.
aimee = Person("Aimee", 21, "Female")
aimee.showInfo()
print (" --------------- ")
# age, gender по умолчанию.
alice = Person( "Alice" )
alice.showInfo()
print (" --------------- ")
# gender по умолчанию.
tran = Person("Tran", 37)
tran.showInfo()
4- Сравнение объектов
В Python, когда вы создаете объект через конструктор (constructor), будет создан реальный объект в памяти, он имеет определенный адрес.
Оператор, назначенный объекту AA объектом BB, не создает в памяти нового объекта, он просто указывает адрес AA к адресу BB.
== Оператор, используемый для сравнения двух объектов, указывающих на него, возвращает True, если два объекта указывают на один и тот же адрес в памяти. Оператор ! = так же используется для сравнения двух адресов двух объектов, на которые указывает, он возвращает True, если два объекта указывают на два разных адреса.
compareObject.py
from rectangle import Rectangle
r1 = Rectangle(20, 10)
r2 = Rectangle(20 , 10)
r3 = r1
# Сравнить адрес r1 и r2
test1 = r1 == r2 # --> False
# Сравнить адрес r1 и r3
test2 = r1 == r3 # --> True
print ("r1 == r2 ? ", test1)
print ("r1 == r3 ? ", test2)
print (" -------------- ")
print ("r1 != r2 ? ", r1 != r2)
print ("r1 != r3 ? ", r1 != r3)
5- Атрибут (Attribute)
В Python есть 2 схожих понятия, которые вам нужно различать:
- Атрибут (Attribute)
- Переменная класса
Чтобы упростить это, давайте проанализируем пример ниже:
player.py
class Player:
# Переменная класса.
minAge = 18
maxAge = 50
def __init__(self, name, age):
self.name = name
self.age = age
Атрибут (attribute):
Объекты, созданные из одного класса, будут расположены по разным адресам в памяти (memory), а их атрибуты «одинаковое название» также имеют разные адреса в памяти. Как в изображении ниже:
testAttributePlayer.py
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 ")
# Прикрепить значение атрибуту (attribute) age принадлежащий player1.
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.
testNewAttributePlayer.py
from player import Player
player1 = Player("Tom", 20)
player2 = Player("Jerry", 20)
# Создать атрибут с названием 'address' для player1.
player1.address = "USA"
print ("player1.name = ", player1.name)
print ("player1.age = ", player1.age)
print ("player1.address = ", player1.address)
print (" ------------------- ")
print ("player2.name = ", player2.name)
print ("player2.age = ", player2.age)
# player2 не имеет атрибута 'address' (Ошибка происходит здесь).
print ("player2.address = ", player2.address)
6- Функции с доступом в атрибут
Как правило, вы получаете доступ к атрибуту объекта через оператор «точка» (например, player1.name). Однако Python позволяет вам иметь к ним доступчерез функцию (function).
Function (функция) | Описание |
getattr(obj, name[, default]) | Возвращает значение атрибута или возвращает значение по умолчанию, если объект не имеет этого атрибута. |
hasattr(obj,name) | Проверяет, имеет ли этот объект атрибут, указанный параметром «name», или нет. |
setattr(obj,name,value) | Устанавливает значение атрибута. Если атрибут не существует, он будет создан. |
delattr(obj, name) | Удаляет атрибут. |
testAttFunctions.py
from player import Player
player1 = Player("Tom", 20)
# getattr(obj, name[, default])
print ("getattr(player1,'name') = " , getattr(player1,"name") )
print ("setattr(player1,'age', 21): ")
# setattr(obj,name,value)
setattr(player1,"age", 21)
print ("player1.age = ", player1.age)
# Проверить имеет ли player1 атрибут (attribute) address или нет?
hasAddress = hasattr(player1, "address")
print ("hasattr(player1, 'address') ? ", hasAddress)
# Создать атрибут 'address' для обхекта 'player1'.
print ("Create attribute 'address' for object 'player1'")
setattr(player1, 'address', "USA")
print ("player1.address = ", player1.address)
# Удалить атрибут 'address'.
delattr(player1, "address")
7- Готовые атрибуты класса
Классы Python являются потомками класса object. Поэтому они наследуют следующие атрибуты:
Атрибут | Описание |
__dict__ | Информация про этот класс легко описана как словарь (Dictionary) |
__doc__ | Возвращает в строчку описания класса, или в None если она не имеет значение. |
__class__ | Возвращает объект, содержащий информацию класса, этот объект имеет много полезных атрибутов, один из которых__name__. |
__module__ | Возвращает имя модуля (module) класса, или возвращает к «__main__» если этот класс был определён в работающем модуле. |
testBuildInAttributes.py
class Customer :
'This is Customer class'
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__)
8- Переменная класса
В Python понятие «Переменная класса» (Class’s Variable) эквивалентна понятию статического поля (Static Field) для других языков, таких как Java, CSharp. Доступ к переменным класса можно получить через имя класса или объект.
Совет здесь — вам стоит получить доступ к «Переменной класса» через имя класса вместо объекта. Это помогает избежать путаницы между «Переменной класса» и атрибутом.
Каждая переменная класса имеет адрес в памяти (memory). Он используется совместно со всеми объектами класса.
testVariablePlayer.py
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..")
# Прикрепить новое значение для minAge через класс.
Player.minAge = 19
print ("Player.minAge = ", Player.minAge)
print ("player1.minAge = ", player1.minAge)
print ("player2.minAge = ", player2.minAge)
9- Перечислить членов класса или объекта
Python предоставляет вам функцию dir, эта функция дает список методов, атрибутов, переменных класса или объекта.
testDirFunction.py
from player import Player
# Распечатать список атрибутов (attribute), методов и переменных класса Player.
print ( dir(Player) )
print ("\n\n")
player1 = Player("Tom", 20)
player1.address ="USA"
# Распечатать список атрибутов, методов и переменных обхекта 'player1'.
print ( dir(player1) )
Запуск примера:
[‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘maxAge’, ‘minAge’]
[‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘address’, ‘age’, ‘maxAge’, ‘minAge’, ‘name’]
10- Уничтожить объект
Атрибуты класса и переменные экземпляра класса в Python.
Переменные экземпляра класса предназначены для данных, уникальных для каждого экземпляра класса, а переменные класса (атрибуты данных класса) — для атрибутов и методов, общих для всех экземпляров класса.
Python вызывает специальный метод __init__()
, который называют конструктором класса, каждый раз при создании нового экземпляра класса.
class Dog: # атрибут данных (переменная класса), # общая для всех экземпляров класса kind = 'canine' def __init__(self, name): # переменная экземпляра класса # уникальна для каждого экземпляра self.name = name >>> d = Dog('Fido') >>> e = Dog('Buddy') # переменная `kind` будет общая для # всех экземпляров объекта `Dog` >>> d.kind # 'canine' >>> e.kind # 'canine' # переменная `name` будет уникальна # для каждого из экземпляров >>> d.name # 'Fido' >>> e.name # 'Buddy'
Как говорилось в материале «Классы в языке Python», общие данные могут иметь неожиданный эффект при использовании изменяемых объектов, таких как списки и словари. Например, список tricks
(трюки, которые может делать отдельная собака) в примере ниже не следует использовать как атрибут данных/переменную класса, потому что для всех экземпляров класса Dog
будет использоваться только один атрибут данных tricks
:
class Dog: # ошибочное использование атрибута tricks - # переменной класса tricks = [] def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') # неожиданно разделяется всеми собаками >>> d.tricks # ['roll over', 'play dead']
Правильный дизайн класса должен использовать tricks
не как атрибут данных класса, а как переменную экземпляра класса:
class Dog: def __init__(self, name): self.name = name # создает новый пустой список # трюков для каждой собаки self.tricks = [] def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks # ['roll over'] >>> e.tricks # ['play dead']
Если одно и то же имя атрибута встречается как в экземпляре класса, так и в самом классе, то поиск атрибута определяет приоритет экземпляра класса:
>>> class Warehouse: purpose = 'storage' region = 'west' >>> w1 = Warehouse() >>> print(w1.purpose, w1.region) # storage west >>> w2 = Warehouse() >>> w2.region = 'east' >>> print(w2.purpose, w2.region) # storage east
На атрибуты данных класса могут ссылаться как методы, так и обычные пользователи — «клиенты» объекта. Другими словами, классы не могут использоваться для реализации чисто абстрактных типов данных. Фактически, ничто в Python не позволяет принудительно скрывать данные — все основано на соглашении.
Клиенты должны использовать переменные класса с осторожностью — клиенты могут испортить инварианты, поддерживаемые методами, изменив их атрибуты данных. Обратите внимание, что клиенты могут добавлять свои собственные атрибуты данных к объекту экземпляра, не влияя на достоверность методов, до тех пор, пока избегаются конфликты имен — опять же, соглашение об именовании может сэкономить здесь много головной боли.
В Python нет сокращений для ссылки на атрибуты данных или другие методы изнутри методов. Это повышает удобочитаемость методов: нет возможности путать локальные переменные и переменные экземпляра при просмотре метода.
Вместо использования привычной точечной нотации для доступа к атрибутам можно использовать встроенные функции:
getattr(obj, name [, default])
— для доступа к атрибутуname
объекта классаobj
.hasattr(obj, name)
— проверить, есть ли в классеobj
атрибутname
.setattr(obj, name, value)
— задать атрибутname
со значениемvalue
. Если атрибут не существует, он будет создан.delattr(obj, name)
— удалить атрибутname
из объекта классаobj
.
Встроенные атрибуты класса.
Классы Python хранят встроенные атрибуты, к которым можно получить доступ как к любому другому атрибуту данных.
__dict__
— словарь, содержащий пространство имен класса.__doc__
— строка документации класса.None
если, документация отсутствует.__name__
— имя класса.__module__
— имя модуля, в котором определяется класс.__bases__
— кортеж, содержащий базовые классы, в порядке их появления. Кортеж будет пустым, если наследование не было.__mro__
— Порядок разрешения методов в множественном наследовании.
Пользовательские атрибуты в Python / Хабр
Вы когда нибудь задумывались о том, что происходит, когда вы ставите точку в python? Что скрывает за собой символ
str(“\u002E”)
? Какие тайны он хранит? Если без мистики, вы знаете как происходит поиск и установка значений пользовательских атрибутов в python? Хотели бы узнать? Тогда… добро пожаловать!
Чтобы время, проведённое за чтением прошло легко, приятно и с пользой, было бы неплохо знать несколько базовых понятий языка. В частности, понимание
type
и
object
будут исключительно полезны, так же как знание нескольких примеров обеих сущностей. Почитать о них можно, в том числе,
здесь
.
Немного о терминологии, которую я использую, прежде чем мы приступим к тому, ради чего собрались:
- Объект есть любая сущность в python (функция, число, строка… словом, всё).
- Класс это объект, чьим типом является type (тип можно подсмотреть в атрибуте __class__).
- Экземпляр некоторого класса A — это объект, у которого в атрибуте __class__ есть ссылка на класс A.
Ах, да, все примеры в статье написаны на
python3
! Это определённо следует учесть.
Если ничто из вышесказанного не смогло умерить ваше желание узнать, что там будет дальше, приступим!
__dict__
Атрибуты объекта можно условно разделить две группы: определённые python-ом (такие как
__class__
,
__bases__
) и определённые пользователем, о них я как раз собираюсь рассказать.
__dict__
согласно этой классификации, относится к “системным” (определённым python-ом) атрибутам. Его задача — хранить пользовательские атрибуты. Он представляет собой dictionary, в котором ключом является
имя_атрибута
, значением, соответственно,
значение_атрибута
.
Чтобы найти атрибут объекта
o
, python обыскивает:
- Сам объект (o.__dict__ и его системные атрибуты).
- Класс объекта (o.__class__.__dict__). Только __dict__ класса, не системные атрибуты.
- Классы, от которых унасаледован класс объекта (o.__class__.__bases__.__dict__).
Таким образом, с помощью
__dict__
атрибут может быть определён как для конкретного экземпляра, так и для класса (то есть для всех объектов, которые являются экземплярами данного класса).
class StuffHolder:
stuff = "class stuff"
a = StuffHolder()
b = StuffHolder()
a.stuff # "class stuff"
b.stuff # "class stuff"
b.b_stuff = "b stuff"
b.b_stuff # "b stuff"
a.b_stuff # AttributeError
В примере описан класс
StuffHolder
с одним атрибутом
stuff
, который, наследуют оба его экземпляра. Добавление объекту
b
атрибута
b_stuff
, никак не отражается на
a
.
Посмотрим на
__dict__
всех действующих лиц:
StuffHolder.__dict__ # {... 'stuff': 'class stuff' ...}
a.__dict__ # {}
b.__dict__ # {'b_stuff': 'b stuff'}
a.__class__ # <class '__main__.StuffHolder'>
b.__class__ # <class '__main__.StuffHolder'>
(У класса StuffHolder в __dict__ хранится объект класса dict_proxy с кучей разного барахла, на которое пока не нужно обращать внимание).
Ни у a ни у b в __dict__ нет атрибута stuff, не найдя его там, механизм поиска ищет его в __dict__ класса (StuffHolder), успешно находит и возвращает значение, присвоенное ему в классе. Ссылка на класс хранится в атрибуте __class__ объекта.
Поиск атрибута происходит во время выполнения, так что даже после создания экземпляров, все изменения в __dict__ класса отразятся в них:
a.new_stuff # AttributeError
b.new_stuff # AttributeError
StuffHolder.new_stuff = "new"
StuffHolder.__dict__ # {... 'stuff': 'class stuff', 'new_stuff': 'new'...}
a.new_stuff # "new"
b.new_stuff # "new"
В случае присваивания значения атрибуту экземпляра, изменяется только
__dict__
экземпляра, то есть значение в
__dict__
класса остаётся неизменным (в случае, если значением атрибута класса не является data descriptor):
StuffHolder.__dict__ # {... 'stuff': 'class stuff' ...}
c = StuffHolder()
c.__dict__ # {}
c.stuff = "more c stuff"
c.__dict__ # {'stuff': 'more c stuff'}
StuffHolder.__dict__ # {... 'stuff': 'class stuff' ...}
Если имена атрибутов в классе и экземпляре совпадают, интерпретатор при поиске значения выдаст значение экземпляра (в случае, если значением атрибута класса не является data descriptor):
StuffHolder.__dict__ # {... 'stuff': 'class stuff' ...}
d = StuffHolder()
d.stuff # "class stuff"
d.stuff = "d stuff"
d.stuff # "d stuff"
По большому счёту это всё, что можно сказать про
__dict__
. Это хранилище атрибутов, определённых пользователем. Поиск в нём производится во время выполнения и при поиске учитывается
__dict__
класса объекта и базовых классов. Также важно знать, что есть несколько способов переопределить это поведение. Одним из них является великий и могучий Дескриптор!
Дескрипторы
С простыми типами в качестве значений атрибутов пока всё ясно. Посмотрим, как ведёт себя функция в тех же условиях:
class FuncHolder:
def func(self):
pass
fh = FuncHolder()
FuncHolder.func # <function func at 0x8f806ac>
FuncHolder.__dict__ # {...'func': <function func at 0x8f806ac>...}
fh.func # <bound method FuncHolder.func of <__main__.FuncHolder object at 0x900f08c>>
WTF!? Спросите вы… возможно. Я бы спросил. Чем функция в этом случае отличается от того, что мы уже видели? Ответ прост: методом
__get__
.
FuncHolder.func.__class__.__get__ # <slot wrapper '__get__' of 'function' objects>
Этот метод переопределяет механизм получения значения атрибута
func
экземпляра
fh
, а объект, который реализует этот метод непереводимо называется
non-data descriptor
.
Из howto:
Дескриптор — это объект, доступ к которому через атрибут переопределён методами в дескриптор протоколе:
descr.__get__(self, obj, type=None) --> value (переопределяет способ получения значения атрибута) descr.__set__(self, obj, value) --> None (переопределяет способ присваивания значения атрибуту) descr.__delete__(self, obj) --> None (переопределяет способ удаления атрибута)
Дескрипторы бывают двух видов:
- Data Descriptor (дескриптор данных) — объект, который реализует метод __get__() и __set__()
- Non-data Descriptor (дескриптор не данных?) — объект, который реализует метод __get__()
Отличаются они своим поведением по отношению к записям в
__dict__
экземпляра. Если в
__dict__
есть запись с тем же именем, что у дескриптора данных, у дескриптора преимущество. Если имя записи совпадает с именем “дескриптора не данных”, приоритет записи в
__dict__
выше.
Дескрипторы данных
Рассмотрим повнимательней дескриптор данных:
class DataDesc:
def __get__(self, obj, cls):
print("Trying to access from {0} class {1}".format(obj, cls))
def __set__(self, obj, val):
print("Trying to set {0} for {1}".format(val, obj))
def __delete__(self, obj):
print("Trying to delete from {0}".format(obj))
class DataHolder:
data = DataDesc()
d = DataHolder()
DataHolder.data # Trying to access from None class <class '__main__.DataHolder'>
d.data # Trying to access from <__main__.DataHolder object at ...> class <class '__main__.DataHolder'>
d.data = 1 # Trying to set 1 for <__main__.DataHolder object at ...>
del(d.data) # Trying to delete from <__main__.DataHolder object at ...>
Стоит обратить внимание, что вызов
DataHolder.data
передаёт в метод
__get__None
вместо экземпляра класса.
Проверим утверждение о том, что у дата дескрипторов преимущество перед записями в
__dict__
экземпляра:
d.__dict__["data"] = "override!"
d.__dict__ # {'data': 'override!'}
d.data # Trying to access from <__main__.DataHolder object at ...> class <class '__main__.DataHolder'>
Так и есть, запись в
__dict__
экземпляра игнорируется, если в
__dict__
класса экземпляра (или его базового класса) существует запись с тем же именем и значением — дескриптором данных.
Ещё один важный момент. Если изменить значение атрибута с дескриптором через класс, никаких методов дескриптора вызвано не будет, значение изменится в __dict__ класса как если бы это был обычный атрибут:
DataHolder.__dict__ # {...'data': <__main__.DataDesc object at ...>...}
DataHolder.data = "kick descriptor out"
DataHolder.__dict__ # {...'data': 'kick descriptor out'...}
DataHolder.data # "kick descriptor out"
Дескрипторы не данных
Пример дескриптора не данных:
class NonDataDesc:
def __get__(self, obj, cls):
print("Trying to access from {0} class {1}".format(obj, cls))
class NonDataHolder:
non_data = NonDataDesc()
n = NonDataHolder()
NonDataHolder.non_data # Trying to access from None class <class '__main__.NonDataHolder'>
n.non_data # Trying to access from <__main__.NonDataHolder object at ...> class <class '__main__.NonDataHolder'>
n.non_data = 1
n.non_data # 1
n.__dict__ # {'non_data': 1}
Его поведение слегка отличается от того, что вытворял дата-дескриптор. При попытке присвоить значение атрибуту
non_data
, оно записалось в
__dict__
экземпляра, скрыв таким образом дескриптор, который хранится в
__dict__
класса.
Примеры использования
Дескрипторы это мощный инструмент, позволяющий контролировать доступ к атрибутам экземпляра класса. Один из примеров их использования — функции, при вызове через экземпляр они становятся методами (см. пример выше). Также распространённый способ применения дескрипторов — создание
свойства
(
property
). Под свойством я подразумеваю некое значение, характеризующее состояние объекта, доступ к которому управляется с помощью специальных методов (геттеров, сеттеров). Создать свойство просто с помощью дескриптора:
class Descriptor:
def __get__(self, obj, type):
print("getter used")
def __set__(self, obj, val):
print("setter used")
def __delete__(self, obj):
print("deleter used")
class MyClass:
prop = Descriptor()
Или можно воспользоваться встроенным классом
property
, он представляет собой дескриптор данных. Код, представленный выше можно переписать следующим образом:
class MyClass:
def _getter(self):
print("getter used")
def _setter(self, val):
print("setter used")
def _deleter(self):
print("deleter used")
prop = property(_getter, _setter, _deleter, "doc string")
В обоих случаях мы получим одинаковое поведение:
m = MyClass()
m.prop # getter used
m.prop = 1 # setter used
del(m.prop) # deleter used
Важно знать, что
property
всегда является дескриптором данных. Если в его конструктор не передать какую либо из функций (геттер, сеттер или делитер), при попытке выполнить над атрибутом соответствующее действие — выкинется
AttributeError
.
class MySecondClass:
prop = property()
m2 = MySecondClass()
m2.prop # AttributeError: unreadable attribute
m2.prop = 1 # AttributeError: can't set attribute
del(m2) # AttributeError: can't delete attribute
К встроенным дескрипторам также относятся:
- staticmethod — то же, что функция вне класса, в неё не передаётся экземпляр в качестве первого аргумента.
- classmethod — то же, что метод класса, только в качестве первого аргумента передаётся класс экземпляра.
class StaticAndClassMethodHolder:
def _method(*args):
print("_method called with ", args)
static = staticmethod(_method)
cls = classmethod(_method)
s = StaticAndClassMethodHolder()
s._method() # _method called with (<__main__.StaticAndClassMethodHolder object at ...>,)
s.static() # _method called with ()
s.cls() # _method called with (<class '__main__.StaticAndClassMethodHolder'>,)
__getattr__(), __setattr__(), __delattr__() и __getattribute__()
Если нужно определить поведение какого-либо объекта
как атрибута
, следует использовать дескрипторы (например
property
). Тоже справедливо для семейства объектов (например
функций
). Ещё один способ повлиять на доступ к атрибутам: методы
__getattr__()
,
__setattr__()
,
__delattr__()
и
__getattribute__()
. В отличие от дескрипторов их следует определять для объекта,
содержащего атрибуты
и вызываются они при доступе
к любому
атрибуту этого объекта.
__getattr__(self, name) будет вызван в случае, если запрашиваемый атрибут не найден обычным механизмом (в __dict__ экземпляра, класса и т.д.):
class SmartyPants:
def __getattr__(self, attr):
print("Yep, I know", attr)
tellme = "It's a secret"
smarty = SmartyPants()
smarty.name = "Smartinius Smart"
smarty.quicksort # Yep, I know quicksort
smarty.python # Yep, I know python
smarty.tellme # "It's a secret"
smarty.name # "Smartinius Smart"
__getattribute__(self, name)
будет вызван при попытке получить значение атрибута. Если этот метод переопределён, стандартный механизм поиска значения атрибута не будет задействован. Следует иметь ввиду, что вызов специальных методов (например
__len__()
,
__str__()
) через встроенные функции или неявный вызов через синтаксис языка осуществляется в обход
__getattribute__()
.
class Optimist:
attr = "class attribute"
def __getattribute__(self, name):
print("{0} is great!".format(name))
def __len__(self):
print("__len__ is special")
return 0
o = Optimist()
o.instance_attr = "instance"
o.attr # attr is great!
o.dark_beer # dark_beer is great!
o.instance_attr # instance_attr is great!
o.__len__ # __len__ is great!
len(o) # __len__ is special\n 0
__setattr__(self, name, value)
будет вызван при попытке установить значение атрибута экземпляра. Аналогично
__getattribute__()
, если этот метод переопределён, стандартный механизм установки значения не будет задействован:
class NoSetters:
attr = "class attribute"
def __setattr__(self, name, val):
print("not setting {0}={1}".format(name,val))
no_setters = NoSetters()
no_setters.a = 1 # not setting a=1
no_setters.attr = 1 # not setting attr=1
no_setters.__dict__ # {}
no_setters.attr # "class attribute"
no_setters.a # AttributeError
__delattr__(self, name)
— аналогичен
__setattr__()
, но используется при удалении атрибута.
При переопределении __getattribute__(), __setattr__() и __delattr__() следует иметь ввиду, что стандартный способ получения доступа к атрибутам можно вызвать через object:
class GentleGuy:
def __getattribute__(self, name):
if name.endswith("_please"):
return object.__getattribute__(self, name.replace("_please", ""))
raise AttributeError("And the magic word!?")
gentle = GentleGuy()
gentle.coffee = "some coffee"
gentle.coffee # AttributeError
gentle.coffee_please # "some coffee"
Соль
Итак, чтобы получить значение атрибута
attrname
экземпляра
a
в python:
- Если определён метод a.__class__.__getattribute__(), то вызывается он и возвращается полученное значение.
- Если attrname это специальный (определённый python-ом) атрибут, такой как __class__ или __doc__, возвращается его значение.
- Проверяется a.__class__.__dict__ на наличие записи с attrname. Если она существует и значением является дескриптор данных, возвращается результат вызова метода __get__() дескриптора. Также проверяются все базовые классы.
- Если в a.__dict__ существует запись с именем attrname, возвращается значение этой записи. Если a — это класс, то атрибут ищется и среди его базовых классов и, если там или в __dict__ a дескриптор данных — возвращается результат __get__() дескриптора.
- Проверяется a.__class__.__dict__, если в нём существует запись с attrname и это “дескриптор не данных”, возвращается результат __get__() дескриптора, если запись существует и там не дескриптор, возвращается значение записи. Также обыскиваются базовые классы.
- Если существует метод a.__class__.__getattr__(), он вызывается и возвращается его результат. Если такого метода нет — выкидывается AttributeError.
Чтобы установить значение
value
атрибута
attrname
экземпляра
a
:
- Если существует метод a.__class__.__setattr__(), он вызывается.
- Проверяется a.__class__.__dict__, если в нём есть запись с attrname и это дескриптор данных — вызывается метод __set__() дескриптора. Также проверяются базовые классы.
- В a.__dict__ добавляется запись value с ключом attrname.
__slots__
Как пишет
Guido
в своей
истории python
о том, как изобретались new-style classes:
… Я боялся что изменения в системе классов плохо повлияют на производительность. В частности, чтобы дескрипторы данных работали корректно, все манипуляции атрибутами объекта начинались с проверки __dict__ класса на то, что этот атрибут является дескриптором данных…
На случай, если пользователи разочаруются ухудшением производительности, заботливые разработчики python придумали
__slots__
.
Наличие
__slots__
ограничивает возможные имена атрибутов объекта теми, которые там указаны. Также, так как все имена атрибутов теперь заранее известны, снимает необходимость создавать
__dict__
экземпляра.
class Slotter:
__slots__ = ["a", "b"]
s = Slotter()
s.__dict__ # AttributeError
s.c = 1 # AttributeError
s.a = 1
s.a # 1
s.b = 1
s.b # 1
dir(s) # [ ... 'a', 'b' ... ]
Оказалось, что опасения Guido не оправдались, но к тому времени, как это стало ясно, было уже слишком поздно. К тому же, использование
__slots__
действительно может увеличить производительность, особенно уменьшив количество используемой памяти при создании множества небольших объектов.
Заключение
Доступ к атрибутом в python можно контролировать огромным количеством способов. Каждый из них решает свою задачу, а вместе они подходят практически под любой мыслимый сценарий использования объекта. Эти механизмы — основа гибкости языка, наряду с множественным наследованием, метаклассами и прочими вкусностями. У меня ушло некоторое время на то, чтобы разобраться, понять и, главное, принять это множество вариантов работы атрибутов. На первый взгляд оно показалось слегка избыточным и не особенно логичным, но, учитывая, что в ежедневном программировании это редко пригодиться, приятно иметь в своём арсенале такие мощные инструменты.
Надеюсь, и вам эта статья прояснила парочку моментов, до которых руки не доходили разобраться. И теперь, с огнём в глазах и уверенностью в Точке, вы напишите огромное количество наичистейшего, читаемого и устойчивого к изменениям требований кода! Ну или комментарий.
Спасибо за ваше время.
Ссылки
- Shalabh Chaturvedi. Python Attributes and Methods
- Guido Van Rossum. The Inside Story on New-Style Classes
- Python documentation
UPD: Полезный линк от пользователя
leron
:
Python Data Model
Доступ к атрибутам класса в языке Python
Атрибуты созданного экземпляра класса можно добавлять, изменять или удалять в любое время, используя для доступа к ним точечную запись. Если построить инструкцию, в которой присвоить значение атрибуту, то можно изменить значение, содержащееся внутри существующего атрибута, либо создать новый с указанным именем и содержащий присвоенное значение:
имя-экземпляра.имя-атрибута = значение
del имя-экземпляра.имя-атрибута
Альтернативным способом добавления, изменения либо удаления переменной экземпляра является использование встроенных функций Python:
getattr(имя-экземпляра, 'имя-атрибута') - возвращает значение атрибута экземпляра класса;
hasattr(имя-экземпляра, 'имя-атрибута') - возвращает True, если значение атрибута существует в экземпляре, в противном случае возвращает False;
setattr(имя-экземпляра, 'имя-атрибута', значение) - модифицирует существующее значение атрибута либо создает новый атрибут для экземпляра;
delattr(имя-экземпляра, 'имя-атрибута') - удаляет атрибут из экземпляра.
Имена атрибутов, автоматически предоставляемые интерпретатором Python, всегда содержат символ подчеркивания, чтобы обозначить «частный» характер этих атрибутов — их не следует изменять либо удалять. Таким же образом вы можете добавить свои собственные атрибуты, обозначив их как «частные», но не забывайте, что они, как и все другие, могут быть изменены.
Допустим, у нас имеется следующий класс Bird:
class Bird:
count = 0
def __init__(self, say):
self.say = say
Bird.count += 1
def talk(self):
return self.say
1. Начните новую программу на Python, сделав доступными функции класса Bird, созданного в предыдущем примере.
from Bird import *
2. Затем создайте экземпляр класса, после чего добавьте, используя точечную запись, новый атрибут с присвоенным значением.
chick = Bird("Cheep, cheep!")
chick.age = "1 month"
3. Теперь выведите значения, хранящиеся в обоих атрибутах экземпляра.
print( '\nChick Says:' , chick.talk() )
print( 'Chick age:' , chick.age )
4. Измените новый атрибут с помощью точечной записи и выведите его новое значение.
chick.age = "2 month"
print( 'Chick Now:' , chick.age )
5. Опять измените новый атрибут, используя в этот раз встроенную функцию.
setattr(chick, 'age', '3 month')
6. После этого выведите список всех атрибутов экземпляра, не являющихся частными, и соответствующие им значения, используя встроенную функцию.
print( '\nChick Attributes...' )
for atttribute in dir( chick ):
if atttribute[0] != '_':
print( atttribute, ':', getattr( chick , atttribute ) )
Данный цикл пропускает все атрибуты, чьи имена начинаются с символа подчеркивания, так что «частные» атрибуты как __name__, __doc__ и т.п. не попадут в результирующий список.
7. Наконец удалите новый атрибут и проверьте его отсутствие, используя встроенные функции.
delattr(chick, 'age')
print( '\nChick age Attribute?' , hasattr( chick , 'age' ) )
Весь наш код выглядит следующим образом:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Bird:
count = 0
def __init__(self, say):
self.say = say
Bird.count += 1
def talk(self):
return self.say
chick = Bird('Cheep, cheep!')
chick.age = '1 month'
print('\nChick Says:', chick.talk())
print('Chick Age:', chick.age)
chick.age = '2 month'
print('Chick Now:', chick.age)
setattr(chick, 'age', '3 month')
print('\nChick Attributes...')
for attribute in dir(chick):
if attribute[0] != '_':
print(attribute, ':', getattr(chick, attribute))
delattr(chick, 'age')
print('\nChick age Attribute?', hasattr(chick, 'age'))
8. Сохраните файл в вашем рабочем каталоге, затем откройте командную строку и запустите программу — вы увидите результат обращения к атрибутам экземпляра.
Интересное видео по теме для читателей:
Атрибуты класса Python: примеры переменных
Недавно у меня было интервью по программированию, экран телефона, в котором мы использовали совместный текстовый редактор.
Меня попросили реализовать определенный API, и я решил сделать это на Python. Абстрагируясь от постановки задачи, скажем, мне нужен класс, экземпляры которого хранят около данных
и около other_data
.
Я глубоко вздохнул и начал печатать. Через несколько строк у меня было что-то вроде этого:
класс Услуга (объект):
данные = []
def __init __ (self, other_data):
себя.other_data = другие_данные
...
Меня остановил интервьюер:
- Опрашивающий: «Эта строка:
data = []
. Я не думаю, что это правильный Python? » - Я: «Я почти уверен, что это так. Он просто устанавливает значение по умолчанию для атрибута экземпляра ».
- Опрашивающий: «Когда выполняется этот код?»
- Я: «Я не совсем уверен. Я просто исправлю это, чтобы не запутаться «.
Для справки и чтобы дать вам представление о том, что я собирался, вот как я изменил код:
класс Услуга (объект):
def __init __ (self, other_data):
себя.данные = []
self.other_data = other_data
...
Как оказалось, мы оба ошибались. Настоящий ответ заключался в понимании различия между атрибутами класса Python и атрибутами экземпляра Python.
Примечание. Если у вас есть экспертный опыт работы с атрибутами класса, вы можете сразу перейти к вариантам использования.
Атрибуты класса Python
Мой интервьюер ошибался в том, что приведенный выше код является синтаксически верным .
Я тоже ошибался в том, что он не устанавливает «значение по умолчанию» для атрибута экземпляра. Вместо этого он определяет данных
как атрибут класса со значением []
.
По моему опыту, атрибуты классов Python — это тема, о которой многие люди знают кое-что, о чем , но мало кто понимает полностью.
Переменная класса
Python и переменная экземпляра: в чем разница?
Атрибут класса Python — это атрибут класса (круговой, я знаю), а не атрибут экземпляра класса.
Давайте воспользуемся примером класса Python, чтобы проиллюстрировать разницу. Здесь class_var
— это атрибут класса, а i_var
— это атрибут экземпляра:
класс MyClass (объект):
class_var = 1
def __init __ (self, i_var):
self.i_var = i_var
Обратите внимание, что все экземпляры класса имеют доступ к class_var
, и что к нему также можно получить доступ как к свойству самого класса :
foo = MyClass (2)
bar = MyClass (3)
фу.class_var, foo.i_var
## 1, 2
bar.class_var, bar.i_var
## 1, 3
MyClass.class_var ## <- Это ключ
## 1
Для программистов на Java или C ++ атрибут класса подобен статическому члену, но не идентичен ему. Чем они отличаются, мы увидим позже.
Класс и пространство имен экземпляра
Чтобы понять, что здесь происходит, давайте кратко поговорим о пространствах имен Python .
Пространство имен - это отображение имен на объекты с тем свойством, что между именами в разных пространствах имен отсутствует связь.Обычно они реализуются в виде словарей Python, хотя это делается абстрагированно.
В зависимости от контекста вам может потребоваться доступ к пространству имен с использованием точечного синтаксиса (например, object.name_from_objects_namespace
) или в качестве локальной переменной (например, object_from_namespace
). В качестве конкретного примера:
класс MyClass (объект):
## Нет необходимости в точечном синтаксисе
class_var = 1
def __init __ (self, i_var):
self.i_var = i_var
## Нужен точечный синтаксис, поскольку мы покинули область пространства имен классов
Мои занятия.class_var
## 1
Классы Python и экземпляры классов имеют свои собственные отдельные пространства имен, представленные предопределенными атрибутами MyClass .__ dict__
и instance_of_MyClass .__ dict__
соответственно.
Когда вы пытаетесь получить доступ к атрибуту из экземпляра класса, он сначала смотрит на его пространство имен instance . Если он находит атрибут, он возвращает связанное значение. Если нет, это , а затем просматривает пространство имен class и возвращает атрибут (если он присутствует, в противном случае выдается ошибка).Например:
foo = MyClass (2)
## Находит i_var в пространстве имен экземпляра foo
foo.i_var
## 2
## Не находит class_var в пространстве имен экземпляра…
## Итак, посмотрите в пространство имен класса (MyClass .__ dict__)
foo.class_var
## 1
Пространство имен экземпляра берет верх над пространством имен класса: если в обоих есть атрибут с тем же именем, сначала проверяется пространство имен экземпляра и возвращается его значение. Вот упрощенная версия кода (источника) для поиска атрибута:
def instlookup (inst, имя):
## упрощенный алгоритм...
если inst .__ dict __. has_key (name):
return inst .__ dict __ [имя]
еще:
вернуть inst .__ class __.__ dict __ [имя]
А, в наглядной форме:
Как атрибуты класса обрабатывают присвоение
Имея это в виду, мы можем понять, как атрибуты класса Python обрабатывают присвоение:
Если атрибут класса установлен путем доступа к классу, он переопределит значение для всех экземпляров . Например:
foo = MyClass (2) фу.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2
На уровне пространства имен… мы устанавливаем
MyClass .__ dict __ ['class_var'] = 2
. (Примечание: это не точный код (это будетsetattr (MyClass, 'class_var', 2)
), поскольку__dict__
возвращает dictproxy, неизменяемую оболочку, которая предотвращает прямое назначение, но помогает для демонстрации) . Затем, когда мы обращаемся кfoo.class_var
,class_var
имеет новое значение в пространстве имен классов, и поэтому возвращается 2.Если переменная класса Paython установлена путем доступа к экземпляру, она переопределит значение только для этого экземпляра . Это по существу переопределяет переменную класса и превращает ее в переменную экземпляра, доступную интуитивно только для этого экземпляра . Например:
foo = MyClass (2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 MyClass.class_var ## 1
На уровне пространства имен… мы добавляем атрибут
class_var
вfoo.__dict__
, поэтому, когда мы ищемfoo.class_var
, мы возвращаем 2. Между тем, другие экземплярыMyClass
будут иметь , а не , будут иметьclass_var
в своих пространствах имен экземпляров, поэтому они продолжают находитьclass_var
вMyClass. __dict__
и таким образом вернуть 1.
Изменчивость
Контрольный вопрос: что, если атрибут вашего класса имеет изменяемый тип ? Вы можете манипулировать (искажать?) Атрибутом класса, обращаясь к нему через конкретный экземпляр, и, в свою очередь, в конечном итоге манипулирует указанным объектом, который все экземпляры обращаются к (как указал Тимоти Уайзман).
Лучше всего это продемонстрировать на примере. Давайте вернемся к службе Service
, которую я определил ранее, и посмотрим, как мое использование переменной класса могло привести к проблемам в будущем.
класс Услуги (объект):
данные = []
def __init __ (self, other_data):
self.other_data = other_data
...
Моя цель состояла в том, чтобы иметь пустой список ( []
) в качестве значения по умолчанию для данных
, а для каждого экземпляра службы
иметь свои собственные данные , которые со временем будут изменены на экземпляре - -экземплярная основа.Но в этом случае мы получаем следующее поведение (напомним, что Service
принимает некоторый аргумент other_data
, который в данном примере произвольный):
s1 = Сервис (['a', 'b'])
s2 = Сервис (['c', 'd'])
s1.data.append (1)
s1.data
## [1]
s2.data
## [1]
s2.data.append (2)
s1.data
## [1, 2]
s2.data
## [1, 2]
Это бесполезно - изменение переменной класса через один экземпляр изменяет ее для всех остальных!
На уровне пространства имен… все экземпляры Service
обращаются к одному и тому же списку в Service и изменяют его.__dict__
без создания собственных атрибутов data
в пространствах имен экземпляров.
Мы могли бы обойти это с помощью присваивания; то есть, вместо того, чтобы использовать изменчивость списка, мы могли бы назначить нашим объектам Service
свои собственные списки, как показано ниже:
s1 = Сервис (['a', 'b'])
s2 = Сервис (['c', 'd'])
s1.data = [1]
s2.data = [2]
s1.data
## [1]
s2.data
## [2]
В этом случае мы добавляем s1 .__ dict __ ['data'] = [1]
, то есть исходную службу .__dict __ ['data']
остается без изменений.
К сожалению, для этого необходимо, чтобы пользователи Service
были хорошо осведомлены о его переменных и, безусловно, подвержены ошибкам. В каком-то смысле мы будем устранять симптомы, а не причину. Мы бы предпочли что-то правильное по конструкции.
Мое личное решение: если вы просто используете переменную класса для присвоения значения по умолчанию потенциальной переменной экземпляра Python, не используйте изменяемые значения .В этом случае каждый экземпляр Service
собирался в конечном итоге переопределить Service.data
собственным атрибутом экземпляра, поэтому использование пустого списка по умолчанию привело к крошечной ошибке, которую легко не заметить. Вместо приведенного выше мы могли бы использовать:
- Полностью привязан к атрибутам экземпляра, как показано во введении.
Избегайте использования пустого списка (изменяемое значение) по умолчанию:
класс Услуга (объект): data = None def __init __ (self, other_data): себя.other_data = другие_данные ...
Конечно, мы должны будем обработать случай
None
надлежащим образом, но это небольшая цена.
Итак, когда следует использовать атрибуты класса Python?
Атрибуты класса
сложны, но давайте рассмотрим несколько случаев, когда они могут пригодиться:
Сохранение констант . Поскольку к атрибутам класса можно получить доступ как к атрибутам самого класса, часто полезно использовать их для хранения общеклассовых констант, специфичных для класса.Например:
класс Круг (объект): пи = 3,14159 def __init __ (self, radius): self.radius = радиус область определения (self): вернуть Circle.pi * self.radius * self.radius Circle.pi ## 3.14159 c = Круг (10) c.pi ## 3.14159 c.area () ## 314.159
Определение значений по умолчанию . В качестве тривиального примера мы могли бы создать ограниченный список (то есть список, который может содержать только определенное количество элементов или меньше) и выбрать ограничение по умолчанию в 10 элементов:
класс MyClass (объект): limit = 10 def __init __ (сам): себя.данные = [] def item (self, i): вернуть self.data [i] def add (self, e): если len (self.data)> = self.limit: поднять исключение ("Слишком много элементов") self.data.append (e) MyClass.limit ## 10
Затем мы могли бы создать экземпляры со своими собственными ограничениями, присвоив экземпляру
limit атрибут
.foo = MyClass () foo.limit = 50 ## foo теперь может содержать 50 элементов - другие экземпляры могут содержать 10
Это имеет смысл только в том случае, если вы хотите, чтобы ваш типичный экземпляр
MyClass
содержал всего 10 элементов или меньше - если вы задаете всем своим экземплярам разные ограничения, тогдаlimit
должен быть переменной экземпляра.(Однако помните: будьте осторожны при использовании изменяемых значений по умолчанию.)Отслеживание всех данных во всех экземплярах данного класса . Это своего рода специфический вопрос, но я мог видеть сценарий, в котором вы могли бы захотеть получить доступ к части данных, относящейся к каждому существующему экземпляру данного класса.
Чтобы сделать сценарий более конкретным, допустим, у нас есть класс
Person
, и у каждого человека естьимя
. Мы хотим отслеживать все использованные имена.Одним из подходов может быть итерация по списку объектов сборщика мусора, но проще использовать переменные класса.Обратите внимание, что в этом случае
имен
будут доступны только как переменная класса, поэтому изменяемое значение по умолчанию приемлемо.класс Человек (объект): all_names = [] def __init __ (я, имя): self.name = имя Person.all_names.append (имя) Джо = Человек ('Джо') bob = Человек ('Боб') распечатать Person.all_names ## ['Джо', 'Боб']
Мы могли бы даже использовать этот шаблон проектирования для отслеживания всех существующих экземпляров данного класса, а не только некоторых связанных данных.
класс Человек (объект): all_people = [] def __init __ (я, имя): self.name = имя Person.all_people.append (сам) Джо = Человек ('Джо') bob = Человек ('Боб') печать Person.all_people ## [<__ main __. Объект Person в 0x10e428c50>, <__ main __. Объект Person в 0x10e428c90>]
Исполнение (вроде… см. Ниже).
Под капотом
Примечание: Если вы беспокоитесь о производительности на этом уровне, возможно, вы в первую очередь не захотите использовать Python, так как различия будут порядка десятых долей миллисекунды, но все равно весело потыкать немного вокруг и помогает для иллюстрации.
Напомним, что пространство имен класса создается и заполняется во время определения класса. Это означает, что мы выполняем только одно присвоение - из - для данной переменной класса, в то время как переменные экземпляра должны назначаться каждый раз при создании нового экземпляра. Возьмем пример.
def call_class ():
распечатать "Распределение по классу"
возврат 2
класс Bar (объект):
y = вызываемый_класс ()
def __init __ (self, x):
self.x = x
## "Назначение класса"
def named_instance ():
print "Назначение экземпляра"
возврат 2
класс Foo (объект):
def __init __ (self, x):
себя.y = вызываемый_экземпляр ()
self.x = x
Бар (1)
Бар (2)
Фу (1)
## "Назначение экземпляра"
Фу (2)
## "Назначение экземпляра"
Мы назначаем Bar.y
только один раз, а instance_of_Foo.y
при каждом вызове __init__
.
В качестве дополнительного доказательства воспользуемся дизассемблером Python:
импорт
класс Bar (объект):
у = 2
def __init __ (self, x):
self.x = x
класс Foo (объект):
def __init __ (self, x):
себя.у = 2
self.x = x
dis.dis (Бар)
## Дизассемблирование __init__:
## 7 0 LOAD_FAST 1 (x)
## 3 LOAD_FAST 0 (самостоятельно)
## 6 STORE_ATTR 0 (x)
## 9 LOAD_CONST 0 (Нет)
## 12 RETURN_VALUE
dis.dis (Foo)
## Дизассемблирование __init__:
## 11 0 LOAD_CONST 1 (2)
## 3 LOAD_FAST 0 (самостоятельно)
## 6 STORE_ATTR 0 (г)
## 12 9 LOAD_FAST 1 (x)
## 12 LOAD_FAST 0 (самостоятельно)
## 15 STORE_ATTR 1 (x)
## 18 LOAD_CONST 0 (Нет)
## 21 RETURN_VALUE
Когда мы смотрим на байт-код, снова становится очевидным, что Foo.__init__
должен выполнять два назначения, а Bar .__ init__
- только одно.
Как на самом деле выглядит это усиление? Я буду первым, кто признает, что временные тесты сильно зависят от часто неконтролируемых факторов, и различия между ними часто трудно объяснить точно.
Однако я думаю, что эти небольшие фрагменты (выполняемые с модулем Python timeit) помогают проиллюстрировать различия между переменными класса и экземпляра, поэтому я все равно включил их.
Примечание. Я использую MacBook Pro с OS X 10.8.5 и Python 2.7.2.
Инициализация
10000000 звонков в `Bar (2)`: 4.940 с
10000000 звонков в `Foo (2)`: 6.043 с
Инициализация Bar
выполняется быстрее более чем на секунду, поэтому разница здесь оказывается статистически значимой.
Так почему это так? Одно предположительное объяснение : мы выполняем два присваивания в Foo .__ init__
, но только одно в Bar.__init__
.
Назначение
10000000 звонков в `Bar (2) .y = 15`: 6,232 с
10000000 обращений к `Foo (2) .y = 15`: 6,855 с
10000000 присвоений `Bar`: 6,232 с - 4,940 с = 1,292 с
10000000 присваиваний `Foo`: 6,855 с - 6,043 с = 0,812 с
Примечание. Невозможно повторно запускать код настройки в каждой пробной версии с timeit, поэтому нам придется повторно инициализировать нашу переменную в нашей пробной версии. Вторая строка значений времени представляет собой время, указанное выше, за вычетом ранее рассчитанного времени инициализации.
Из вышесказанного, похоже, что Foo
занимает всего около 60% от длины Bar
для обработки назначений.
Почему это так? Одно предположительное объяснение : когда мы назначаем Bar (2) .y
, мы сначала смотрим в пространство имен экземпляра ( Bar (2) .__ dict __ [y]
), не можем найти y
, а затем смотрим в пространстве имен класса ( Bar .__ dict __ [y]
), а затем выполните правильное присвоение. Когда мы присваиваем Foo (2).y
, мы выполняем вдвое меньше запросов, чем сразу назначаем пространству имен экземпляра ( Foo (2) .__ dict __ [y]
).
Таким образом, хотя такой прирост производительности в действительности не имеет значения, эти тесты интересны на концептуальном уровне. Во всяком случае, я надеюсь, что эти различия помогут проиллюстрировать механические различия между переменными класса и экземпляра.
Заключение
Атрибуты класса используются в Python недостаточно; у многих программистов разные представления о том, как они работают и почему они могут быть полезны.
Мое мнение: переменные класса Python нашли свое место в школе хорошего кода. При осторожном использовании они могут упростить работу и улучшить читаемость. Но когда их небрежно бросают в класс, они обязательно сбивают вас с толку.
Приложение : Переменные частного экземпляра
Одна вещь, которую я хотел включить, но у меня не было естественной точки входа…
Python не имеет, так сказать, частных переменных, но еще одна интересная взаимосвязь между именами классов и экземпляров связана с изменением имен.
В руководстве по стилю Python сказано, что псевдоприватные переменные должны начинаться с двойного подчеркивания: ‘__’. Это не только знак для других, что ваша переменная должна обрабатываться конфиденциально, но и своего рода способ предотвратить доступ к ней. Вот что я имею в виду:
класс Бар (объект):
def __init __ (сам):
self .__ zap = 1
a = Бар ()
a .__ zap
## Traceback (последний вызов последний):
## Файл "", строка 1, в
## AttributeError: объект 'Bar' не имеет атрибута '__baz'
## Хм.Так что в пространстве имен?
а .__ dict__
{'_Bar__zap': 1}
a._Bar__zap
## 1
Посмотрите на это: атрибут экземпляра __zap
автоматически получает префикс с именем класса, что дает _Bar__zap
.
Хотя все еще можно установить и получить с помощью a._Bar__zap
, это изменение имени является средством создания «частной» переменной, поскольку оно не позволяет вам и другим получить к ней доступ случайно или по незнанию.
Edit: как любезно заметил Педро Вернек, это поведение в значительной степени предназначено для помощи при создании подклассов.В руководстве по стилю PEP 8 они видят, что это служит двум целям: (1) предотвращение доступа подклассов к определенным атрибутам и (2) предотвращение конфликтов пространств имен в этих подклассах. Несмотря на свою полезность, изменение переменных не следует рассматривать как приглашение к написанию кода с предполагаемым разделением между публичным и частным, например, как это присутствует в Java.
Атрибут класса
и атрибут экземпляра в Python
Как объектно-ориентированный язык Python предоставляет две области для атрибутов: атрибуты класса и атрибуты экземпляра.
Хотя атрибут экземпляра в Python имеет точно такие же характеристики и определение, как и в других объектно-ориентированных языках, атрибут класса всегда ошибочно считается точным эквивалентом статического атрибута в Java или C ++. Чтобы быть точным, атрибуты класса в Python и статические атрибуты в Java или C ++ имеют много общего, однако у них есть различия в поведении, которые я выделю в этой статье.
Атрибут класса и атрибут экземпляра
Начнем с основ:
Атрибут экземпляра - это переменная Python, принадлежащая одному и только одному объекту.Эта переменная доступна только в области видимости этого объекта и определяется внутри функции-конструктора,
__init __ (self, ..)
класса.Атрибут класса - это переменная Python, которая принадлежит классу, а не конкретному объекту. Он распределяется между всеми объектами этого класса и определяется вне функции конструктора,
__init __ (self, ...)
, класса.
Приведенный ниже ExampleClass
является базовым классом Python с двумя атрибутами: class_attr
и instance_attr
.
класс ExampleClass (объект):
class_attr = 0
def __init __ (self, instance_attr):
self.instance_attr = instance_attr
instance_attr
доступен только из области видимости объекта. Атрибут класса ( class_attr
) доступен как свойство класса, так и как свойство объектов, поскольку он является общим для всех из них.
, если __name__ == '__main__':
foo = ExampleClass (1)
bar = ExampleClass (2)
# выводим атрибут экземпляра объекта foo
печать (foo.istance_attr)
# 1
# распечатать атрибут экземпляра объекта var
печать (bar.instance_attr)
# 2
# распечатать атрибут класса класса ExampleClass как свойство самого класса
печать (ExampleClass.class_attr)
# 0
# распечатать атрибут класса как свойство объектов foo, bar
печать (bar.class_attr)
# 0
печать (foo.class_attr)
# 0
# пытаемся распечатать атрибут экземпляра как свойство класса
печать (ExampleClass.instance_attr)
#AttributeError: объект типа 'ExampleClass' не имеет атрибута 'instance_attr'
Обратите внимание, что к атрибуту класса можно получить доступ как к свойству класса и как к свойству экземпляра, однако доступ к атрибуту экземпляра как к свойству класса вызывает AttributeError
.
За кадром
За кулисами это игра пространств имен. Если вы уже читали «Дзен Python», последняя строка гласит: «Пространства имен - отличная идея - давайте сделаем еще больше!» Итак, что такое пространство имен?
В Python пространство имен - это отображение между объектами и именами. Для простоты предположим, что это словарь Python, который имеет ключ к имени объекта и его значение в качестве значения. Различные пространства имен могут сосуществовать со свойством, в то время как имена в них независимы.
Классы и объекты
Python имеют разные пространства имен, например, у нас есть ExampleClass .__ dict__
в качестве пространства имен для нашего класса и foo .__ dict __ (bar .__ dict__)
в качестве пространства имен для нашего объекта foo (bar)
.
, если __name__ = '__main__':
foo = ExampleClass (1)
bar = ExampleClass (2)
print str (ExampleClass .__ dict__)
# {'__ module__': '__main__', 'class_attr': 0, '__dict__': <атрибут '__dict__' объектов 'ExampleClass'>, '__weakref__': , '__doc__ ': Нет,' __init__ ': <функция __init__ в 0x031192F0>}
печать str (foo.__dict__)
# {'instance_attr': 1}
печать str (bar .__ dict__)
# {'instance_attr': 2}
Когда вы обращаетесь к атрибуту (экземпляру или атрибуту класса) как к свойству объекта, используя точечное соглашение, он сначала выполняет поиск в пространстве имен этого объекта для этого имени атрибута. Если он найден, он возвращает значение, в противном случае он ищет в пространстве имен класса. Если и там ничего не найдено, возникает AttributeError
. Пространство имен объекта находится перед пространством имен класса.
Если мы найдем в одном классе как атрибут экземпляра, так и атрибут класса с тем же именем, доступ к этому имени из вашего объекта даст вам значение в пространстве имен объекта. Ниже упрощенная версия функции поиска.
def instlookup (inst, имя):
## упрощенный алгоритм ...
если inst .__ dict __. has_key (name):
return inst .__ dict __ [имя]
еще:
return inst .__ class __.__ dict __ [имя]
Когда вы обращаетесь к атрибуту как к свойству класса, он ищет имя этого атрибута непосредственно в пространстве имен класса.Если он найден, он возвращает значение, в противном случае он вызывает AttributeError
.
Атрибуты класса изменяются на атрибуты экземпляра
Да, это может показаться странным, но они есть!
Давайте рассмотрим следующий сценарий, а затем вместе прокомментируем его.
, если __name__ = '__main__':
foo = ExampleClass (1)
bar = ExampleClass (2)
# распечатать атрибут класса как свойство foo
напечатать foo.class_attr
# 0
# изменить атрибут класса как свойство foo
фу.class_attr = 5
напечатать foo.class_attr
# 5
# распечатать атрибут класса как атрибут бара
печать bar.class_attr
# 0
#oups !!!!
class_attr
используется всеми объектами класса. Однако, когда мы изменили значение из объекта foo
, это изменение не было видно из объекта bar
, который все еще имеет старое значение, 0
, а не новое значение, 5
! Погодите ... давайте проверим наши пространства имен и попытаемся понять, что, черт возьми, происходит.
, если __name__ = '__main__':
foo = ExampleClass (1)
bar = ExampleClass (2)
печать str (foo .__ dict__)
# {'instance_attr': 1} пространство имен объекта foo имело только его атрибут экземпляра
напечатать foo.class_attr
# 0
foo.class_attr = 5
печать str (foo .__ dict__)
'' '{' instance_attr ': 1,' class_attr ': 5} после завершения обработки
в атрибуте класса из объекта foo он добавляется как атрибут экземпляра
в его пространстве имен
'' '
Эффект добавил новый атрибут экземпляра к объекту foo
и только к этому объекту, поэтому в предыдущем примере объект bar
продолжал печатать атрибут класса.
С неизменяемыми объектами это поведение всегда одинаково. Однако с изменяемыми объектами, такими как списки, например, это не всегда так, в зависимости от того, как вы изменяете свой атрибут класса.
Давайте изменим наш предыдущий класс, чтобы он имел список в качестве атрибута класса.
класс ExampleClass (объект):
class_attr = []
def __init __ (self, instance_attr):
self.instance_attr = instance_attr
Мы изменяем этот список как свойство объекта foo
, добавляя к нему новый элемент.
, если __name__ = '__main__':
foo = ExampleClass (1)
bar = ExampleClass (2)
# распечатать атрибут класса как свойство foo
напечатать foo.class_attr
# []
# изменить атрибут класса как свойство foo
foo.class_attr.append (0)
напечатать foo.class_attr
# [0]
# распечатать атрибут класса как атрибут бара
печать bar.class_attr
# [0]
Когда изменяемый атрибут класса изменяется объектом, он не изменяется, чтобы превратиться в атрибут экземпляра для этого объекта.Он остается общим для всех объектов класса с добавленными к нему новыми элементами.
Однако, если вы прикрепите новый список к этому атрибуту ( foo.class_attr = list ("foo")
), вы получите то же поведение, что и неизменяемые объекты.
, если __name__ = '__main__':
foo = ExampleClass (1)
bar = ExampleClass (2)
# распечатать атрибут класса как свойство foo
напечатать foo.class_attr
# []
# изменить атрибут класса как свойство foo
фу.class_attr = list ("пример")
напечатать foo.class_attr
#[пример]
# распечатать атрибут класса как атрибут бара
печать bar.class_attr
# []
Вы можете сравнить пространства имен самостоятельно в качестве доказательства предыдущего поведения.
Заключение
Атрибуты класса
Python могут быть полезны в разных случаях, однако их следует использовать с осторожностью, чтобы избежать неожиданного поведения.
Атрибуты класса и экземпляра в Python
Атрибуты класса
Атрибуты класса принадлежат самому классу, они будут совместно использоваться всеми экземплярами.Такие атрибуты определены в частях тела класса, как правило, вверху для удобочитаемости.
|
Выход:
1 2 2
Атрибуты экземпляра
В отличие от атрибутов класса, атрибуты экземпляра не используются объектами совместно.Каждый объект имеет свою собственную копию атрибута экземпляра (в случае атрибутов класса все объекты относятся к одной копии).
Для перечисления атрибутов экземпляра / объекта у нас есть две функции: -
1. vars () - Эта функция отображает атрибут экземпляра в форме словаря.
2. dir () - Эта функция отображает больше атрибутов, чем функция vars, поскольку она не ограничена экземпляром. Он также отображает атрибуты класса. Он также отображает атрибуты классов-предков.
|
Выход:
Словарная форма: {'зарплата': 4000, 'имя': 'xyz'} ['__doc__', '__init__', '__module__', 'name', 'salary', 'show']
Автор статьи: Harsh Valecha .Если вам нравится GeeksforGeeks, и вы хотели бы внести свой вклад, вы также можете написать статью с помощью provide.geeksforgeeks.org или отправить ее по электронной почте на [email protected]. Посмотрите, как ваша статья появляется на главной странице GeeksforGeeks, и помогите другим гикам.
Пожалуйста, напишите комментарии, если вы обнаружите что-то неправильное, или если вы хотите поделиться дополнительной информацией по теме, обсуждаемой выше.
Внимание компьютерщик! Укрепите свои основы с помощью курса Python Programming Foundation и изучите основы.
Для начала подготовьтесь к собеседованию. Расширьте свои концепции структур данных с помощью курса Python DS . И чтобы начать свое путешествие по машинному обучению, присоединитесь к курсу Машинное обучение - базовый уровень
Понимание атрибутов класса Python на практических примерах
Резюме : в этом руководстве вы узнаете об атрибутах класса Python и когда использовать их по назначению.
Введение в атрибуты класса
Начнем с простого Circle
class:
Язык кода: Python (python)
class Circle: def __init __ (self, radius): себя.пи = 3,14159 self.radius = радиус область определения (self): return self.pi * self.radius ** 2 окружность def (self): return 2 * self.pi * self.radius
Класс Circle
имеет два атрибута: pi
и radius
. В нем также есть два метода, которые вычисляют площадь и длину окружности.
И pi
, и радиус
называются атрибутами экземпляра .Другими словами, они принадлежат определенному экземпляру класса Circle
. Если вы измените атрибуты экземпляра, это не повлияет на другие экземпляры.
Помимо атрибутов экземпляра, Python также поддерживает атрибуты класса . Атрибуты класса не связаны с каким-либо конкретным экземпляром класса. Но они общие для всех экземпляров класса.
Если вы программировали на Java или C #, вы увидите, что атрибуты класса похожи на статические члены, но не совпадают.
Чтобы определить атрибут класса, вы помещаете его вне метода __init __ ()
. Например, следующее определяет pi
как атрибут класса:
Язык кода: Python (python)
class Circle: пи = 3,14159 def __init __ (self, radius): self.radius = радиус область определения (self): return self.pi * self.radius ** 2 окружность def (self): return 2 * self.pi * self.radius
После этого вы можете получить доступ к атрибуту класса через экземпляры класса или через имя класса:
Язык кода: Oracle Rules Language (ruleslanguage)
имя_объекта.class_attribute class_name.class_attribute
В методах area ()
и circle ()
мы получаем доступ к атрибуту класса pi
через переменную self
.
Вне класса Circle
вы можете получить доступ к атрибуту класса pi
через экземпляр класса Circle
или напрямую через класс Circle
. Например:
Язык кода: Python (python)
c = Круг (10) печать (c.Пи) print (Circle.pi)
Вывод:
Язык кода: Python (python)
3.14159 3.14159
Как работают атрибуты класса Python
Когда вы обращаетесь к атрибуту через экземпляр класса, Python ищет атрибут в списке атрибутов экземпляра. Если в списке атрибутов экземпляра нет этого атрибута, Python продолжает поиск атрибута в списке атрибутов класса.Python возвращает значение атрибута, пока он находит атрибут в списке атрибутов экземпляра или списке атрибутов класса.
Однако, если вы обращаетесь к атрибуту, Python напрямую ищет атрибут в списке атрибутов класса.
В следующем примере определяется класс Test
, чтобы продемонстрировать, как Python обрабатывает атрибуты экземпляра и класса.
Язык кода: Python (python)
класс Тест: х = 10 def __init __ (сам): self.x = 20 test = Test () печать (test.Икс) print (Test.x)
Как это работает.
Класс Test
имеет два атрибута с одинаковым именем ( x
), один - это атрибут экземпляра, а другой - атрибут класса.
Когда мы обращаемся к атрибуту x
через экземпляр класса Test
, он возвращает 20, которое является переменной атрибута экземпляра.
Однако, когда мы обращаемся к атрибуту x
через класс Test
, он возвращает 10, что является значением атрибута класса x
.
Когда использовать атрибуты класса Python
Атрибуты класса полезны в некоторых случаях, таких как хранение констант классов, отслеживание данных по всем экземплярам и определение значений по умолчанию.
1) Сохранение констант класса
Поскольку константа не меняется от экземпляра к экземпляру класса, удобно хранить ее как атрибут класса.
Например, класс Circle
имеет константу pi
, которая одинакова для всех экземпляров класса.Следовательно, это хороший кандидат на атрибуты класса.
2) Отслеживание данных по всем экземплярам
Следующее добавляет атрибут класса circle_list
к классу Circle
. Когда вы создаете новый экземпляр класса Circle
, конструктор добавляет этот экземпляр в список:
Язык кода: Python (python)
class Circle: circle_list = [] пи = 3,14159 def __init __ (self, radius): self.radius = радиус себя.circle_list.append (самостоятельно) область определения (self): return self.pi * self.radius ** 2 окружность def (self): return 2 * self.pi * self.radius c1 = Круг (10) c2 = Круг (20) print (len (Circle.circle_list))
3) Определение значений по умолчанию
Иногда вы хотите установить значение по умолчанию для всех экземпляров класса. В этом случае вы можете использовать атрибут класса.
В следующем примере определяется класс Product
.Все экземпляры класса Product
будут иметь скидку по умолчанию, заданную атрибутом default_discount
class:
Язык кода: Python (python)
class Product: default_discount = 0 def __init __ (self, price): self.price = цена self.discount = Product.default_discount def set_discount (самостоятельно, скидка): self.discount = скидка def net_price (self): return self.price * (1 - самостоятельная скидка) p1 = Продукт (100) печать (p1.net_price ()) p2 = Продукт (200) p2.set_discount (0,05) печать (p2.net_price ())
Сводка
- Атрибут класса используется всеми экземплярами класса. Чтобы определить атрибут класса, вы помещаете его вне метода
__init __ ()
. - Используйте
class_name.class_attribute
илиobject_name.class_attribute
для доступа к значениюclass_attribute
. - Используйте атрибуты класса для хранения контуров класса, отслеживания данных по всем экземплярам и установки значений по умолчанию для всех экземпляров класса.
Вы нашли это руководство полезным?
Как получить список атрибутов класса в Python
На днях я пытался выяснить, есть ли простой способ получить определенные атрибуты класса (также известные как «переменные экземпляра»). Причина заключалась в том, что мы использовали созданные нами атрибуты для сопоставления с полями в анализируемом файле. Итак, в основном мы читаем файл построчно, и каждую строку можно разделить на 150+ частей, которые необходимо сопоставить с полями, которые мы создаем в классе.Загвоздка в том, что мы недавно добавили в класс больше полей, и в коде есть проверка, жестко запрограммированная на количество полей, которые должны быть в файле. Таким образом, когда я добавил больше полей, это прервало проверку. Я надеюсь, что все это имело смысл. Теперь вы знаете предысторию, поэтому мы можем двигаться дальше. Я нашел три разных способа сделать это, поэтому мы перейдем от самого сложного к самому простому.
Как должно знать большинство программистов Python, Python предоставляет небольшую удобную встроенную функцию под названием dir .Я могу использовать это в экземпляре класса, чтобы получить список всех атрибутов и методов этого класса вместе с некоторыми унаследованными магическими методами, такими как '__delattr__', '__dict__', '__doc__', '__format__' и т. Д. попробуйте сами, выполнив следующие действия:
x = директория (myClassInstance)
Однако мне не нужны ни магические методы, ни методы. Мне просто нужны атрибуты. Чтобы все было кристально ясно, давайте напишем код!
########################################################################## ##################### класс Test: "" "" "" # ------------------------------------------------- --------------------- def __init __ (сам): себя.varOne = "" self.varTwo = "" self.varThree = "" # ------------------------------------------------- --------------------- def methodOne (self): "" "" "" print "Вы только что позвонили в methodOne!" # ------------------------------------------------- --------------------- если __name__ == "__main__": t = Тест ()
Мы хотим получить список, содержащий только self.varOne, self.varTwo и self.varThree. Первый метод, который мы рассмотрим, - это использование модуля проверки Python.
импортная инспекция переменные = [i для i в dir (t), если не inspect.ismethod (i)]
Выглядит не слишком сложно, правда? Но для этого требуется импорт, и я бы предпочел этого не делать. С другой стороны, если вам нужно заняться самоанализом, модуль inspect - отличный способ. Он довольно мощный и может рассказать вам много замечательных вещей о вашем классе или о том, о котором вы даже не писали.В любом случае, следующий самый простой способ, который я нашел, заключался в использовании Python вызываемой встроенной функции :
переменные = [i for i in dir (t) if not callable (i)]
Вы можете узнать больше о callable в документации Python. По сути, все, что делает callable, - это возвращает True или False в зависимости от того, является ли объект, который вы ему передали, вызываемым. Методы вызываются, переменные - нет. Таким образом, мы перебираем каждый элемент в классе dict и добавляем их в список только в том случае, если они являются вызываемыми , а не (т.е.е. не методы). Довольно красиво и не требует импорта! Но есть еще более простой способ!
Самый простой способ, который я нашел, - использовать магический метод __dict__ . Это встроено в каждый создаваемый вами класс, если вы его не переопределите. Поскольку мы имеем дело со словарем Python, мы можем просто вызвать метод ключей !
переменные = t .__ dict __. keys ()
Настоящий вопрос теперь в том, следует ли вам использовать магический метод для этого? Большинство программистов на Python, вероятно, не одобрят это.Они волшебны, поэтому их не следует использовать, если вы не занимаетесь метапрограммированием. Лично я считаю, что это вполне приемлемо для этого варианта использования. Сообщите мне о любых других методах, которые я пропустил или которые, по вашему мнению, лучше.
ресурса
Как атрибуты объекта и класса работают в Python 🙂 | Джулиан Вильегас | Основы Разработка полного стека
Собственный источник
Класс - это настраиваемый тип данных. В python мы можем создавать настраиваемые классы, которые полностью интегрированы и могут использоваться так же, как встроенные типы данных.Например, dict , int и str. Мы используем термин объект и экземпляр, для обозначения экземпляра определенного класса. Например, 7 - это объект int , а «Holberton» - объект str .
Прежде чем описывать эту новую концепцию, поясним, что у объектов обычно есть атрибуты и методы, которые также являются вызываемыми атрибутами, а другие атрибуты являются данными.Атрибуты данных обычно реализуются как переменные экземпляра, то есть переменные, уникальные для конкретного объекта. Тогда:
Атрибуты класса - это атрибуты, принадлежащие самому классу. Они будут общими для всех экземпляров класса. Следовательно, они имеют одинаковую ценность для каждого экземпляра. Мы определяем атрибуты класса вне всех методов, обычно они размещаются вверху, прямо под заголовком класса. Например:
Собственный источник
На предыдущем изображении мы видим в интерактивном сеансе Python определение атрибута класса. Кроме того, мы видим, что мы можем получить доступ к атрибуту класса через экземпляр или через имя класса:
Атрибуты экземпляра - это принадлежат конкретным экземплярам класса.Это означает, что для двух разных экземпляров атрибуты экземпляра обычно разные. В следующем примере показано это определение:
Если у нас есть два экземпляра, и мы хотим изменить атрибут класса, вы должны сделать это с помощью записи ClassName.AttributeName. В противном случае вы создадите новую переменную экземпляра.
Собственный источник
В предыдущем определении обоих понятий; мы определяем атрибуты класса вне всех методов, обычно они помещаются вверху, прямо под заголовком класса.т.е.
Собственный источник
Атрибуты экземпляра , просто объявив их и присвоив им значение вне класса. Есть другой способ сделать это, в котором используется метод __init__ . Это популярный метод «конструктора», используемый во многих языках ООП. Конструктор вызывается автоматически после создания экземпляра класса. Обычно это первый метод, определенный внутри класса. Например:
Собственный источник
self ключевое слово в магическом методе __init__ относится к текущему экземпляру или объекту класса.Теперь, вне класса, мы можем создать новый экземпляр:
Собственный источник
Будет вызван метод __init__ , а ширина и высота атрибутов экземпляра будут установлены на 8 и 4 соответственно в объекте my_rectangle_1.
Как мы видели в предыдущем примере, атрибуты класса и экземпляра отличаются. Атрибуты класса доступны для всех экземпляров класса, где атрибуты экземпляра доступны только для этого конкретного экземпляра класса.Мы можем получить доступ к атрибуту класса через экземпляр или имя класса, тогда как к атрибуту экземпляра можно получить доступ только через экземпляр, которому он принадлежит.
Преимущества атрибутов класса:
Все экземпляры класса наследуют их от класса.
Атрибуты класса часто используются для определения констант, которые тесно связаны с конкретным классом.
В них хранятся данные, относящиеся ко всем экземплярам. Например, у нас может быть атрибут класса счетчика, который увеличивается каждый раз, когда мы создаем новый экземпляр, и уменьшается каждый раз, когда мы удаляем экземпляр.Таким образом, мы всегда можем отслеживать, сколько экземпляров одного и того же класса мы создали.
Использование классов дает возможность повторно использовать код, что делает программу более эффективной.
Недостатки атрибутов класса:
Это может стать беспорядочным, когда вы создаете экземпляр, в котором значение атрибута класса отличается от значения атрибута класса, а затем пытаетесь получить его через другой экземпляр. Поведение быстро становится неожиданным.
Когда мы устанавливаем атрибут для экземпляра, имя которого совпадает с именем атрибута класса, мы заменяем атрибут класса атрибутом экземпляра, который будет иметь приоритет над ним.
Преимущества атрибутов экземпляра:
Они специфичны для объекта, их легко установить и получить благодаря свойствам.
Они удаляются после удаления экземпляра, поэтому они умирают вместе с экземпляром, с которым они связаны, что делает ситуацию более понятной.
Очень часто методы объекта обновляют значения атрибутов объекта
Недостатки атрибутов экземпляра:
Они не позволяют отслеживать значения между экземплярами.
Тот факт, что их значения теряются при удалении, также является недостатком в некоторых случаях, когда, например, вы хотите сохранить историю значений.
Атрибуты классов и объектов Python хранятся в отдельных словарях.которые они используют для хранения своих атрибутов и соответствующих им значений, как мы видим здесь:
Собственный источник
Результатом будет:
Собственный источник
Во-первых, мы напечатали атрибуты экземпляра my_rectangle_1. Они были инициализированы при создании объекта. То же самое печатает другой экземпляр объекта прямоугольника. В последней строке мы видим, что применение __dict__ к классу Rectangle также распечатывает словарь с атрибутами, потому что даже если Rectangle является классом, технически он тоже является объектом и имеет атрибуты, которые можно перечислить.
Классы Python
Классы / объекты Python
Python - это объектно-ориентированный язык программирования.
Почти все в Python представляет собой объект со своими свойствами и методами.
Класс похож на конструктор объекта или «план» для создания объектов.
Создать класс
Для создания класса используйте ключевое слово class
:
Пример
Создайте класс MyClass со свойством x:
класс MyClass:
x = 5
Попробуй сам "
Создать объект
Теперь мы можем использовать класс MyClass для создания объектов:
Пример
Создайте объект с именем p1 и распечатайте значение x:
p1 = MyClass ()
печать (p1.х)
Попробуй сам "
Функция __init __ ()
Приведенные выше примеры представляют собой классы и объекты в их простейшей форме и являются
не очень полезен в реальных приложениях.
Чтобы понять значение классов, мы должны понимать встроенный __init __ ()
функция.
Все классы имеют функцию __init __ (), которая всегда выполняется, когда
занятие инициируется.
Используйте функцию __init __ () для присвоения значений свойствам объекта или другим
операции, которые необходимо выполнить, когда объект
создается:
Пример
Создайте класс с именем Person, используйте функцию __init __ () для присвоения значений
для имени и возраста:
class Person:
def __init __ (я, имя, возраст):
себя.name = name
self.age = age
p1 = Person ("Джон",
36)
print (p1.name)
print (p1.age)
Попробуй сам "
Примечание: Функция __init __ ()
вызывается автоматически каждый раз, когда класс используется для создания нового объекта.
Методы объекта
Объекты также могут содержать методы. Методы в объектах - это функции, которые
принадлежат объекту.
Создадим метод в классе Person:
Пример
Вставьте функцию, которая печатает приветствие, и выполните ее для объекта p1:
class Person:
def __init __ (я, имя, возраст):
себя.name = name
self.age = age
def myfunc (self):
print («Привет, меня зовут» + self.name)
p1 = Person («Джон»,
36)
p1.myfunc ()
Попробуй сам "
Примечание: Параметр self
является ссылкой на текущий экземпляр класса и используется для доступа к переменным, принадлежащим этому классу.
Самостоятельный параметр
Параметр self
является ссылкой на
текущий экземпляр класса и используется для доступа к переменным, принадлежащим этому классу.
Необязательно называть сам
, вы можете
назовите его как хотите, но он должен быть первым параметром любой функции
в классе:
Пример
Используйте слова mysillyobject и abc вместо self :
класс Person:
def __init __ (mysillyobject, name, age):
mysillyobject.