Как сделать очистку надежно в Python? - PullRequest
3 голосов
/ 25 июня 2009

У меня есть несколько привязок ctypes и для каждого тела. Теперь я должен вызвать body. Бесплатно. Библиотека, которую я связываю, не имеет процедур выделения, выделенных из остального кода (их можно вызывать где угодно), и для использования нескольких полезных функций мне нужно делать циклические ссылки.

Я думаю, это решило бы, если бы я нашел надежный способ привязать деструктор к объекту. (Слабые ссылки помогли бы, если бы они дали мне обратный вызов только за до , когда данные были отброшены.

Так что, очевидно, этот код не работает, когда я помещаю в speed_func:

class Body(object):
    def __init__(self, mass, inertia):
        self._body = body.New(mass, inertia)

    def __del__(self):
        print '__del__ %r' % self
        if body:
            body.Free(self._body)

    ...        

    def set_velocity_func(self, func):
        self._body.contents.velocity_func = ctypes_wrapping(func)

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

Даже если я не введу в speed_func, по крайней мере, когда я это сделаю, появятся циклы:

class Toy(object):
    def __init__(self, body):
        self.body.owner = self

...

def collision(a, b, contacts):
    whatever(a.body.owner)

Так как же убедиться, что Structures будет собирать мусор, даже если он выделяется / освобождается общей библиотекой?

Есть хранилище, если вас интересует более подробная информация: http://bitbucket.org/cheery/ctypes-chipmunk/

Ответы [ 3 ]

3 голосов
/ 25 июня 2009

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

Стандартным способом в Python является просто:

try:
    allocate()
    dostuff()
finally:
    cleanup()

Или, начиная с версии 2.5, вы также можете создавать контекстные менеджеры и использовать оператор with, что является более точным способом сделать это.

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

0 голосов
/ 25 июня 2009

В CPython __del__ является надежным деструктором объекта, потому что он всегда будет вызываться, когда счетчик ссылок достигнет нуля (примечание: могут быть случаи - например, циклические ссылки элементов с __del__ метод определен - когда счетчик ссылок никогда не достигнет нуля, но это другая проблема).

Обновление Из комментариев я понимаю, что проблема связана с порядком уничтожения объектов: тело является глобальным объектом, и он уничтожается раньше всех других объектов, поэтому он больше не доступен для них.
На самом деле, использование глобальных объектов нехорошо; не только из-за подобных проблем, но и из-за технического обслуживания.

Я бы тогда поменял ваш класс на что-то вроде этого

class Body(object):
    def __init__(self, mass, inertia):
        self._bodyref = body
        self._body = body.New(mass, inertia)

    def __del__(self):
        print '__del__ %r' % self
        if body:
            body.Free(self._body)

...        

def set_velocity_func(self, func):
    self._body.contents.velocity_func = ctypes_wrapping(func)

Пара заметок:

  1. Изменение только добавляет ссылку на глобальный body объект, который, таким образом, будет жить как минимум столько же, сколько все объекты, производные от этого класса.
  2. Тем не менее, использование глобального объекта не годится из-за модульного тестирования и обслуживания; Лучше было бы иметь фабрику для объекта, которая установит правильное «тело» для класса, и в случае модульного теста легко поместит фиктивный объект. Но это действительно ваше дело, и сколько усилий вы считаете целесообразным в этом проекте.
0 голосов
/ 25 июня 2009

Если слабые ссылки не сломаны, я думаю, это может сработать:

from weakref import ref

pointers = set()

class Pointer(object):
    def __init__(self, cfun, ptr):
        pointers.add(self)
        self.ref = ref(ptr, self.cleanup)
        self.data = cast(ptr, c_void_p).value # python cast it so smart, but it can't be smarter than this.
        self.cfun = cfun

    def cleanup(self, obj):
        print 'cleanup 0x%x' % self.data
        self.cfun(self.data)
        pointers.remove(self)

def cleanup(cfun, ptr):
    Pointer(cfun, ptr)

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

Редактировать: Попробовал, и, кажется, он работает после небольшой настройки моего кода. Удивительно то, что даже если я вытащил del из всех своих структур, он все равно потерпит неудачу. Интересно, но расстраивает.

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

Редактировать: Ну ... слабые стороны были сломаны в конце концов! так что, вероятно, нет никакого решения для надежной очистки в python, кроме как принудительное его явное выполнение.

...