Исследование модели памяти объектов первого класса в Python

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

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

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

Введение

В этом практическом занятии (лабораторной работе) вы узнаете о концепции объектов первого класса в Python и исследуете его модель памяти. Python рассматривает функции, типы и данные как объекты первого класса, что позволяет использовать мощные и гибкие шаблоны программирования.

Вы также создадите переиспользуемые вспомогательные функции для обработки данных в формате CSV. В частности, вы создадите обобщенную функцию для чтения данных CSV в файле reader.py, которая может быть использована в разных проектах.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/PythonStandardLibraryGroup(["Python Standard Library"]) python(("Python")) -.-> python/BasicConceptsGroup(["Basic Concepts"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ModulesandPackagesGroup(["Modules and Packages"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/FileHandlingGroup(["File Handling"]) python/BasicConceptsGroup -.-> python/strings("Strings") python/BasicConceptsGroup -.-> python/type_conversion("Type Conversion") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/scope("Scope") python/ModulesandPackagesGroup -.-> python/importing_modules("Importing Modules") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/FileHandlingGroup -.-> python/file_operations("File Operations") python/PythonStandardLibraryGroup -.-> python/data_collections("Data Collections") subgraph Lab Skills python/strings -.-> lab-132489{{"Исследование модели памяти объектов первого класса в Python"}} python/type_conversion -.-> lab-132489{{"Исследование модели памяти объектов первого класса в Python"}} python/function_definition -.-> lab-132489{{"Исследование модели памяти объектов первого класса в Python"}} python/scope -.-> lab-132489{{"Исследование модели памяти объектов первого класса в Python"}} python/importing_modules -.-> lab-132489{{"Исследование модели памяти объектов первого класса в Python"}} python/classes_objects -.-> lab-132489{{"Исследование модели памяти объектов первого класса в Python"}} python/file_operations -.-> lab-132489{{"Исследование модели памяти объектов первого класса в Python"}} python/data_collections -.-> lab-132489{{"Исследование модели памяти объектов первого класса в Python"}} end

Понимание объектов первого класса в Python

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

Исследование типов первого класса

Сначала запустим интерпретатор Python. Откройте новый терминал в WebIDE и введите следующую команду. Эта команда запустит интерпретатор Python, где мы будем запускать наш код на Python.

python3

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

coltypes = [str, int, float]

Обратите внимание, что мы создаем список, содержащий фактические функции типов, а не строки. В Python типы являются объектами первого класса, что означает, что мы можем обращаться с ними так же, как с любым другим объектом. Мы можем помещать их в списки, передавать их и использовать в нашем коде.

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

import csv
f = open('portfolio.csv')
rows = csv.reader(f)
headers = next(rows)
row = next(rows)
print(row)

При запуске этого кода вы должны увидеть вывод, похожий на следующий. Это первая строка данных из файла CSV, представленная в виде списка строк.

['AA', '100', '32.20']

Далее мы будем использовать функцию zip. Функция zip принимает несколько итерируемых объектов (например, списки или кортежи) и объединяет их элементы в пары. Мы будем использовать ее, чтобы соотнести каждое значение из строки с соответствующей функцией преобразования типа.

r = list(zip(coltypes, row))
print(r)

В результате получится следующий вывод. Каждая пара содержит функцию типа и строковое значение из файла CSV.

[(<class 'str'>, 'AA'), (<class 'int'>, '100'), (<class 'float'>, '32.20')]

Теперь, когда у нас есть эти пары, мы можем применить каждую функцию, чтобы преобразовать значения в соответствующие типы.

record = [func(val) for func, val in zip(coltypes, row)]
print(record)

В выводе будет показано, что значения были преобразованы в соответствующие типы. Строка 'AA' остается строкой, '100' становится целым числом 100, а '32.20' становится числом с плавающей точкой 32.2.

['AA', 100, 32.2]

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

record_dict = dict(zip(headers, record))
print(record_dict)

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

{'name': 'AA', 'shares': 100, 'price': 32.2}

Вы можете выполнить все эти шаги в одном выражении - генераторе. Генератор - это компактный способ создания списков, словарей или множеств в Python.

result = {name: func(val) for name, func, val in zip(headers, coltypes, row)}
print(result)

В выводе будет тот же словарь, что и раньше.

{'name': 'AA', 'shares': 100, 'price': 32.2}

Когда вы закончите работу в интерпретаторе Python, вы можете выйти из него, введя следующую команду.

exit()

Это демонстрация показывает, как отношение Python к функциям как к объектам первого класса позволяет использовать мощные методы обработки данных. Благодаря тому, что мы можем рассматривать типы и функции как объекты, мы можем писать более гибкий и компактный код.

Создание вспомогательной функции для обработки CSV

Теперь, когда мы понимаем, как объекты первого класса в Python могут помочь нам с преобразованием данных, мы создадим переиспользуемую вспомогательную функцию. Эта функция будет считывать данные из CSV-файла и преобразовывать их в список словарей. Это очень полезная операция, так как CSV-файлы обычно используются для хранения табличных данных, и преобразование их в список словарей упрощает работу с данными в Python.

Создание вспомогательной функции для чтения CSV

Сначала откройте WebIDE. После открытия перейдите в директорию проекта и создайте новый файл с именем reader.py. В этом файле мы определим функцию, которая считывает данные из CSV-файла и применяет преобразования типов. Преобразования типов важны, так как данные в CSV-файле обычно считываются как строки, но для дальнейшей обработки нам могут понадобиться другие типы данных, такие как целые числа или числа с плавающей точкой.

Добавьте следующий код в файл reader.py:

import csv

def read_csv_as_dicts(filename, types):
    """
    Read a CSV file into a list of dictionaries, converting each field according
    to the types provided.

    Parameters:
    filename (str): Name of the CSV file to read
    types (list): List of type conversion functions for each column

    Returns:
    list: List of dictionaries representing the CSV data
    """
    records = []
    with open(filename, 'r') as f:
        rows = csv.reader(f)
        headers = next(rows)  ## Get the column headers

        for row in rows:
            ## Apply type conversions to each value in the row
            converted_row = [func(val) for func, val in zip(types, row)]

            ## Create a dictionary mapping headers to converted values
            record = dict(zip(headers, converted_row))
            records.append(record)

    return records

Эта функция сначала открывает указанный CSV-файл. Затем она считывает заголовки CSV-файла, которые являются именами столбцов. После этого она проходит по каждой строке в файле. Для каждого значения в строке она применяет соответствующую функцию преобразования типа из списка types. Наконец, она создает словарь, в котором ключами являются заголовки столбцов, а значениями - преобразованные данные, и добавляет этот словарь в список records. После обработки всех строк функция возвращает список records.

Тестирование вспомогательной функции

Протестируем нашу вспомогательную функцию. Сначала откройте терминал и запустите интерпретатор Python, введя:

python3

Теперь, находясь в интерпретаторе Python, мы можем использовать нашу функцию для чтения данных о портфеле. Данные о портфеле хранятся в CSV-файле и содержат информацию о акциях, такую как название акции, количество акций и цена.

import reader
portfolio = reader.read_csv_as_dicts('portfolio.csv', [str, int, float])
for record in portfolio[:3]:  ## Show the first 3 records
    print(record)

При запуске этого кода вы должны увидеть вывод, похожий на следующий:

{'name': 'AA', 'shares': 100, 'price': 32.2}
{'name': 'IBM', 'shares': 50, 'price': 91.1}
{'name': 'CAT', 'shares': 150, 'price': 83.44}

Этот вывод показывает первые три записи из данных о портфеле с правильно преобразованными типами данных.

Попробуем также использовать нашу функцию с данными о автобусах CTA. Данные о автобусах CTA хранятся в другом CSV-файле и содержат информацию о маршрутах автобусов, датах, типах дней и количестве поездок.

rows = reader.read_csv_as_dicts('ctabus.csv', [str, str, str, int])
print(f"Total rows: {len(rows)}")
print("First row:", rows[0])

Вывод должен быть похожим на следующий:

Total rows: 577563
First row: {'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}

Это показывает, что наша функция может обрабатывать разные CSV-файлы и применять соответствующие преобразования типов.

Чтобы выйти из интерпретатора Python, введите:

exit()

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

✨ Проверить решение и практиковаться

Исследование модели памяти Python

Модель памяти Python играет важную роль в определении того, как объекты хранятся в памяти и как на них делаются ссылки. Понимание этой модели является обязательным, особенно при работе с большими наборами данных, так как это может существенно повлиять на производительность и использование памяти ваших программ на Python. В этом разделе мы сосредоточимся на том, как в Python обрабатываются строковые объекты, и рассмотрим способы оптимизации использования памяти для больших наборов данных.

Повторение строк в наборах данных

Данные о автобусах CTA содержат много повторяющихся значений, таких как названия маршрутов. Повторяющиеся значения в наборе данных могут привести к неэффективному использованию памяти, если их не обрабатывать правильно. Чтобы понять масштабы этой проблемы, давайте сначала проверим, сколько уникальных строк с названиями маршрутов содержится в наборе данных.

Сначала откройте интерпретатор Python. Вы можете сделать это, запустив следующую команду в терминале:

python3

После открытия интерпретатора Python мы загрузим данные о автобусах CTA и найдем уникальные маршруты. Вот код для этого:

import reader
rows = reader.read_csv_as_dicts('ctabus.csv', [str, str, str, int])

## Find unique route names
routes = {row['route'] for row in rows}
print(f"Number of unique route names: {len(routes)}")

В этом коде мы сначала импортируем модуль reader, который, предположительно, содержит функцию для чтения CSV-файлов в виде словарей. Затем мы используем функцию read_csv_as_dicts для загрузки данных из файла ctabus.csv. Второй аргумент [str, str, str, int] указывает типы данных для каждого столбца в CSV-файле. После этого мы используем генератор множества, чтобы найти все уникальные названия маршрутов в наборе данных и выводим количество уникальных названий маршрутов.

Вывод должен быть таким:

Number of unique route names: 181

Теперь проверим, сколько различных строковых объектов создается для этих маршрутов. Несмотря на то, что есть только 181 уникальное название маршрута, Python может создать новый строковый объект для каждого вхождения названия маршрута в наборе данных. Чтобы проверить это, мы используем функцию id() для получения уникального идентификатора каждого строкового объекта.

## Count unique string object IDs
routeids = {id(row['route']) for row in rows}
print(f"Number of unique route string objects: {len(routeids)}")

Вывод может вас удивить:

Number of unique route string objects: 542305

Это показывает, что есть только 181 уникальное название маршрута, но более 500 000 уникальных строковых объектов. Это происходит потому, что Python создает новый строковый объект для каждой строки, даже если значения одинаковы. Это может привести к значительному расходу памяти, особенно при работе с большими наборами данных.

Интернирование строк для экономии памяти

Python предоставляет способ "интернировать" (повторно использовать) строки с помощью функции sys.intern(). Интернирование строк может сэкономить память, если в вашем наборе данных есть много дубликатов строк. Когда вы интернируете строку, Python проверяет, существует ли идентичная строка в пуле интернированных строк. Если да, он возвращает ссылку на существующий строковый объект вместо создания нового.

Давайте продемонстрируем, как работает интернирование строк на простом примере:

import sys

## Without interning
a = 'hello world'
b = 'hello world'
print(f"a is b (without interning): {a is b}")

## With interning
a = sys.intern(a)
b = sys.intern(b)
print(f"a is b (with interning): {a is b}")

В этом коде мы сначала создаем две строковые переменные a и b с одинаковым значением без интернирования. Оператор is проверяет, ссылаются ли две переменные на один и тот же объект. Без интернирования a и b - разные объекты, поэтому a is b возвращает False. Затем мы интернируем обе строки с помощью sys.intern(). После интернирования a и b ссылаются на один и тот же объект в пуле интернированных строк, поэтому a is b возвращает True.

Вывод должен быть таким:

a is b (without interning): False
a is b (with interning): True

Теперь давайте используем интернирование строк при чтении данных о автобусах CTA, чтобы уменьшить использование памяти. Мы также используем модуль tracemalloc для отслеживания использования памяти до и после интернирования.

import sys
import reader
import tracemalloc

## Start memory tracking
tracemalloc.start()

## Read data with interning for the route column
rows = reader.read_csv_as_dicts('ctabus.csv', [sys.intern, str, str, int])

## Check unique route objects again
routeids = {id(row['route']) for row in rows}
print(f"Number of unique route string objects (with interning): {len(routeids)}")

## Check memory usage
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current / 1024 / 1024:.2f} MB")
print(f"Peak memory usage: {peak / 1024 / 1024:.2f} MB")

В этом коде мы сначала запускаем отслеживание памяти с помощью tracemalloc.start(). Затем мы читаем данные о автобусах CTA с интернированием для столбца маршрута, передав sys.intern в качестве типа данных для первого столбца. После этого мы снова проверяем количество уникальных строковых объектов маршрутов и выводим текущее и пиковое использование памяти.

Вывод должен быть похожим на следующий:

Number of unique route string objects (with interning): 181
Current memory usage: 189.56 MB
Peak memory usage: 209.32 MB

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

exit()

Запустите Python снова:

python3
import sys
import reader
import tracemalloc

## Start memory tracking
tracemalloc.start()

## Read data with interning for both route and date columns
rows = reader.read_csv_as_dicts('ctabus.csv', [sys.intern, sys.intern, str, int])

## Check memory usage
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage (interning route and date): {current / 1024 / 1024:.2f} MB")
print(f"Peak memory usage (interning route and date): {peak / 1024 / 1024:.2f} MB")

Вывод должен показать дальнейшее уменьшение использования памяти:

Current memory usage (interning route and date): 170.23 MB
Peak memory usage (interning route and date): 190.05 MB

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

Наконец, выйдите из интерпретатора Python:

exit()

Столбцовое хранение данных

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

Создание считывателя столбцовых данных

Теперь мы создадим новый файл, который поможет нам считывать данные из CSV-файла в столбцовом формате. Создайте новый файл с именем colreader.py в директории проекта со следующим кодом:

import csv

class DataCollection:
    def __init__(self, headers, columns):
        """
        Initialize a column-oriented data collection.

        Parameters:
        headers (list): Column header names
        columns (dict): Dictionary mapping header names to column data lists
        """
        self.headers = headers
        self.columns = columns
        self._length = len(columns[headers[0]]) if headers else 0

    def __len__(self):
        """Return the number of rows in the collection."""
        return self._length

    def __getitem__(self, index):
        """
        Get a row by index, presented as a dictionary.

        Parameters:
        index (int): Row index

        Returns:
        dict: Dictionary representing the row at the given index
        """
        if isinstance(index, int):
            if index < 0 or index >= self._length:
                raise IndexError("Index out of range")

            return {header: self.columns[header][index] for header in self.headers}
        else:
            raise TypeError("Index must be an integer")

def read_csv_as_columns(filename, types):
    """
    Read a CSV file into a column-oriented data structure, converting each field
    according to the types provided.

    Parameters:
    filename (str): Name of the CSV file to read
    types (list): List of type conversion functions for each column

    Returns:
    DataCollection: Column-oriented data collection representing the CSV data
    """
    with open(filename, 'r') as f:
        rows = csv.reader(f)
        headers = next(rows)  ## Get the column headers

        ## Initialize columns
        columns = {header: [] for header in headers}

        ## Read data into columns
        for row in rows:
            ## Convert values according to the specified types
            converted_values = [func(val) for func, val in zip(types, row)]

            ## Add each value to its corresponding column
            for header, value in zip(headers, converted_values):
                columns[header].append(value)

    return DataCollection(headers, columns)

Этот код выполняет две важные задачи:

  1. Он определяет класс DataCollection. Этот класс хранит данные в столбцах, но позволяет нам обращаться к данным, как если бы они были списком словарей, представляющих строки. Это полезно, так как предоставляет знакомый способ работы с данными.
  2. Он определяет функцию read_csv_as_columns. Эта функция считывает данные из CSV-файла и хранит их в столбцовой структуре. Она также преобразует каждое поле в CSV-файле в соответствии с типами, которые мы предоставляем.

Тестирование столбцового считывателя

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

python3

После открытия интерпретатора Python запустите следующий код:

import colreader
import tracemalloc
from sys import intern

## Start memory tracking
tracemalloc.start()

## Read data into column-oriented structure with string interning
data = colreader.read_csv_as_columns('ctabus.csv', [intern, intern, intern, int])

## Check that we can access the data like a list of dictionaries
print(f"Number of rows: {len(data)}")
print("First 3 rows:")
for i in range(3):
    print(data[i])

## Check memory usage
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current / 1024 / 1024:.2f} MB")
print(f"Peak memory usage: {peak / 1024 / 1024:.2f} MB")

Вывод должен выглядеть так:

Number of rows: 577563
First 3 rows:
{'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}
{'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288}
{'route': '6', 'date': '01/01/2001', 'daytype': 'U', 'rides': 6048}
Current memory usage: 38.67 MB
Peak memory usage: 103.42 MB

Теперь сравним это с нашим предыдущим строковым подходом. Запустите следующий код в том же интерпретаторе Python:

import reader
import tracemalloc
from sys import intern

## Reset memory tracking
tracemalloc.reset_peak()

## Read data into row-oriented structure with string interning
rows = reader.read_csv_as_dicts('ctabus.csv', [intern, intern, intern, int])

## Check memory usage
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage (row-oriented): {current / 1024 / 1024:.2f} MB")
print(f"Peak memory usage (row-oriented): {peak / 1024 / 1024:.2f} MB")

Вывод должен быть похожим на следующий:

Current memory usage (row-oriented): 170.23 MB
Peak memory usage (row-oriented): 190.05 MB

Как вы можете видеть, столбцовый подход использует значительно меньше памяти!

Давайте также проверим, что мы все еще можем анализировать данные, как и раньше. Запустите следующий код:

## Find all unique routes in the column-oriented data
routes = {row['route'] for row in data}
print(f"Number of unique routes: {len(routes)}")

## Count rides per route (first 5)
from collections import defaultdict
route_rides = defaultdict(int)
for row in data:
    route_rides[row['route']] += row['rides']

## Show the top 5 routes by total rides
top_routes = sorted(route_rides.items(), key=lambda x: x[1], reverse=True)[:5]
print("Top 5 routes by total rides:")
for route, rides in top_routes:
    print(f"Route {route}: {rides:,} rides")

Вывод должен быть таким:

Number of unique routes: 181
Top 5 routes by total rides:
Route 9: 158,545,826 rides
Route 49: 129,872,910 rides
Route 77: 120,086,065 rides
Route 79: 109,348,708 rides
Route 4: 91,405,538 rides

Наконец, выйдите из интерпретатора Python, запустив следующую команду:

exit()

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

✨ Проверить решение и практиковаться

Резюме

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

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