Подтвердить что ты не робот

RAII в Python - автоматическое разрушение при выходе из области

Я пытался найти RAII в Python. Распределение ресурсов - Инициализация - это шаблон в C++, посредством которого объект инициализируется при его создании. Если это терпит неудачу, то это вызывает исключение. Таким образом, программист знает, что объект никогда не будет оставлен в полусозданном состоянии. Python может сделать это много.

Но RAII также работает с правилами области видимости C++, чтобы обеспечить быстрое уничтожение объекта. Как только переменная выскакивает из стека, она уничтожается. Это может произойти в Python, но только если нет внешних или циклических ссылок.

Что еще более важно, имя объекта все еще существует до тех пор, пока не завершится функция, в которой он находится (а иногда и дольше). Переменные на уровне модуля будут сохраняться на протяжении всего срока службы модуля.

Я хотел бы получить ошибку, если я сделаю что-то вроде этого:

for x in some_list:
    ...

... 100 lines later ...

for i in x:
    # Oops! Forgot to define x first, but... where my error?
    ...

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

И я бы хотел, чтобы в этом случае было "Что я имею в виду":

for x in some_list:
    surface = x.getSurface()
    new_points = []
    for x,y,z in surface.points:
        ...     # Do something with the points
        new_points.append( (x,y,z) )
    surface.points = new_points
    x.setSurface(surface)

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

В Python 2.5 есть оператор "with", но для этого необходимо, чтобы я явно вставил функции __enter__ и __exit__ и, как правило, он больше ориентирован на очистку ресурсов, таких как блокировки файлов и мьютексов, независимо от вектора выхода. Это не помогает с ограничением. Или я что-то упустил?

Я искал "Python RAII" и "Python scope", и я не смог найти ничего, что решало бы проблему напрямую и авторитетно. Я просмотрел все ПКП. Кажется, эта концепция не рассматривается в Python.

Я плохой человек, потому что я хочу иметь переменные области видимости в Python? Это слишком непиратонично?

Разве я не ворчу?

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

Мне лень, что я хочу, чтобы компилятор/интерпретатор ловил мои небрежные ошибки при повторном использовании переменных? Ну да, конечно, я ленивый, но я ленив в плохом смысле?

4b9b3361

Ответ 1

tl; dr RAII невозможно, вы смешиваете его с областью обзора в целом, и когда вы пропустите те дополнительные области, вы, вероятно, записываете плохой код.

Возможно, я не получу ваш вопрос (ы), или вы не получите некоторые очень важные вещи о Python... Во-первых, детерминированное уничтожение объекта, привязанное к сфере видимости, невозможно в сборнике мусора. Переменные в Python - это просто ссылки. Вы бы не хотели, чтобы malloc 'd chunk of memory был free' d, как только указатель, указывающий на него, выходит из области видимости, не так ли? Практическое исключение в некоторых случаях, если вам посчастливилось использовать подсчет ref - но ни один язык не настолько сумасшедший, чтобы установить точную реализацию в камне.

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

Что касается имен, существующих для остальной части вызова функции: вы можете удалить имя из текущей или глобальной области действия с помощью инструкции del. Однако это не имеет никакого отношения к ручному управлению памятью. Он просто удаляет ссылку. Это может или не может привести к тому, что объект, на который ссылаются, будет GC'd и не является точкой упражнения.

  • Если ваш код достаточно длинный, чтобы вызвать конфликты имен, вы должны написать меньшие функции. И используйте более описательные, менее вероятные имена. То же самое для вложенных циклов, перезаписывающих переменную итерации out loop: я еще не столкнулся с этой проблемой, поэтому, возможно, ваши имена недостаточно описательны или вы должны разделить эти циклы друг от друга?

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

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

Нет. Достойный лексический охват - это заслуга, не зависящая от динамической/статичности. По общему признанию, Python (2 - 3 довольно сильно исправил это) имеет слабые стороны в этом отношении, хотя они больше в сфере закрытий.

Но объяснить "почему": Python должен быть консервативным с тем, где он запускает новую область, потому что без объявления, говорящего иначе, присвоение имени делает его локальным для самой внутренней/текущей области. Так, например, если цикл for имел собственную область видимости, вы не могли бы легко изменять переменные за пределами цикла.

Я ленив из-за того, что вы хотите, чтобы компилятор/интерпретатор поймал мои ошибки небрежного использования повторного использования переменной? Ну да, конечно, я ленив, но я ленив плохой способ?

Опять же, я полагаю, что повторное использование имени (таким образом, которое вводит ошибки или подводные камни) редки и все равно.

Изменить: Чтобы сформулировать это как можно более четко:

  • Невозможно очистить на основе стека на языке с использованием GC. Это просто не возможно, по определению: переменная является одной из потенциально многих ссылок на объекты в куче, которые ни знают, ни заботиться о том, когда переменные выходят за рамки, и все управление памятью находится в руках GC, который работает, когда ему нравится, а не когда выставляется стек стека. Очистка ресурсов решается по-разному, см. Ниже.
  • Детерминированная очистка происходит с помощью инструкции with. Да, она не вводит новую область (см. ниже), потому что это не то, для чего она предназначена. Не имеет значения, что он удаляет имя, с которым привязан управляемый объект, не удаляется. Тем не менее, очистка происходила, но остается объект "Не трогайте меня, я непригодный" (например, закрытый поток файлов).
  • Python имеет область действия для каждой функции, класса и модуля. Период.. Как работает язык, нравится вам это или нет. Если вы хотите/ "нуждаетесь" в более мелкозернистом охвате, переведите код в более мелкозернистые функции. Возможно, вам понадобится более мелкомасштабная область обзора, но нет - и по причинам, указанным ранее в этом ответе (три абзаца выше "Изменить:" ), есть причины для этого. Нравится вам это или нет, но так работает язык.

Ответ 2

  • Вы правы в отношении with - он полностью не связан с изменением переменных.

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

  • Основным инструментом скрыть состояние в Python являются классы.

  • В выражениях генератора (и в Python 3 также перечислены общие понятия) есть своя область.

  • Если ваши функции достаточно длинны, чтобы вы могли потерять информацию о локальных переменных, вам следует, вероятно, реорганизовать ваш код.

Ответ 3

Но RAII также работает с областью обзора правила С++ для обеспечения подсказки разрушение объекта.

Это считается несущественным в языках GC, которые основаны на идее, что память fungible. Нет необходимости надавливать память объекта, если в других местах достаточно памяти для размещения новых объектов. Невоспроизводимые ресурсы, такие как дескрипторы файлов, сокеты и мьютексы, считаются особым случаем, который должен быть рассмотрен специально (например, with). Это контрастирует с моделью С++, которая обрабатывает все ресурсы одинаково.

Как только переменная выскочит стек уничтожен.

У Python нет переменных стека. В терминах С++ все равно shared_ptr.

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

Он также просматривает уровень понимания генератора (и в 3.x во всех смыслах).

Если вы не хотите сжимать переменные цикла for, не используйте так много циклов for. В частности, он не-Pythonic использует append в цикле. Вместо:

new_points = []
for x,y,z in surface.points:
    ...     # Do something with the points
    new_points.append( (x,y,z) )

записи:

new_points = [do_something_with(x, y, z) for (x, y, z) in surface.points]

или

# Can be used in Python 2.4-2.7 to reduce scope of variables.
new_points = list(do_something_with(x, y, z) for (x, y, z) in surface.points)

Ответ 4

В основном вы, вероятно, используете неправильный язык. Если вы хотите, чтобы правила здравого смысла и надежное уничтожение, то придерживайтесь С++ или попробуйте Perl. Дискуссия GC о том, когда выпущена память, по-видимому, не увенчалась успехом. Это о выпуске других ресурсов, таких как мьютексы и дескрипторы файлов. Я считаю, что С# делает различие между деструктором, который вызывается, когда счетчик ссылок идет на ноль и когда он решает переработать память. Люди не заботятся об утилизации памяти, но хотят знать, как только ее больше не ссылают. Жаль, что у Питона был реальный потенциал как язык. Но это нетрадиционные прицелы и ненадежные деструкторы (или, по крайней мере, зависимые от реализации) означает, что вам отказывают в силе, которую вы получаете с С++ и Perl.

Интересно, что комментарий сделан только о том, чтобы использовать новую память, если она доступна, а не утилизировать старые в GC. Разве это не просто причудливый способ сказать, что он утечки памяти: -)

Ответ 5

При переходе на Python после нескольких лет C++ я обнаружил соблазн полагаться на __del__ для имитации поведения RAII-типа, например, для закрытия файлов или соединений. Тем не менее, существуют ситуации (например, шаблон наблюдателя, реализованный в Rx), когда наблюдаемая вещь поддерживает ссылку на ваш объект, сохраняя его живым! Поэтому, если вы хотите закрыть соединение до того, как оно будет __del__ источником, вы не __del__ пытаясь сделать это в __del__.

В программировании пользовательского интерфейса возникает следующая ситуация:

class MyComponent(UiComponent):

    def add_view(self, model):
        view = TheView(model) # observes model
        self.children.append(view)

    def remove_view(self, index):
        del self.children[index] # model keeps the child alive

Итак, вот способ получить поведение RAII-типа: создать контейнер с хуками add и remove:

import collections

class ScopedList(collections.abc.MutableSequence):

    def __init__(self, iterable=list(), add_hook=lambda i: None, del_hook=lambda i: None):
        self._items = list()
        self._add_hook = add_hook
        self._del_hook = del_hook
        self += iterable

    def __del__(self):
        del self[:]

    def __getitem__(self, index):
        return self._items[index]

    def __setitem__(self, index, item):
        self._del_hook(self._items[index])
        self._add_hook(item)
        self._items[index] = item

    def __delitem__(self, index):
        if isinstance(index, slice):
            for item in self._items[index]:
                self._del_hook(item)
        else:
            self._del_hook(self._items[index])
        del self._items[index]

    def __len__(self):
        return len(self._items)

    def __repr__(self):
        return "ScopedList({})".format(self._items)

    def insert(self, index, item):
        self._add_hook(item)
        self._items.insert(index, item)

Если UiComponent.children является ScopedList, который вызывает методы acquire и dispose UiComponent.children ScopedList, вы получаете ту же гарантию получения и удаления детерминированных ресурсов, что и в C++.