В чем отличие в присвоении атрибута класса в конструкторе и нет
В первом случае переменная a является атрибутом класса, и доступна и как W.a и как:
w = W() w.a
Во втором же случае переменная создаётся в момент инициализации объекта, а значит принадлежит объекту и доступна только как:
w = W() w.a
Основная разница заключается в том, как распределяется память. В первом случае память на хранение данной переменной выделяется при объявлении класса, во втором же случае память выделяется для каждого из объектов класса. Используется и то и другое, всё зависит от задачи.
Из фишек — можно например изменять значение сразу для всех объектов класса:
class A(): b = 1 a = A() b = A() a.b # результат 1 b.b # результат 1 # Когда меняем значение для члена класса A.b = 5 # Оно изменяется для всех объектов класса b.b # результат 5 a.b # результат 5
Отслеживать
ответ дан 19 окт 2015 в 7:40
Алексей Стародубцев Алексей Стародубцев
649 1 1 золотой знак 4 4 серебряных знака 15 15 бронзовых знаков
Очень подробно! Все яснопонятно! Спасибо вам!
19 окт 2015 в 17:02
Немного расширю ваш пример:
class T: def __init__(self): self.a = 10 self.b = [1, 2, 3] c = 20 d = [15]
Когда интерпретатор встречает этот код, он выполняет тело класса и все локальные переменные сохраняет как атрибуты этого класса.
Таким образом, мы имеем класс T , у которого есть три атрибута (не считая автоматические): c , d и __init__ (объявление функции тоже создаёт локальную переменную!):
>>> T.__dict__ < '__init__': , 'c': 15, 'd': [20], # . >
Когда мы создаём экземпляр класса, выполняется метод __init__ класса, в который передаётся заготовка создаваемого экземпляра. Таким образом, в строке
t = T()
Мы получаем экземпляр t класса T , который имеет два атрибута: a и b :
>>> t.__dict__
Что происходит дальше, когда мы пытаемся получить доступ к атрибутам?
Например, имеем три строки кода:
print(t.a) # 10 print(t.c) # 20 print(T.c) # 20 # print(T.a) # error: у класса нет такого атрибута
В первой строке интерпретатор посмотрит в атрибуты переменной t , найдёт среди них имя a и вернёт его значение.
Во второй строке происходит следующее. Интерпретатор не может найти атрибут c среди атрибутов объекта t , поэтому обращается к объекту класса, экземпляром которого является этот объект. У класса уже имеется нужный атрибут, поэтому на этом поиск завершается, возвращается значение 20 .
Третья строка выполняется аналогично первой, с условием что атрибут ищется сразу у класса.
Четвёртая строка выполнится с ошибкой, потому что атрибуты экземпляров не распространяются на классы.
Какие за счёт этого возникают эффекты?
Все атрибуты класса доступны всем экземплярам. Мы можем изменять атрибуты или заменять их значения, и это отразится на всех экземплярах класса:
t1 = T() t2 = T() print(t1.d, t2.d) # [15] [15] # изменим атрибут класса, пользуясь тем, что список является изменяемым типом t1.d.append(16) print(t1.d, t2.d) # [15, 16] [15, 16] # создадим новый атрибут уровня класса T.x = "Hello!" print(t1.x, t2.x) # Hello! Hello!
Важно помнить, что атрибуты экземпляра класса скрывают атрибуты класса:
t1.d = [-1, -2] print(t1.d, t2.d, T.d) # [-1, -2] [15, 16] [15, 16]
Т.е. в строке t1.d = [-1, -2] не изменяется значение атрибута класса, а создаётся новый атрибут у объекта t1 . Поэтому изменить значение неизменяемого атрибута класса через экземпляр этого класса не получится. Если требуется подобная операция, стоит изменять атрибут класса.
В итоге имеем следующее: при использовании атрибутов класса стоит проявлять осторожность с изменяемыми объектами (изменение в одном экземпляре затронет все остальные), а также со скрытием атрибутов класса атрибутами экземпляра класса.
С учётом этих замечаний обычно рекомендуется создавать все атрибуты в методе __init__ класса, а не в самом классе.
Иногда в самом классе хранят константные значения, относящиеся ко всем экземплярам, например, значения по умолчанию.
Свойства — Python: Введение в ООП
Что нам делать, если мы захотим иметь возможность получить еще и полное имя (имя+фамилию) объекта?
Мы могли бы в инициализаторе добавить строчку self.full_name = name + ‘ ‘ + surname . Но если впоследствии имя или фамилия поменяются, полное имя устареет, и его нужно будет не забыть поменять.
Также мы могли бы добавить метод с именем вроде get_full_name , который бы возвращал полное имя. Но и такой вариант не идеален, ведь нам хочется работать с полным именем, как с простым атрибутом-переменной.
Оказывается, что в Python есть средство, позволяющее получить атрибут, значение которого вычисляется динамически, то есть во время обращения к атрибуту. Речь идет о свойствах.
Добавим в класс свойство и посмотрим на его использование:
class Person: def __init__(self, name, surname): self.name = name self.surname = surname @property def full_name(self): return self.name + ' ' + self.surname tom = Person('Thomas', 'Smith') tom.full_name # 'Thomas Smith'
full_name выглядит как утка, то есть как атрибут! Но вычисляется динамически. И если мы поменяем name , то full_name также изменится:
tom.name = 'Alice' tom.full_name # 'Alice Smith'
Как вы можете видеть, свойство объявляется как метод без параметров (кроме self , естественно), декорированный с помощью property . Такой метод, возвращающий динамически вычисляемое значение, называется геттером (getter).
Сеттер
Атрибутам пространства имен можно присваивать значения, но что делать, если атрибут — свойство? Если вы попытаетесь присвоить значение свойству, у которого один лишь getter, вы получите ошибку «AttributeError: can’t set attribute».
Чтобы иметь возможность присвоить значение свойству, нужно использовать сеттер (setter). Сеттер — это тоже метод, который принимает новое значение для атрибута и как-то его обрабатывает. Чтобы метод стал сеттером, его тоже нужно соответствующим образом декорировать. Если у вас уже есть геттер, вы можете сделать так:
class Person: def __init__(self, name, surname): self.name = name self.surname = surname @property def full_name(self): return self.name + ' ' + self.surname # сеттер для свойства full_name @full_name.setter def full_name(self, new): self.name, self.surname = new.split(' ')
Теперь свойству full_name можно присваивать новое полное имя. В итоге оно будет разделено по пробелу на две части, первая из которых станет новым именем, а вторая — фамилией. Пример:
tom = Person('Thomas', 'Smith') tom.full_name # 'Thomas Smith' tom.full_name = 'Alice Cooper' tom.name # 'Alice' tom.surname # 'Cooper'
Сеттеры часто используют для того, чтобы проверить корректность нового значения или произвести какие-то его преобразования перед фактическим сохранением в другие атрибуты.
Делитер
Кроме геттеров и сеттеров, в Python существует еще один важный метод для управления атрибутами классов — это делитеры (deleter). Этот метод предоставляет дополнительный уровень контроля над тем, как атрибуты класса удаляются.
Делитер — это особый метод в классе, который вызывается при удалении атрибута. Как правило, удаление атрибута — это нечастая операция, и в большинстве случаев мы можем обойтись без явного определения делитера. Тем не менее, зная о его существовании, мы можем контролировать процесс удаления атрибутов, например, очищать связанные ресурсы или выполнять некоторую логику очистки.
В нашем примере с классом Person , если мы решим реализовать делитер для свойства full_name , то его можно использовать для удаления имени и фамилии. Давайте посмотрим, как это работает:
class Person: def __init__(self, name, surname): self.name = name self.surname = surname @property def full_name(self): return self.name + ' ' + self.surname @full_name.setter def full_name(self, new): self.name, self.surname = new.split(' ') @full_name.deleter def full_name(self): print("Удаляем имя и фамилию!") self.name = None self.surname = None
Теперь при удалении свойства full_name будет вызываться наш делитер, который устанавливает свойствам name и surname значение None , тем самым «удаляя» эти значения. Пример:
tom = Person('Thomas', 'Smith') tom.full_name # 'Thomas Smith' del tom.full_name # Удаляем имя и фамилию tom.name # None tom.surname # None
Использование делитера особенно полезно в случаях, когда необходимо управлять удалением важных или связанных данных, или выполнять освобождение ресурсов (как, например, закрытие файлов или сетевых соединений) в момент уничтожения объекта или его свойств.
Декоратор property
Если посмотреть в документацию к декоратору property , то можно увидеть такую сигнатуру:
property(fget=None, fset=None, fdel=None, doc=None)
Первые три аргумента позволяют задать getter, setter и deleter, а аргумент doc позволяет указать docstring. В такой форме property удобно использовать, когда вы уже имеете готовые функции, которые хотите просто «упаковать» в свойство:
def get_full_name(self): . def set_full_name(self, new): . class Person: . full_name = property( fget=get_full_name, fset=set_full_name, doc='A full name of person' )
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Разница между старым и новым стилем классов в Python
Существует некоторая путаница между старым и новым стилем классов в Python. В этой статье мы попытаемся разобраться в этом вопросе.
Старый стиль классов
Старый стиль классов был основным способом определения классов в Python до версии 2.2. Пример старого стиля класса:
class OldStyleClass: def __init__(self, name): self.name = name
Старые классы создаются без указания базового класса или указываются без явного наследования от object . Они имеют некоторые ограничения, такие как отсутствие некоторых магических методов и свойств.
Новый стиль классов
Начиная с версии Python 2.2, был введен новый стиль классов. Это основной способ определения классов в Python 3. Пример нового стиля класса:
class NewStyleClass(object): def __init__(self, name): self.name = name
Новые классы создаются путем явного наследования от object или от другого нового стиля класса. Они имеют множество преимуществ по сравнению со старым стилем, включая поддержку множественного наследования, дескрипторов, свойств, магических методов и декораторов.
Когда использовать старый и новый стиль классов?
Так как новый стиль классов предоставляет больше возможностей и является стандартом в Python 3, рекомендуется использовать его в большинстве случаев. Однако старый стиль классов все еще может быть полезен в некоторых ситуациях, особенно при работе с очень большими и сложными системами, где изменение стиля классов может привести к непредвиденным последствиям.
В заключение, выбор стиля классов зависит от конкретного случая и требований к коду. Но в целом, новый стиль классов предоставляет больше возможностей и более гибок.
Чем отличается свойство от поля? Вопрос по ООП. Нет, это не синонимы.
Разница в том, что свойство — это само поле плюс два метода (установить и взять значение в поле). Вы может сделать свойство, а потом откройте IL код и посмотрите, и увидите поле типа private и два метода get_ и set_. А разные варианты синтаксической записи — это просто синтаксический сахар самого языка.
Остальные ответы
Фактически одно и то же, но разный подтекст. Поле это глобальная переменная класса, а свойство это атрибут объекта (поле класса), к которому обращаешься через геттеры и сеттеры. В c# реализация свойств отличается от полей. Но в целом это одно и то же.
Zazi DooyiloУченик (95) 6 лет назад
То есть, свойство — это в описание класса, а поле это уже конкретное реализация свойства, описанного в классе?
Максим Искусственный Интеллект (189158) Я бы сказал, что что-то типа этого. То есть свойство это уже ближе к вопросу о том, для чего применяется поле. Свойство объекта реализуется полями (но в c# для полей есть свой механизм). Хотя, вероятно, не любое поле можно назвать свойством.