Внутренности модели объектов Python

PythonPythonBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

В этом разделе приводятся более подробные сведения о внутренней модели объектов Python и обсуждаются некоторые вопросы, связанные с управлением памятью, копированием и проверкой типов.

Присваивание

Многие операции в Python связаны с присваиванием или хранением значений.

a = value         ## Присваивание переменной
s[n] = value      ## Присваивание элементу списка
s.append(value)   ## Добавление элемента в список
d['key'] = value  ## Добавление пары ключ-значение в словарь

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

Пример присваивания

Рассмотрим этот фрагмент кода.

a = [1,2,3]
b = a
c = [a,b]

Картина о происходящих в памяти операциях. В этом примере есть только один объект списка [1,2,3], но к нему есть четыре разных ссылки.

Диаграмма примеров ссылок в памяти

Это означает, что изменение значения влияет на все ссылки.

>>> a.append(999)
>>> a
[1,2,3,999]
>>> b
[1,2,3,999]
>>> c
[[1,2,3,999], [1,2,3,999]]
>>>

Заметьте, как изменение в исходном списке отображается везде (ужас!). Это происходит потому, что никаких копий не создавалось. Все указывает на то же самое.

Переприсваивание значений

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

a = [1,2,3]
b = a
a = [4,5,6]

print(a)      ## [4, 5, 6]
print(b)      ## [1, 2, 3]    Содержит исходное значение

Помните: Переменные - это имена, а не адреса в памяти.

Некоторые опасности

Если вы не знаете о этом разделении, вы рано или поздно пострадаете сами от своих действий. Типичная ситуация. Вы изменяете какие-то данные, полагаясь на то, что это ваша собственная приватная копия, и случайно портите какие-то другие данные в другой части программы.

Комментарий: Именно поэтому примитивные типы данных (int, float, string) являются неизменяемыми (только для чтения).

Идентичность и ссылки

Используйте оператор is, чтобы проверить, являются ли два значения одним и тем же объектом.

>>> a = [1,2,3]
>>> b = a
>>> a is b
True
>>>

is сравнивает идентификатор объекта (целое число). Идентификатор можно получить с помощью id().

>>> id(a)
3588944
>>> id(b)
3588944
>>>

Примечание: почти всегда лучше использовать == для проверки объектов. Поведение is часто является неожиданным:

>>> a = [1,2,3]
>>> b = a
>>> c = [1,2,3]
>>> a is b
True
>>> a is c
False
>>> a == c
True
>>>

Поверхностные копии

Списки и словари имеют методы для копирования.

>>> a = [2,3,[100,101],4]
>>> b = list(a) ## Создаем копию
>>> a is b
False

Это новый список, но элементы списка общаются.

>>> a[2].append(102)
>>> b[2]
[100,101,102]
>>>
>>> a[2] is b[2]
True
>>>

Например, внутренний список [100, 101, 102] общается. Это называется поверхностной копией. Вот картинка.

Поверхностная копия

Глубокие копии

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

>>> a = [2,3,[100,101],4]
>>> import copy
>>> b = copy.deepcopy(a)
>>> a[2].append(102)
>>> b[2]
[100,101]
>>> a[2] is b[2]
False
>>>

Имена, значения, типы

Имена переменных не имеют типа. Это всего лишь имя. Однако значения имеют базовый тип.

>>> a = 42
>>> b = 'Hello World'
>>> type(a)
<type 'int'>
>>> type(b)
<type 'str'>

type() покажет вам, что это за значение. Имя типа обычно используется в качестве функции, которая создает или преобразует значение в этот тип.

Проверка типа

Как определить, является ли объект определенным типом.

if isinstance(a, list):
    print('a is a list')

Проверка на один из нескольких возможных типов.

if isinstance(a, (list,tuple)):
    print('a is a list or tuple')

*Внимание: не злоупотребляйте проверкой типов. Это может привести к чрезмерной сложности кода. Обычно вы будете делать это только в том случае, если это предотвратит распространенные ошибки других, использующих ваш код.

Все является объектом

Числа, строки, списки, функции, исключения, классы, экземпляры и т.д. все являются объектами. Это означает, что все объекты, которые можно назвать, могут быть переданы в качестве данных, помещены в контейнеры и т.д., без каких-либо ограничений. Нет особых видов объектов. Иногда говорят, что все объекты являются "первоклассными".

Простой пример:

>>> import math
>>> items = [abs, math, ValueError ]
>>> items
[<built-in function abs>,
  <module'math' (builtin)>,
  <type 'exceptions.ValueError'>]
>>> items[0](-45)
45
>>> items[1].sqrt(2)
1.4142135623730951
>>> try:
        x = int('not a number')
    except items[2]:
        print('Failed!')
Failed!
>>>

Здесь items - это список, содержащий функцию, модуль и исключение. Вы можете напрямую использовать элементы списка вместо исходных имен:

items[0](-45)       ## abs
items[1].sqrt(2)    ## math
except items[2]:    ## ValueError

С большой властью приходит ответственность. Просто потому, что вы можете это сделать, не означает, что вы должны.

В этом наборе упражнений мы рассмотрим некоторые аспекты власти, которая присуща первоклассным объектам.

Упражнение 2.24: Данные первого класса

В файле portfolio.csv мы читаем данные, организованные в столбцы, которые выглядят так:

name,shares,price
"AA",100,32.20
"IBM",50,91.10
...

В предыдущем коде мы использовали модуль csv для чтения файла, но все еще приходилось выполнять ручные преобразования типов. Например:

for row in rows:
    name   = row[0]
    shares = int(row[1])
    price  = float(row[2])

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

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

>>> types = [str, int, float]
>>>

Причина, по которой вы даже можете создать этот список, заключается в том, что все в Python является первоклассным. Поэтому, если вы хотите иметь список функций, это нормально. Элементы в списке, который вы создали, - это функции для преобразования значения x в заданный тип (например, str(x), int(x), float(x)).

Теперь прочитайте строку данных из вышеуказанного файла:

>>> import csv
>>> f = open('portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>>

Как уже отмечалось, эта строка недостаточна для выполнения вычислений, потому что типы данных неправильные. Например:

>>> row[1] * row[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type'str'
>>>

Однако, возможно, данные можно сопоставить с типами, которые вы указали в types. Например:

>>> types[1]
<type 'int'>
>>> row[1]
'100'
>>>

Попробуйте преобразовать одно из значений:

>>> types[1](row[1])     ## То же, что и int(row[1])
100
>>>

Попробуйте преобразовать другое значение:

>>> types[2](row[2])     ## То же, что и float(row[2])
32.2
>>>

Попробуйте выполнить вычисление с преобразованными значениями:

>>> types[1](row[1])*types[2](row[2])
3220.0000000000005
>>>

Сопоставьте типы столбцов с полями и посмотрите на результат:

>>> r = list(zip(types, row))
>>> r
[(<type'str'>, 'AA'), (<type 'int'>, '100'), (<type 'float'>,'32.20')]
>>>

Вы заметите, что это сопоставило преобразование типа с значением. Например, int сопоставлено со значением '100'.

Сопоставленный список полезен, если вы хотите выполнить преобразования для всех значений последовательно. Попробуйте это:

>>> converted = []
>>> for func, val in zip(types, row):
          converted.append(func(val))
...
>>> converted
['AA', 100, 32.2]
>>> converted[1] * converted[2]
3220.0000000000005
>>>

Убедитесь, что понимаете, что происходит в вышеприведенном коде. В цикле переменная func - это одна из функций преобразования типов (например, str, int и т.д.), а переменная val - это одно из значений, таких как 'AA', '100'. Выражение func(val) преобразует значение (что-то вроде приведения типа).

Вышеприведенный код можно сократить до одной списочной генерации.

>>> converted = [func(val) for func, val in zip(types, row)]
>>> converted
['AA', 100, 32.2]
>>>

Упражнение 2.25: Создание словарей

Помните, как функция dict() может легко создать словарь, если у вас есть последовательность имен ключей и значений? Давайте создадим словарь из имен столбцов:

>>> headers
['name','shares', 'price']
>>> converted
['AA', 100, 32.2]
>>> dict(zip(headers, converted))
{'price': 32.2, 'name': 'AA','shares': 100}
>>>

Конечно, если вы хорошо владеете списочными выражениями, вы можете выполнить всю конвертацию за один шаг, используя словарное включение:

>>> { name: func(val) for name, func, val in zip(headers, types, row) }
{'price': 32.2, 'name': 'AA','shares': 100}
>>>

Упражнение 2.26: Общая картина

Используя методы из этого упражнения, вы можете написать инструкции, которые легко преобразуют поля из практически любого столбцового файла данных в Python-словарь.

Для примера допустим, что вы читаете данные из другого файла с данными так:

>>> f = open('dowstocks.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> headers
['name', 'price', 'date', 'time', 'change', 'open', 'high', 'low', 'volume']
>>> row
['AA', '39.48', '6/11/2007', '9:36am', '-0.18', '39.67', '39.69', '39.45', '181800']
>>>

Преобразуем поля с использованием похожей хитрости:

>>> types = [str, float, str, str, float, float, float, float, int]
>>> converted = [func(val) for func, val in zip(types, row)]
>>> record = dict(zip(headers, converted))
>>> record
{'volume': 181800, 'name': 'AA', 'price': 39.48, 'high': 39.69,
'low': 39.45, 'time': '9:36am', 'date': '6/11/2007', 'open': 39.67,
'change': -0.18}
>>> record['name']
'AA'
>>> record['price']
39.48
>>>

Бонус: Как бы вы модифицировали этот пример, чтобы дополнительно распарсить запись date в кортеж, такой как (6, 11, 2007)?

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

Резюме

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