Введение
В этом разделе приводятся более подробные сведения о внутренней модели объектов Python и обсуждаются некоторые вопросы, связанные с управлением памятью, копированием и проверкой типов.
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
С большой властью приходит ответственность. Просто потому, что вы можете это сделать, не означает, что вы должны.
В этом наборе упражнений мы рассмотрим некоторые аспекты власти, которая присуща первоклассным объектам.
В файле 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]
>>>
Помните, как функция 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}
>>>
Используя методы из этого упражнения, вы можете написать инструкции, которые легко преобразуют поля из практически любого столбцового файла данных в 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, чтобы улучшить свои навыки.