1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-jackfrued-Python-100-Days

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
那些年我们踩过的那些坑.md 20 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 09.03.2025 08:53 637e172

Эти самые ловушки, в которые мы попадали годами

Ловушка 1 — ловушка целочисленного сравнения

В Python всё является объектом, включая целые числа. При сравнении двух целых чисел используются два оператора: == и is. Разница между ними заключается в следующем:

  • Оператор is сравнивает идентификаторы (id) двух объектов, то есть проверяет, указывают ли они на один и тот же адрес памяти.
  • Оператор == сравнивает значения самих объектов, при этом вызывается метод __eq__() объекта.

Зная различия между is и ==, рассмотрим следующий код, чтобы понять, какие ловушки могут возникнуть при сравнении целых чисел в Python:

def main():
    x = y = -1
    while True:
        x += 1
        y += 1
        if x is y:
            print(f'{x} is {y}')
        else:
            print(f'Внимание! {x} не равно {y}')
            break
            
    x = y = 0
    while True:
        x -= 1
        y -= 1
        if x is y:
            print(f'{x} is {y}')
        else:
            print(f'Внимание! {x} не равно {y}')
            break


if __name__ == '__main__':
    main()
```Результат выполнения части этого кода представлен ниже. Этот результат связан с оптимизацией производительности, которую Python применяет для часто используемых целых чисел. Для целых чисел Python кэширует некоторые часто используемые объекты целых чисел в списке `small_ints`. В течение всего жизненного цикла программы любое использование этих целых чисел будет ссылаться на уже существующие объекты в кэше, а не создавать новые. Объекты целых чисел в диапазоне [-5, 256] кэшируются, поэтому если требуется число из этого диапазона, используется ссылка на кэшированный объект вместо создания нового. Числа вне этого диапазона создаются каждый раз заново, даже если их значения совпадают.![Результат сравнения целых чисел](./res/int-is-comparation.png)

Конечно, это было бы слишком просто, если бы всё ограничивалось этим. Если вы понимаете вышеописанные правила, давайте рассмотрим ещё одну часть кода.

```python
import dis
a = 257


def main():
    b = 257  # 6-я строка
    c = 257  # 7-я строка
    print(b is c)  # True
    print(a is b)  # False
    print(a is c)  # False
``````markdown
Результат выполнения программы уже был указан в комментариях к коду. Вот такая ловушка! На первый взгляд значения переменных `a`, `b` и `c` выглядят одинаковыми, но результат операции `is` отличается. Почему такое поведение? Давайте сначала поговорим о блоках кода в программе Python. Блок кода  это минимальная единица исполнения программы, которая может быть модулем файла, телом функции, классом или одиночной командой в интерактивном режиме. Вышеуказанный код состоит из двух блоков: `a = 257` является одним блоком, а функция `main`  другим. Внутри Python для повышения производительности все целочисленные объекты, созданные внутри одного блока кода, если их значение находится вне диапазона кэширования `small_ints`, но уже существует другой целочисленный объект с таким же значением в том же блоке кода, будут ссылаться на этот объект, а не создавать новый. Однако данное правило не применимо к отрицательным числам за пределами диапазона `small_ints`, а также к отрицательным числам с плавающей запятой. Но для неположительных чисел с плавающей запятой и строк это правило действует. Поэтому `b is c` возвращает `True`, а `a` и `b` находятся в разных блоках кода, хотя их значения равны 257, они представляют собой два различных объекта, поэтому результат операции `is` естественно будет `False`.
```Чтобы проверить вышеупомянутое заключение, мы можем использовать модуль `dis` (название говорит само за себя — это модуль для декомпиляции). Если вы не знакомы с понятием байткода, рекомендуется прочитать статью ["О чем говорится: работа Python программы"](http://www.cnblogs.com/restran/p/4903056.html). Сначала импортируйте модуль `dis` командой `import dis` и измените код следующим образом:

```python
import dis

def main():
    a = 257
    b = 257
    c = 257
    
    print(a is b)
    print(b is c)

if __name__ == "__main__":
    dis.dis(main)

Этот код позволит вам получить представление о внутреннем устройстве вашего кода на уровне байткода. Код выполняется следующим образом. Как видно, строки 6 и 7 кода, то есть значения 257 в функции main, загружены из одного и того же места, поэтому это один и тот же объект; а значение a в строке 9 явно загружено из разных мест, поэтому это разные объекты.

Если вы хотите углубиться в этот вопрос, рекомендую прочитать статью "Причины реализации целочисленных объектов Python".

Подводные камни №2 — подводные камни с вложенными спискамиВ Python существует встроенный тип данных, называемый списком, который является контейнером и может содержать другие объекты (то есть, ссылки на другие объекты). Элементы списка могут быть другими списками, что позволяет создавать вложенные списки. Вложенные списки можно использовать для моделирования таблиц, матриц, карт в играх (например, сада в игре «Zombi против растений») и шахматных досок (например, международной шахты и игры в пентагонале). Однако при использовании вложенных списков следует быть осторожным, так как это может привести к нежелательным последствиям. Вот пример:```python

def main(): names = ['Гуанью', 'Чжанфэй', 'Жоу-юань', 'Маошжао', 'Хуаньцзун'] subjects = ['китайский', 'математика', 'английский'] scores = [[0] * 3] * 5 for row, name in enumerate(names): print('Введите баллы для {}.'.format(name)) for col, subject in enumerate(subjects): scores[row][col] = float(input('{}: '.format(subject))) print(scores)

if name == 'main': main() ```Мы хотим ввести оценки за три предмета для пяти студентов, поэтому мы определяем список из пяти элементов, каждый из которых представляет собой список из трёх элементов. Этот двумерный список соответствует таблице размером 5×3, где каждое значение представляет собой оценку студента за конкретный предмет. Мы используем вложенные циклы for-in для ввода оценок каждого студента за все три предмета. После выполнения программы мы замечаем, что оценки всех студентов за все три предмета совпадают и равны оценкам последнего введённого студента. Чтобы заполнить этот пробел в знаниях, нам сначала следует различить понятия объекта и ссылки на объект. Для этого важно понять концепцию стека и кучи в памяти компьютера. Часто можно услышать слово "стек", но на самом деле "куча" и "стек" — это два разных понятия. Всем известно, что при выполнении программы требуется некоторое количество памяти для хранения данных и кода. Логически эту память можно разделить на несколько частей. Программисты, имеющие опыт работы с низкоуровневыми языками программирования, как C, знают, что доступная программа память логически может быть разделена на пять частей: стек (stack), куча (heap), секция данных (data segment), секция статических данных (static area) и секция кода (code segment).Стек используется для хранения локальных и временных переменных, а также данных, необходимых для сохранения состояния перед вызовом функции и восстановления после её завершения. Эта часть памяти автоматически выделяется при начале выполнения блока кода и освобождается после его завершения; управление ею обычно осуществляется компилятором. Размер кучи не фиксирован и может динамически распределяться и освобождаться. Поэтому если программа работает с большим количеством данных, эти данные обычно хранятся в куче. Неправильное освобождение памяти в куче может привести к утечкам памяти. Языки программирования, такие как Python и Java, используют механизмы сборки мусора для автоматического управления памятью (автоматическое освобождение не используемых участков кучи).Итак, в следующем коде переменная a не является настоящим объектом; она представляет собой ссылку на объект, которая фактически указывает на адрес объекта в куче. Через этот адрес мы можем получить доступ к объекту. Аналогично, переменная `b` является ссылкой на контейнер списка, который сам ссылается на список в куче. Сам список не содержит реальных объектов; он содержит лишь ссылки на объекты. Известно это, мы можем вернуться назад и еще раз взглянуть на нашу программу. Когда мы выполняли операцию `[[0] * 3] * 5`, то просто копировали адрес списка `[0, 0, 0]`, а не создавали новые объекты списков. Поэтому, хотя в контейнере было 5 элементов, эти 5 элементов ссылались на один и тот же список. Это можно проверить с помощью функции `id` для `scores[0]` и `scores[1]`. Таким образом, правильный код следует модифицировать следующим образом.

def main():
    names = ['Guanyu', 'Zhangfei', 'Zhongyue', 'Mashou', 'Huangzhong']
    subjects = ['Chinese', 'Mathematics', 'English']
    scores = [[]] * 5
    for row, name in enumerate(names):
        print(f'Введите оценки для {name}')
        scores[row] = [0] * 3
        for col, subject in enumerate(subjects):
            scores[row][col] = float(input(subject + ': '))
    print(scores)
    
if __name__ == '__main__':
    main()

или

def main():
    names = ['Guanyu', 'Zhangfei', 'Zhongyue', 'Mashou', 'Huangzhong']
    subjects = ['Chinese', 'Mathematics', 'English']
    scores = [[0] * 3 for _ in range(5)]
    for row, name in enumerate(names):
        print(f'Введите оценки для {name}')
        scores[row] = [0] * 3
        for col, subject in enumerate(subjects):
            scores[row][col] = float(input(subject + ': '))
    print(scores)
``````markdown
if __name__ == '__main__':
    main()

Если вы не уверены в использовании памяти, вы можете использовать функцию визуализации выполнения кода на сайте PythonTutor. Визуализация выполнения поможет вам понять, как распределяется память, что позволит избежать проблем при работе с вложенными списками или копированием объектов.

Python Tutor Visualization

Python Tutor Visualization 2

Поддержка 3 - Поддержка с доступом к модификаторам

Люди, работавшие с ООП на Python, знают, что язык предоставляет два уровня доступа к членам класса — открытый и закрытый (добавление двойного подчеркивания перед свойствами или методами). Однако те, кто привык к Java или C#, могут столкнуться с тем, что в Python нет строгого обеспечения конфиденциальности закрытых членов. Это потому, что Python просто меняет имя для так называемых закрытых членов класса; если известна эта схема изменения имени, то можно получить доступ к этим членам. Посмотрите на следующий пример кода.

class Student(object):

    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def __str__(self):
        return self.__name + ': ' + str(self.__age)


def main():
    stu = Student('骆昊', 38)
    # 'Student' object has no attribute '__name'
    # print(stu.__name)
    # Можно всё равно получить доступ к приватным членам класса следующим образом
    print(stu._Student__name)
    print(stu._Student__age)
``````python
if __name__ == '__main__':
    main()

Почему Python использует такие правила? Это можно объяснить популярной цитатой: "We are all consenting adults here" (мы все взрослые люди здесь). Эта фраза отражает общую позицию многих Python-программистов, что открытость лучше, чем закрытость, и мы должны сами контролировать доступ к данным или методам, а не ограничивать это на уровне языка.

Поэтому в Python нет необходимости использовать двойное подчеркивание (__) перед названием свойства или метода для того чтобы сделать его приватным. Такое использование не имеет практического смысла. Если вы хотите защитить свойства или методы, рекомендуется использовать одинарное подчеркивание (_). Хотя это тоже не обеспечивает полной защиты, но служит сигналом для вызывающего кода, что эти свойства или методы следует обращаться с осторожностью, и при этом не препятствуют наследованию этих свойств или методов в подклассах.

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

Для удобства восприятия:Магические методы класса Python, такие как __str__ и __repr__, не являются приватными членами, даже если они начинаются с двойного подчеркивания. Эти методы завершаются двойным подчеркиванием, что делает их не приватными членами. Это может быть источником путаницы для новичков.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/mirrors-jackfrued-Python-100-Days.git
git@api.gitlife.ru:oschina-mirror/mirrors-jackfrued-Python-100-Days.git
oschina-mirror
mirrors-jackfrued-Python-100-Days
mirrors-jackfrued-Python-100-Days
master