Атрибуты класса и переменные экземпляра класса в 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 без четко определенного и настраиваемого поведения атрибутов. Атрибуты в Python хранятся в магическом методе с именем __dict__ . Получить доступ к нему можно следующим образом:
class MyClass: class_attr = "Class" def __init__(self): self.instance_attr = "Instance" >>> my_object = MyClass() # атрибут экземпляра класса >>> my_object.__dict__ # # атрибут экземпляра класса >>> MyClass.__dict__['class_attr'] # 'Class' >>> my_object.class_attr 'Class' >>> my_object.instance_attr 'Instance'
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Пространство имен и область видимости в классах
- Определение классов
- Объект класса и конструктор класса
- Создание экземпляра класса
- Метод экземпляра класса
- Что такое метод класса и зачем нужен
- Что такое статический метод в классах Python и зачем нужен
- Атрибуты класса и переменные экземпляра класса
- Кэширование методов экземпляра декоратором lru_cache
- Закрытые/приватные методы и переменные класса Python
- Наследование классов
- Множественное наследование классов
- Абстрактные классы
- Перегрузка методов в классе Python
- Что такое миксины и как их использовать
- Класс Python как структура данных, подобная языку C
- Создание пользовательских типов данных
- Специальные (магические) методы класса Python
- Базовая настройка классов Python магическими методами
- Настройка доступа к атрибутам класса Python
- Дескриптор класса для чайников
- Протокол дескриптора класса
- Практический пример дескриптора
- Использование метода .__new__() в классах Python
- Специальный атрибут __slots__ класса Python
- Специальный метод __init_subclass__ класса Python
- Определение метаклассов metaclass
- Эмуляция контейнерных типов в классах Python
- Другие специальные методы класса
- Как Python ищет специальные методы в классах
- Шаблон проектирования Фабрика и его реализация
Функция dir() в Python, все атрибуты объекта
Функция dir() , вызванная без аргумента, возвращает список имен в текущей локальной области, а вызванная с аргументом попытается вернуть список допустимых атрибутов для указанного объекта.
Если объект имеет метод с именем __dir__() , этот метод будет вызван и вернет список атрибутов. Это позволяет объектам, реализующим пользовательскую функцию __getattr__() или __getattribute__() , настраивать способ представления своих атрибутов для функции dir().
Если объект не предоставляет метод __dir__() , то функция делает все возможное, чтобы собрать информацию из атрибута __dict__ объекта, если он определен, и из объекта типа. Результирующий список не обязательно является полным и может быть неточным, если объект имеет пользовательский __getattr__() .
Механизм dir() по умолчанию ведет себя по-разному с различными типами объектов, поскольку он пытается создать наиболее релевантную, а не полную информацию:
- Если объект является модулем, список будет содержать имена атрибутов модуля;
- Если объект является типом или классом, список будет содержать имена атрибутов данного объекта и его родителей (вычисляются рекурсивно).
- В других случаях список будет содержать имена атрибутов самого объекта, его класса, и классов-родителей (вычисляются рекурсивно).
Функция dir() в основном используется в интерактивном режиме интерпретатора. Она пытается предоставить интересный набор имен, а не строго определенный набор имен. Ее поведение может изменяться в разных версиях Python.
Примеры получения списка атрибутов объекта.
>>> x = 5 >>> dir() ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'x'] >>> import struct >>> dir() ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'struct', 'x'] >>> dir(struct) ['Struct', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_clearcache', 'calcsize', 'error', 'iter_unpack', 'pack', 'pack_into', 'unpack', 'unpack_from'] >>> class Shape: . def __dir__(self): . return ['area', 'perimeter', 'location'] >>> s = Shape() >>> dir(s) ['area', 'location', 'perimeter']
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Функция abs(), абсолютное значение числа
- Функция all(), все элементы True
- Функция any(), хотя бы один элемент True
- Функция ascii(), преобразует строку в ASCII
- Функция bin(), число в двоичную строку
- Класс bool(), логическое значение объекта
- Функция breakpoint(), отладчик кода
- Класс bytearray(), преобразует в массив байтов
- Класс bytes(), преобразует в строку байтов
- Функция callable(), проверяет можно ли вызвать объект
- Функция chr(), число в символ Юникода
- Класс classmethod, делает функцию методом класса
- Функция compile() компилирует блок кода Python
- Класс complex(), преобразует в комплексное число
- Функция delattr(), удаляет атрибут объекта
- Класс dict() создает словарь
- Функция dir(), все атрибуты объекта
- Функция divmod(), делит числа с остатком
- Функция enumerate(), счетчик элементов последовательности
- Функция eval(), выполняет строку-выражение с кодом
- Функция exec(), выполняет блок кода
- Функция filter(), фильтрует список по условию
- Класс float(), преобразует в вещественное число
- Функция format(), форматирует значение переменной
- Класс frozenset(), преобразует в неизменяемое множество
- Функция getattr(), значение атрибута по имени
- Функция globals(), переменные глобальной области
- Функция hasattr(), наличие атрибута объекта
- Функция hash(), хэш-значение объекта
- Функция help(), справка по любому объекту
- Функция hex(), число в шестнадцатеричную строку
- Функция id(), идентификатор объекта
- Функция input(), ввод данных с клавиатуры
- Класс int(), преобразует в тип int
- Функция isinstance(), принадлежность экземпляра к классу
- Функция issubclass(), проверяет наследование класса
- Функция iter(), создает итератор
- Функция len(), количество элементов объекта
- Класс list(), преобразовывает в список
- Функция locals(), переменные локальной области
- Функция map(), обработка последовательности без цикла
- Функция max(), максимальное значение элемента
- Класс memoryview(), ссылка на буфер обмена
- Функция min(), минимальное значение элемента
- Функция next(), следующий элемент итератора
- Класс object(), возвращает безликий объект
- Функция oct(), число в восьмеричную строку
- Функция open(), открывает файл на чтение/запись
- Функция ord(), число символа Unicode
- Функция pow(), возводит число в степень
- Функция print(), печатает объект
- Класс property(), метод класса как свойство
- Класс range(), генерирует арифметические последовательности
- Функция repr(), описание объекта
- Функция reversed(), разворачивает последовательность
- Функция round(), округляет число
- Класс set(), создает или преобразовывает в множество
- Функция setattr(), создает атрибут объекта
- Класс slice(), шаблон среза
- Функция sorted(), выполняет сортировку
- Декоратор staticmethod(), метод класса в статический метод
- Класс str(), преобразует объект в строку
- Функция sum(), сумма последовательности
- Функция super(), доступ к унаследованным методам
- Класс tuple(), создает или преобразует в кортеж
- Класс type(), возвращает тип объекта
- Функция vars(), словарь переменных объекта
- Функция zip(), объединить элементы в список кортежей
- Функция __import__(), находит и импортирует модуль
- Функция aiter(), создает асинхронный итератор
- Функция anext(), следующий элемент асинхронного итератора
Ответы на вопросы с PyObject
2. Как получить список всех публичных атрибутов объекта
В Python для обозначения protected атрибутов используют «_», для private — «__» перед названием переменной. Следовательно, для получения списка только публичных атрибутов, список все атрибутов нужно отфильтровать. Сделать это можно или с помощью списковых выражений (list comprehension):
print [arg for arg in dir(Foo) if not arg.startswith('_')]
или воспользоваться функцией filter:
print filter(lambda x: not x.startswith('_'), dir(Foo))
Как по мне, то первый вариант является более предпочтительным по причине большей читабельности.
3. Как получить список методов объекта
Поскольку функции и методы в Python являются объектами первого рода (вроде правильно написал?), то для проверки будем использовать функцию getattr, которая возвращает сам аттрибут объекта и функцию callable, которая и осуществляет проверку.
print [arg for arg in dir(Foo) if callable(getattr(Foo, arg))] # или print filter(lambda arg: callable(getattr(Foo, arg)), dir(Foo))
4. В какой «магической» переменной хранится содержимое help?
В атрибуте __doc__. В данную переменную заносится комментарий сразу после
объявления класса/метода/функции (см. тестовый класс).
print Foo.__doc__
Так же можно воспользоваться функцией help в интерактивном режиме:
>>> help(int)
5. Есть два кортежа, получить третий как конкатенацию первых двух
print (2, 5) + (4, 6)
6. Есть два кортежа, получить третий как объединение уникальных элементов первых двух кортежей
В данном задании я видел 2 подхода:
1. писать циклы для проверки вхождения элемента в кортежи
2. воспользоваться встроенным типом set (по сути — хеш), над которым можно применять логические операции.
Решение с использованием второго подхода (используется XOR):
print tuple(set((2, 3, 4)) ^ set((4, 5)))
7. Почему если в цикле меняется список, то используется for x in lst[:], что означает [:]?
[:] — обозначение среза в питоне. Про них можно почитать, например, тут. В кратце: [:] создает копию lst и изменения в первом никак не влияют на итерацию по исходным значениям.
8. Есть два списка одинаковой длины, в одном ключи, в другом значения. Составить словарь.
Будем использовать функцию zip, которая делает кортежи из пары значений и dict, которая создает словарь из переданных аргументов.
a = ('John', 'Peter') b = (1, 2) print dict(zip(a, b))
9. Есть два списка разной длины, в одном ключи, в другом значения. Составить словарь. Для ключей, для которых нет значений использовать None в качестве значения. Значения, для которых нет ключей игнорировать.
a = ('John', 'Peter', 'Daniel') b = (1, 2) print dict((len(a) > len(b)) and map(None, a, b) or zip(a, b)) # или вариант с if/else, введенный в Python 2.5 print dict(map(None, a, b) if (len(a) > len(b)) else zip(a, b))
В данном случае будем использовать функции zip, map. Особенностью zip является то, что возвращаемый результат ограничен самым коротким итерируемым. То есть это прекрасно подходит нам для случая, когда значений больше чем ключей. Во второй ветке python у map есть одна документированная особенность, а именно — если какое-либо из итерируемых значений короче других, оно дополняется с помощью None. Если вместо функции передано None, выполняется объединение и на выходе мы получаем те же кортежи.
Как вариант, можно рассмотреть использование функции itertools.izip_longest, которая была добавлена в 2.6.
10.Есть словарь. Инвертировать его. Т.е. пары ключ: значение поменять местами — значение: ключ.
a = print dict((val, key) for (key, val) in a.iteritems())
Как вариант — опять использовать функцию zip.
a = print dict(zip(a.itervalues(), a.iterkeys()))
P.S. прошу гуру подсказать, что правильней использовать в данном случае — zip или itertools.izip. Тоже самое относится и к values/itervalues.
11. Есть строка в юникоде, получить 8-битную строку в кодировке utf-8 и cp1251
Писал прямо во время написания статьи, но если правильно понял задание, то:
greeting = u'Привет' print greeting.encode('utf-8') print greeting.encode('cp1251')
12. Есть строка в кодировке cp1251, получить юникодную строку
Аналогично:
greeting = 'Привет' print greeting.decode('cp1251')
Функции
1. Написать функцию, которой можно передавать аргументы либо списком/кортежем, либо по одному. Функция производит суммирование всех аргументов.
Решение вижу следующим: итерация по переданному списку. В случае если элемент списка — имеет атрибут __len__, а значит является итерируемым, разворачиваем его и передаем в нашу функцию.
def sum(*args): """ Returns sum of the args. >>> sum(1) 1 >>> sum(2, 2) 4 >>> sum(1, 2, (3, 4)) 10 >>> sum(1, ()) 1 >>> sum(-1, 1.0) 0.0 """ result = 0 for arg in args: if hasattr(arg, '__len__'): result += sum(*arg) else: result += arg return result
2. Написать функцию-фабрику, которая будет возвращать функцию сложения с аргументом.
Так как функции являются объектами первого рода (если лажать, так по-крупному), то одними из вариантов их использования являются возврат их из других функций или методов и передача в качестве в аргументов. Суть сводится к тому, что мы должны вернуть функцию сложения, один из аргументов
которой задан при ее создании, а второй может варьироваться. Как я понимаю, это — замыкание — доступ к переменным, объявленным вне тела функции.
def addition_inner(val): """ >>> add5 = addition_inner(5) >>> add5(3) 8 >>> add1 = addition_inner(-1) >>> add1(3) 2 >>> add_str = addition_inner('s') >>> add_str(3) Traceback (most recent call last): . TypeError: unsupported operand type(s) for +: 'int' and 'str' """ def func(arg): return arg + val return func def addition_lambda(val): """ >>> add5 = addition_lambda(5) >>> add5(3) 8 >>> add1 = addition_lambda(-1) >>> add1(3) 2 >>> add_str = addition_lambda('s') >>> add_str(3) Traceback (most recent call last): . TypeError: unsupported operand type(s) for +: 'int' and 'str' """ return lambda x: x + val
3. Написать фабрику, аналогичную п.2, но возвращающей список таких функций
В данном случае есть смысл возвращать список функций, которые мы написали выше.
Выглядеть будет примерно так:
def additions(start, end): """ Returns list of addition functions >>> for addition in additions(0, 5): . print addition(1) 1 2 3 4 5 >>> print additions(5, 0) [] >>> print additions(0, 2) # doctest: +ELLIPSIS [, ] """ return [addition_inner(val) for val in range(start, end)]
4. Написать аналог map:
— первым аргументом идет либо функция, либо список функций
— вторым аргументом — список аргументов, которые будут переданы функциям
— полагается, что эти функции — функции одного аргумента
Данное задание я реализовал с использованием встроенного map (как вариант — заменен циклом). Так же, для проверки типа переданного значения использовал функцию isinstance и модуль collections, как аналог hasattr и магическому методу __len__.
def mymap(func, args): """ Applies list of functions for args. >>> add0 = addition_inner(0) >>> add1 = addition_inner(1) >>> add2 = addition_inner(2) >>> add5 = addition_inner(5) >>> print mymap(add5, [1, 2, 3]) [6, 7, 8] >>> print mymap([add0, add1, add2], [1, 2, 3]) [(1, 2, 3), (2, 3, 4), (3, 4, 5)] >>> print mymap([], []) [] >>> print mymap(sum, [(3, 4, 5, 6, (7,))]) [25] """ if isinstance(func, collections.Iterable): return [tuple(map(f, args)) for f in func] # или #return [tuple(f(arg) for arg in args) for f in func] else: return [func(arg) for arg in args]
Итераторы
Маленькое отступление. Я не видел особого смысла писать код итераторов, т.к. их код описан в документации к модулю itertools. Врядли я напишу лучше.
Итераторы основываются на генераторах, о которых есть прекрасная статья.
Модули
1. У нас есть импортированный модуль foo, как узнать физический путь файла, откуда он импортирован?
Путь хранится в аттрибуте __file__ модуля.
2. Из модуля foo вы импортируете модуль feedparser. Версия X feedparser’а есть в общесистемном каталоге site-packages, версия Y — рядом с модулем foo. Определена переменная окружения PYTHONPATH, и там тоже есть feedparser, версии Z. Какая версия будет использоваться?
Будет импортирована версия Y.
Согласно документации (6 раздел туториала), порядок импорта следующий:
1. директория рядом со скриптом, который был запущен
2. PYTHONPATH
3. системный каталог
3. Как посмотреть список каталогов, в которых Python ищет модули?
>>> import sys >>> sys.path
4. У вас есть модуль foo, внутри него импортируется модуль bar. Рядом с модулем foo есть файлы bar.py и bar/__init__.py Какой модуль будет использоваться.
Будет использован второй, т.е. пакет. Как я понял, происходит рекурсивный обход директорий, и пакеты импортируются первыми.
5. Что означает и для чего используется конструкция __name__ == ‘__main__’
Используется для определения был ли файл импортирован или запущен. Если мы его запускаем, то значение будет __main__, если импортируем — имя модуля.
Сегодня — все, спасибо.
P.S. если кому будет интересно — напишу свою версию ответов на последние 2 раздела.
Как вывести список атрибутов класса 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. Поэтому такой метод можно сделать статическим.