Перейти к содержимому

Как обращаться к атрибутам класса python

  • автор:

Как обращаться к атрибутам класса python

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

class Person: def __init__(self, name, age): self.name = name # устанавливаем имя self.age = age # устанавливаем возраст def print_person(self): print(f"Имя: \tВозраст: ") tom = Person("Tom", 39) tom.name = "Человек-паук" # изменяем атрибут name tom.age = -129 # изменяем атрибут age tom.print_person() # Имя: Человек-паук Возраст: -129

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

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

Язык программирования Python позволяет определить приватные или закрытые атрибуты. Для этого имя атрибута должно начинаться с двойного подчеркивания — __name . Например, перепишем предыдущую программу, сделав оба атрибута — name и age приватными:

class Person: def __init__(self, name, age): self.__name = name # устанавливаем имя self.__age = age # устанавливаем возраст def print_person(self): print(f"Имя: \tВозраст: ") tom = Person("Tom", 39) tom.__name = "Человек-паук" # пытаемся изменить атрибут __name tom.__age = -129 # пытаемся изменить атрибут __ tom.print_person() # Имя: Tom Возраст: 39

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

tom.__name = "Человек-паук" # пытаемся изменить атрибут __name tom.__age = -129 # пытаемся изменить атрибут __

Но вывод метода print_person покажет, что атрибуты объекта не изменили свои значения:

tom.print_person() # Имя: Tom Возраст: 39

Как это работает? При объявлении атрибута, имя которого начинается с двух прочерков, например, __attribute , Python в реальности определяет атрибута, который называется по шаблону _ClassName__atribute . То есть в случае выше будут создаваться атрибуты _Person__name и _Person__age . Поэтому к такому атрибуту мы сможем обратиться только из того же класса. Но не сможем обратиться вне этого класса. Например, присвоение значения этому атрибуту ничего не даст:

tom.__age = 43

Потому что в данном случае просто определяется динамически новый атрибут __age, но это он не имеет ничего общего с атрибутом self.__age или точнее self._Person__age .

А попытка получить его значение приведет к ошибке выполнения (если ранее не была определена переменная __age):

print(tom.__age)

Тем не менее приватность атрибутов тут довольно относительна. Например, мы можем использовать полное имя атрибута:

class Person: def __init__(self, name, age): self.__name = name # устанавливаем имя self.__age = age # устанавливаем возраст def print_person(self): print(f"Имя: \tВозраст: ") tom = Person("Tom", 39) tom._Person__name = "Человек-паук" # изменяем атрибут __name tom.print_person() # Имя: Человек-паук Возраст: 39

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

Методы доступа. Геттеры и сеттеры

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

class Person: def __init__(self, name, age): self.__name = name # устанавливаем имя self.__age = age # устанавливаем возраст # сеттер для установки возраста def set_age(self, age): if 0 < age < 110: self.__age = age else: print("Недопустимый возраст") # геттер для получения возраста def get_age(self): return self.__age # геттер для получения имени def get_name(self): return self.__name def print_person(self): print(f"Имя: \tВозраст: ") tom = Person("Tom", 39) tom.print_person() # Имя: Tom Возраст: 39 tom.set_age(-3486) # Недопустимый возраст tom.set_age(25) tom.print_person() # Имя: Tom Возраст: 25

Для получения значения возраста применяется метод get_age:

def get_age(self): return self.__age

Для изменения возраста определен метод set_age:

def set_age(self, age): if 0 < age < 110: self.__age = age else: print("Недопустимый возраст")

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

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

Аннотации свойств

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

Для создания свойства-геттера над свойством ставится аннотация @property .

Для создания свойства-сеттера над свойством устанавливается аннотация имя_свойства_геттера.setter .

Перепишем класс Person с использованием аннотаций:

class Person: def __init__(self, name, age): self.__name = name # устанавливаем имя self.__age = age # устанавливаем возраст # свойство-геттер @property def age(self): return self.__age # свойство-сеттер @age.setter def age(self, age): if 0 < age < 110: self.__age = age else: print("Недопустимый возраст") @property def name(self): return self.__name def print_person(self): print(f"Имя: \tВозраст: ") tom = Person("Tom", 39) tom.print_person() # Имя: Tom Возраст: 39 tom.age = -3486 # Недопустимый возраст (Обращение к сеттеру) print(tom.age) # 39 (Обращение к геттеру) tom.age = 25 # (Обращение к сеттеру) tom.print_person() # Имя: Tom Возраст: 25

Во-первых, стоит обратить внимание, что свойство-сеттер определяется после свойства-геттера.

Во-вторых, и сеттер, и геттер называются одинаково - age. И поскольку геттер называется age, то над сеттером устанавливается аннотация @age.setter .

После этого, что к геттеру, что к сеттеру, мы обращаемся через выражение tom.age .

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

Как обращаться к атрибутам класса python

Кроме атрибутов объектов в классе можно определять атрибуты классов. Подобные атрибуты определяются в виде переменных уровня класса. Например:

class Person: type = "Person" description = "Describes a person" print(Person.type) # Person print(Person.description) # Describes a person Person.type = "Class Person" print(Person.type) # Class Person

Здесь в классе Person определено два атрибута: type, который хранит имя класса, и description, который хранит описание класса.

Для обращения к атрибутам класса мы можем использовать имя класса, например: Person.type , и, как и атрибуты объекта, мы можем получать и изменять их значения.

Подобные атрибуты являются общими для всех объектов класса:

class Person: type = "Person" def __init__(self, name): self.name = name tom = Person("Tom") bob = Person("Bob") print(tom.type) # Person print(bob.type) # Person # изменим атрибут класса Person.type = "Class Person" print(tom.type) # Class Person print(bob.type) # Class Person

Атрибуты класса могут применяться для таких ситуаций, когда нам надо определить некоторые общие данные для всех объектов. Например:

class Person: default_name = "Undefined" def __init__(self, name): if name: self.name = name else: self.name = Person.default_name tom = Person("Tom") bob = Person("") print(tom.name) # Tom print(bob.name) # Undefined

В данном случае атрибут default_name хранит имя по умолчанию. И если в конструктор передана пустая строка для имени, то атрибуту name передается значение атрибута класса default_name. Для обращения к атрибуту класса внутри методов можно применять имя класса

self.name = Person.default_name
Атрибут класса

Возможна ситуация, когда атрибут класса и атрибут объекта совпадает по имени. Если в коде для атрибута объекта не задано значение, то для него может применяться значение атрибута класса:

class Person: name = "Undefined" def print_name(self): print(self.name) tom = Person() bob = Person() tom.print_name() # Undefined bob.print_name() # Undefined bob.name = "Bob" bob.print_name() # Bob tom.print_name() # Undefined

Здесь метод print_name использует атрибут объект name, однако нигде в коде этот атрибут не устанавливается. Зато на уровне класса задан атрибут name. Поэтому при первом обращении к методу print_name, в нем будет использоваться значение атрибута класса:

tom = Person() bob = Person() tom.print_name() # Undefined bob.print_name() # Undefined

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

bob.name = "Bob" bob.print_name() # Bob tom.print_name() # Undefined

Причем второй объект - tom продолжит использовать атрибут класса. И если мы изменим атрибут класса, соответственно значение tom.name тоже изменится:

tom = Person() bob = Person() tom.print_name() # Undefined bob.print_name() # Undefined Person.name = "Some Person" # меняем значение атрибута класса bob.name = "Bob" # устанавливаем атрибут объекта bob.print_name() # Bob tom.print_name() # Some Person

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

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

class Person: __type = "Person" @staticmethod def print_type(): print(Person.__type) Person.print_type() # Person - обращение к статическому методу через имя класса tom = Person() tom.print_type() # Person - обращение к статическому методу через имя объекта

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

Также в классе Person определен статический метод print_type , который выводит на консоль значение атрибута __type. Действие этого метода не зависит от конкретного объекта и относится в целом ко всему классу - вне зависимости от объекта на консоль будет выводится одно и то же значение атрибута __type. Поэтому такой метод можно сделать статическим.

Настройка доступа к атрибутам класса Python

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

Содержание:

  • object.__getattr__() вызывается при обращении, к несуществующему атрибуту,
  • object.__getattribute__() вызывается всегда, при обращении к любому атрибуту,
  • object.__setattr__() вызывается при попытке присвоения значения любому атрибуту,
  • object.__delattr__() вызывается при попытке удаления любого атрибута,
  • object.__dir__() вызывается функцией dir() .
object.__getattr__(self, name) :

Метод object.__getattr__() вызывается, когда доступ к атрибуту по умолчанию завершается с ошибкой AttributeError (либо __getattribute__() вызывает исключение AttributeError , потому что name не является атрибутом экземпляра, либо атрибутом в дереве классов для self ; либо метод __get__() свойства name вызывает AttributeError ). Этот метод должен либо вернуть (вычисленное) значение атрибута, либо вызвать исключение AttributeError .

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

Смотрите метод __getattribute__() , чтобы получить полный контроль над доступом к атрибутам.

class Order: bascket = 0 def __init__(self, price, num): self.price = price self.num = num def __getattr__(self, attrname): # магический метод `__getattr__` будет вызываться при # попытке доступа к любому несуществующему атрибуту, # имя атрибута сохраняется в переменной `attrname` if attrname == "total": # условие выполнится при доступе к атрибуту `total`, # т.к. он фактически не определен и является вычисляемым return self.price * self.num elif attrname == "bascket": # условие выполнятся не будет, т.к. # атрибут `bascket` существует return self.bascket + 100 else: # условие выполнится при доступе к # любому, несуществующему атрибуту print(f'Атрибута attrname> не существует!') raise AttributeError >>> order = Order(10, 15) # вычисляемый атрибут >>> order.total # 150 # атрибут `bascket` задан, для него # магический метод `__getattr__` не вызывается >>> order.bascket # 0 # атрибут `basket_total` не существует >>> order.basket_total # Атрибута basket_total не существует! # Traceback (most recent call last): # . # AttributeError # присвоим значение `на лету` >>> order.basket_total = 0 # теперь все нормально >>> order.basket_total # 0 

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

object.__getattribute__(self, name) :

Метод object.__getattribute__() вызывается безоговорочно (всегда) для реализации доступа к атрибутам для экземпляров класса. Если класс также определяет специальный метод __getattr__() , то он не будет вызываться, если только метод __getattribute__() либо вызовет его явно, либо вызовет исключение AttributeError .

Этот метод должен возвращать (вычисленное) значение атрибута или вызывать исключение AttributeError . Чтобы избежать бесконечной рекурсии в этом методе, его реализация всегда должна вызывать метод базового класса с тем же name для доступа к любым нужным атрибутам, например, object.__getattribute__(self, name) .

Примечание. Специальный метод object.__getattribute__() может быть пропущен при поиске специальных методов в результате неявного вызова через синтаксис языка или встроенных функций.

class Order: basket = 0 def __init__(self, price, num): self.price = price self.num = num def __getattribute__(self, attrname): # магический метод `__getattribute__` будет вызываться # ВСЕГДА, при попытке доступа к любому атрибуту, # имя атрибута сохраняется в переменной `attrname` if attrname == "total": # `total` - вычисляемый атрибут, return self.price * self.num if attrname == "price": # `price` - существующий атрибут, что бы не было # рекурсии обратимся к нему как сказано в документации return object.__getattribute__(self, attrname) elif attrname == "basket": # `basket` существующий атрибут, # попробуем к нему обратиться через `self` return self.basket elif attrname == "total_basket": # `basket` атрибут не существует, при этом # сразу возвращается значение return 0 else: # определяем как вызывать остальные существующие # атрибуты - в частности `num` return object.__getattribute__(self, attrname) >>> order = Order(10, 15) >>> order.total # 150 >>> order.price # 10 # назначим другую стоимость >>> order.price = 5 # вычисляемый атрибут работает >>> order.total # 75 # к этому атрибуту обратились через `self` >>> order.basket # в итоге рекурсия (как описано в документации) # Traceback (most recent call last): # . # [Previous line repeated 995 more times] # File "", line 13, in __getattribute__ # RecursionError: maximum recursion depth exceeded in comparison # в определении метода `__getattribute__` # к атрибуту `total_basket` не обращаемся # а сразу возвращаем значение >>> order.total_basket # 0 # попробуем что нибудь присвоить >>> order.total_basket = 100 # значение осталось то же, как и ожидалось >>> order.total_basket # 0 # атрибут `other` - не определен >>> order.other # Traceback (most recent call last): # . # AttributeError: 'Order' object has no attribute 'other' # присвоим значение `на лету` >>> order.other = 'ok' # теперь работает >>> order.other 'ok' 

Из примера отчетливо видно, что специальный (магический) метод __getattribute__() вызывается автоматически для всех атрибутов, существуют они или нет. Но, для предотвращения рекурсии, к существующим атрибутам необходимо обращаться явно object.__getattribute__(self, attrname) .

object.__setattr__(self, name, value) :

Метод object.__setattr__() вызывается при попытке присвоения значения атрибуту. Метод вызывается вместо обычного механизма (т.е. сохранения значения в словаре экземпляра). Аргумент name - это имя атрибута, value - значение, которое будет ему присвоено.

Если специальный метод __setattr__() назначает атрибут экземпляра класса, то он должен вызвать метод базового класса с тем же именем name , например, object.__setattr__(self, name, value) .

class Order: basket = 0 # кол-во товара в корзине def __init__(self, price, num): self.price = price self.num = num def __setattr__(self, attrname, value): if attrname == 'basket': # увеличиваем количество товаров в корзине на # значение, которое передаем атрибуту `basket` self.__dict__[attrname] = self.basket + value else: # для остальных атрибутов, просто # присваиваем новое значение self.__dict__[attrname] = value def __getattr__(self, attrname): # магический метод `__getattr__` вызываться всегда # если атрибут фактически не определен и не создан `на лету` if attrname == "total": # `total` - вычисляемый атрибут (общая стоимость товара) return self.price * self.num elif attrname == "total_basket": # `basket` - вычисляемый атрибут (стоимость товара в корзине) return self.price * self.basket else: raise AttributeError >>> order = Order(10, 15) >>> order.total # 150 # смотрим что в корзине >>> order.basket # 0 # положим 1 товар в корзину >>> order.basket = 1 # смотрим >>> order.basket # 1 # положим еще 2 товара в корзину >>> order.basket = 2 # смотрим >>> order.basket # 3 # стоимость товаров в корзине >>> order.total_basket # 30 
object.__delattr__(self, name) :

Метод object.__delattr__() работает так же как __setattr__() , но только для удаления атрибута вместо присвоения. Метод должен быть реализован, только если имеет смысл операции удаления del obj.name для этого атрибута объекта.

object.__dir__(self) :

Метод object.__dir__() вызывается, когда для объекта вызывается функция dir() . Должна быть возвращена последовательность. Функция dir() преобразует возвращенную последовательность в список и сортирует его.

  • ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
  • Пространство имен и область видимости в классах
  • Определение классов
  • Объект класса и конструктор класса
  • Создание экземпляра класса
  • Метод экземпляра класса
  • Что такое метод класса и зачем нужен
  • Что такое статический метод в классах Python и зачем нужен
  • Атрибуты класса и переменные экземпляра класса
  • Кэширование методов экземпляра декоратором lru_cache
  • Закрытые/приватные методы и переменные класса Python
  • Наследование классов
  • Множественное наследование классов
  • Абстрактные классы
  • Перегрузка методов в классе Python
  • Перегрузка операторов в классах Python
  • Что такое миксины и как их использовать
  • Класс Python как структура данных, подобная языку C
  • Создание пользовательских типов данных
  • Специальные (магические) методы класса Python
  • Базовая настройка классов Python магическими методами
  • Настройка доступа к атрибутам класса Python
  • Дескриптор класса для чайников
  • Протокол дескриптора класса
  • Практический пример дескриптора
  • Использование метода .__new__() в классах Python
  • Специальный атрибут __slots__ класса Python
  • Специальный метод __init_subclass__ класса Python
  • Определение метаклассов metaclass
  • Эмуляция контейнерных типов в классах Python
  • Другие специальные методы класса
  • Как Python ищет специальные методы в классах
  • Шаблон проектирования Фабрика и его реализация

Доступ к атрибутам и методам в Python

Статические методы: статический метод - это метод [функция-член], который вообще не использует аргумент self. Чтобы объявить статический метод, используйте оператор «@staticmethod».

# Python code for accessing methods using static method
class test:
@staticmethod
def square(x):
test.result = x * x
# object 1 for class
# object 2 for class
t1.square( 2 )
# printing result for square(2)
print (t1.result)
t2.square( 3 )
# printing result for square(3)
print (t2.result)
# printing the last value of result as we declared the method static
print (t1.result)

4 9 9

Доступ к атрибутам и методам одного класса в другом классе Доступ к атрибутам и методам одного класса в другом классе осуществляется путем передачи объекта одного класса другому.
Объяснено приведенным ниже примером:

# Python code for Accessing attributes and methods
# of one class in another class
class ClassA():
def __init__( self ):
self .var1 = 1
self .var2 = 2
def methodA( self ):
self .var1 = self .var1 + self .var2
return self .var1
class ClassB(ClassA):
def __init__( self , class_a):
self .var1 = class_a.var1
self .var2 = class_a.var2
object1 = ClassA()
# updates the value of var1
summ = object1.methodA()
# return the value of var1
print (summ)
# passes object of classA
object2 = ClassB(object1)
# return the values carried by var1,var2
print ( object2.var1)
print (object2.var2)

3 3 2

Внимание компьютерщик! Укрепите свои основы с помощью базового курса программирования Python и изучите основы. Для начала подготовьтесь к собеседованию. Расширьте свои концепции структур данных с помощью курса Python DS. А чтобы начать свое путешествие по машинному обучению, присоединяйтесь к курсу Машинное обучение - базовый уровень.

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *