Введение
Система объектов Python в значительной мере основана на реализации, включающей словари. В этом разделе обсуждается это.
This tutorial is from open-source community. Access the source code
💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал
Система объектов Python в значительной мере основана на реализации, включающей словари. В этом разделе обсуждается это.
Помните, что словарь - это коллекция именованных значений.
stock = {
'name' : 'GOOG',
'shares' : 100,
'price' : 490.1
}
Словари обычно используются для простых структур данных. Однако, они используются для важнейших частей интерпретатора и могут быть самым важным типом данных в Python.
Внутри модуля словарь хранит все глобальные переменные и функции.
## foo.py
x = 42
def bar():
...
def spam():
...
Если вы просмотрите foo.__dict__
или globals()
, то увидите словарь.
{
'x' : 42,
'bar' : <function bar>,
'spam' : <function spam>
}
Пользовательские определяемые объекты также используют словари для данных экземпляра и классов. Фактически, вся система объектов представляет собой в основном дополнительный слой, который накладывается поверх словарей.
В словаре хранятся данные экземпляра, __dict__
.
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{'name' : 'GOOG','shares' : 100, 'price': 490.1 }
Вы заполняете этот словарь (и экземпляр), когда присваиваете значения self
.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Данные экземпляра, self.__dict__
, выглядят так:
{
'name': 'GOOG',
'shares': 100,
'price': 490.1
}
Каждый экземпляр имеет собственный приватный словарь.
s = Stock('GOOG', 100, 490.1) ## {'name' : 'GOOG','shares' : 100, 'price': 490.1 }
t = Stock('AAPL', 50, 123.45) ## {'name' : 'AAPL','shares' : 50, 'price': 123.45 }
Если вы создадите 100 экземпляров какого-то класса, будут созданы 100 словарей, хранящих данные.
В отдельном словаре хранятся также методы.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, nshares):
self.shares -= nshares
Словарь находится в Stock.__dict__
.
{
'cost': <function>,
'sell': <function>,
'__init__': <function>
}
Экземпляры и классы связаны между собой. Атрибут __class__
ссылается обратно на класс.
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{ 'name': 'GOOG','shares': 100, 'price': 490.1 }
>>> s.__class__
<class '__main__.Stock'>
>>>
Словарь экземпляра хранит данные, уникальные для каждого экземпляра, в то время как словарь класса хранит данные, общими для всех экземпляров.
Когда вы работаете с объектами, вы получаете доступ к данным и методам с использованием оператора .
.
x = obj.name ## Получение
obj.name = value ## Установка
del obj.name ## Удаление
Эти операции напрямую связаны с словарями, лежащими под капотом.
Операции, которые изменяют объект, обновляют соответствующий словарь.
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{ 'name':'GOOG','shares': 100, 'price': 490.1 }
>>> s.shares = 50 ## Установка
>>> s.date = '6/7/2007' ## Установка
>>> s.__dict__
{ 'name': 'GOOG','shares': 50, 'price': 490.1, 'date': '6/7/2007' }
>>> del s.shares ## Удаление
>>> s.__dict__
{ 'name': 'GOOG', 'price': 490.1, 'date': '6/7/2007' }
>>>
Предположим, что вы читаете атрибут у экземпляра.
x = obj.name
Атрибут может существовать в двух местах:
Оба словаря должны быть проверены. Во - первых, проверьте в локальном __dict__
. Если не найдено, посмотрите в __dict__
класса через __class__
.
>>> s = Stock(...)
>>> s.name
'GOOG'
>>> s.cost()
49010.0
>>>
Эта схема поиска объясняет, как члены класса общаются между всеми экземплярами.
Классы могут наследоваться от других классов.
class A(B, C):
...
Базовые классы хранятся в кортеже в каждом классе.
>>> A.__bases__
(<class '__main__.B'>, <class '__main__.C'>)
>>>
Это обеспечивает ссылку на родительские классы.
Логически процесс поиска атрибута следующий. Во - первых, проверьте в локальном __dict__
. Если не найдено, посмотрите в __dict__
класса. Если не найдено в классе, посмотрите в базовых классах через __bases__
. Однако, есть некоторые тонкости этого, о которых будет рассказано далее.
В иерархиях наследования атрибуты ищутся путём последовательного обхода дерева наследования вверх.
class A: pass
class B(A): pass
class C(A): pass
class D(B): pass
class E(D): pass
При одиночном наследовании существует единственный путь к вершине. Вы останавливаетесь при первом совпадении.
Python предварительно вычисляет цепочку наследования и хранит её в атрибуте MRO класса. Вы можете просмотреть его.
>>> E.__mro__
(<class '__main__.E'>, <class '__main__.D'>,
<class '__main__.B'>, <class '__main__.A'>,
<type 'object'>)
>>>
Эта цепочка называется порядком разрешения методов (Method Resolution Order). Чтобы найти атрибут, Python последовательно обходит MRO. Первый найденный атрибут выбирается.
При множественном наследовании нет единственного пути к вершине. Посмотрим на пример.
class A: pass
class B: pass
class C(A, B): pass
class D(B): pass
class E(C, D): pass
Что происходит, когда вы обращаетесь к атрибуту?
e = E()
e.attr
Производится процесс поиска атрибута, но какой порядок? Это проблема.
Python использует совместное множественное наследование, которое подчиняется некоторым правилам порядка классов.
MRO вычисляется путём сортировки всех классов в иерархии согласно этим правилам.
>>> E.__mro__
(
<class 'E'>,
<class 'C'>,
<class 'A'>,
<class 'D'>,
<class 'B'>,
<class 'object'>)
>>>
В основе лежит алгоритм, называемый "алгоритмом C3-линейной иерархии". Точные детали не важны, главное запомнить, что иерархия классов подчиняется тем же правилам порядка, что и при эвакуации из дома при пожаре - сначала дети, затем родители.
Рассмотрим два совершенно не связанных объекта:
class Dog:
def noise(self):
return 'Bark'
def chase(self):
return 'Chasing!'
class LoudDog(Dog):
def noise(self):
## Общий код с LoudBike (ниже)
return super().noise().upper()
И
class Bike:
def noise(self):
return 'On Your Left'
def pedal(self):
return 'Pedaling!'
class LoudBike(Bike):
def noise(self):
## Общий код с LoudDog (выше)
return super().noise().upper()
В реализации LoudDog.noise()
и LoudBike.noise()
есть общий код. На самом деле, код идентичен. Естественно, такой код неизбежно привлекает внимание программистов.
Паттерн Mixin представляет собой класс с фрагментом кода.
class Loud:
def noise(self):
return super().noise().upper()
Этот класс нельзя использовать в отдельности. Он "мешается" с другими классами путём наследования.
class LoudDog(Loud, Dog):
pass
class LoudBike(Loud, Bike):
pass
Загадочно, громкость теперь реализована всего один раз и повторно используется в двух совершенно не связанных классах. Этот трюк является одним из основных применений множественного наследования в Python.
super()
Всегда используйте super()
при переопределении методов.
class Loud:
def noise(self):
return super().noise().upper()
super()
делегирует вызов методу следующего класса в MRO.
Трудность заключается в том, что вы не знаете, какой это класс. Особенно это актуально, если используется множественное наследование.
Множественное наследование - это мощный инструмент. Помните, что с властью приходит ответственность. Фреймворки / библиотеки иногда используют его для реализации продвинутых функций, связанных с композицией компонентов. Теперь, забывайте, что это вы видели.
В разделе 4 вы определили класс Stock
, представляющий акционерноеholding. В этом упражнении мы будем использовать этот класс. Перезапустите интерпретатор и создайте несколько экземпляров:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> goog = Stock('GOOG',100,490.10)
>>> ibm = Stock('IBM',50, 91.23)
>>>
В интерактивной оболочке исследуйте внутренние словари двух экземпляров, которые вы создали:
>>> goog.__dict__
... посмотрите на вывод...
>>> ibm.__dict__
... посмотрите на вывод...
>>>
Попробуйте установить новый атрибут для одного из вышеупомянутых экземпляров:
>>> goog.date = '6/11/2007'
>>> goog.__dict__
... посмотрите на вывод...
>>> ibm.__dict__
... посмотрите на вывод...
>>>
В выводе выше вы заметите, что у экземпляра goog
есть атрибут date
, в то время как у экземпляра ibm
его нет. Важно отметить, что Python на самом деле не накладывает никаких ограничений на атрибуты. Например, атрибуты экземпляра не ограничиваются только теми, которые настраиваются в методе __init__()
.
Вместо установки атрибута попробуйте напрямую поместить новое значение в объект __dict__
:
>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>>
Здесь вы действительно замечаете тот факт, что экземпляр - это просто слой поверх словаря. Примечание: следует подчеркнуть, что прямой манипуляция словарем - это необычное действие - вы должны всегда писать свой код, используя синтаксис (.).
Определения, составляющие определение класса, общие для всех экземпляров этого класса. Обратите внимание, что все экземпляры имеют ссылку на их связанный класс:
>>> goog.__class__
... посмотрите на вывод...
>>> ibm.__class__
... посмотрите на вывод...
>>>
Попробуйте вызвать метод для экземпляров:
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5
>>>
Заметим, что имя 'cost' не определено ни в goog.__dict__
, ни в ibm.__dict__
. Вместо этого оно предоставляется словарем класса. Попробуйте это:
>>> Stock.__dict__['cost']
... посмотрите на вывод...
>>>
Попробуйте вызвать метод cost()
напрямую через словарь:
>>> Stock.__dict__['cost'](goog)
49010.0
>>> Stock.__dict__['cost'](ibm)
4561.5
>>>
Заметим, как вы вызываете функцию, определенную в определении класса, и как аргумент self
получает экземпляр.
Попробуйте добавить новый атрибут в класс Stock
:
>>> Stock.foo = 42
>>>
Заметим, как этот новый атрибут теперь появляется у всех экземпляров:
>>> goog.foo
42
>>> ibm.foo
42
>>>
Однако, заметим, что он не является частью словаря экземпляра:
>>> goog.__dict__
... посмотрите на вывод и заметьте, что нет атрибута 'foo'...
>>>
Причина, по которой вы можете получить доступ к атрибуту foo
у экземпляров, заключается в том, что Python всегда проверяет словарь класса, если он не может найти что-то у самого экземпляра.
Примечание: Эта часть упражнения иллюстрирует то, что известно как классная переменная. Предположим, например, у вас есть класс такого вида:
class Foo(object):
a = 13 ## Классная переменная
def __init__(self,b):
self.b = b ## Экземплярная переменная
В этом классе переменная a
, присвоенная в теле класса самой, является "классной переменной". Она общая для всех созданных экземпляров. Например:
>>> f = Foo(10)
>>> g = Foo(20)
>>> f.a ## Проверьте классную переменную (одинаковая для обоих экземпляров)
13
>>> g.a
13
>>> f.b ## Проверьте экземплярную переменную (отличается)
10
>>> g.b
20
>>> Foo.a = 42 ## Измените значение классной переменной
>>> f.a
42
>>> g.a
42
>>>
Некоторый тонкий аспект Python заключается в том, что вызов метода на самом деле включает два шага и что-то, что называется связанным методом. Например:
>>> s = goog.sell
>>> s
<bound method Stock.sell of Stock('GOOG', 100, 490.1)>
>>> s(25)
>>> goog.shares
75
>>>
Связанные методы на самом деле содержат все элементы, необходимые для вызова метода. Например, они хранят запись о функции, реализующей метод:
>>> s.__func__
<function sell at 0x10049af50>
>>>
Это то же значение, что и в словаре Stock
.
>>> Stock.__dict__['sell']
<function sell at 0x10049af50>
>>>
Связанные методы также запоминают экземпляр, который является аргументом self
.
>>> s.__self__
Stock('GOOG',75,490.1)
>>>
Когда вы вызываете функцию с использованием ()
, все элементы объединяются. Например, вызов s(25)
на самом деле делает это:
>>> s.__func__(s.__self__, 25) ## То же, что и s(25)
>>> goog.shares
50
>>>
Создайте новый класс, который наследуется от Stock
.
>>> class NewStock(Stock):
def yow(self):
print('Yow!')
>>> n = NewStock('ACME', 50, 123.45)
>>> n.cost()
6172.50
>>> n.yow()
Yow!
>>>
Наследование реализуется путём расширения процесса поиска атрибутов. Атрибут __bases__
содержит кортеж непосредственных родителей:
>>> NewStock.__bases__
(<class'stock.Stock'>,)
>>>
Атрибут __mro__
содержит кортеж всех родителей в порядке, в котором они будут искаться при поиске атрибутов.
>>> NewStock.__mro__
(<class '__main__.NewStock'>, <class'stock.Stock'>, <class 'object'>)
>>>
Вот, как метод cost()
экземпляра n
выше будет найден:
>>> for cls in n.__class__.__mro__:
if 'cost' in cls.__dict__:
break
>>> cls
<class '__main__.Stock'>
>>> cls.__dict__['cost']
<function cost at 0x101aed598>
>>>
Поздравляем! Вы завершили лабораторную работу "Повторение словарей". Вы можете выполнить больше лабораторных работ в LabEx, чтобы улучшить свои навыки.