Python - как модерировать список слабых объектов прокси

Так weakref.proxy кажется, что объекты вообще не работают weakref.ref объекты, когда дело доходит до проверки, является ли ссылка живой, или "разыменовывает" их, или вообще-то, вообще-то, вообще-то.:П

Кажется, они все еще занимают свое место - например, ведение списка объектов, отвечающих на события. Поскольку не нужно разыменовывать его, чтобы вызывать методы для объектов, на которые они слабо ссылаются (в отличие от weakref.ref объекты, которые должны быть вызваны в первую очередь), можно получить некоторое время назад от использования proxy объекты за тысячи итераций. Тем не менее, кажется, что их сложнее идентифицировать как "мертвых" и в целом очистить, как только исчезнет объект, на который они ссылаются. Пример Par -

>>> mylist    #note the object at mylist[1] is already dead...
[<weakproxy at 0x10ccfe050 to A at 0x10ccf9f50>, <weakproxy at 0x10ccfe1b0 to NoneType at 0x10cb53538>]
>>> for i in mylist[:]:
...     try:
...             getattr(i, 'stuff')   #the second arg could be any sentinel;
...                                   #I just want to hit a ReferenceError
...     except AttributeError:
...             pass
...     except ReferenceError:
...             mylist.remove(i)
... 
Traceback (most recent call last):
  File "<stdin>", line 7, in <module>
ReferenceError: weakly-referenced object no longer exists

Так что он живет там, на него просто нельзя ссылаться напрямую или передавать как переменную, потому что это, очевидно, заставляет Python "разыменовать" его и работать с объектом, на который он слабо указывает. (Или что-то.)

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

for i, obj in enumerate(mylist):
    try:
        obj.__hash__
    except ReferenceError:
        del mylist[i]

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

К сожалению, это все, что я могу представить как потенциальное решение, которое не включает проверку типов или другой мусор, но я предполагаю, что я что-то пропустил в weakref документация о том, как обращаться weakref.proxy объекты соответственно. Такое ощущение, что мы хотим weakref.proxy не исключительные обстоятельства, поэтому использование try/except это разовая утилита.

Так что, если я прав в своем предположении об использовании try/except здесь, есть ли лучший способ для определения мертвых weakref.proxy объекты?

РЕДАКТИРОВАТЬ: Я принял ответ, так что спасибо вам за это - попробуйте / за исключением, кажется, единственный приемлемый выход.

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

>>> from weakref import WeakSet, proxy
>>> import cProfile
>>> class A(object):
...     def __init__(self):
...             self.x = 0
...     def foo(self, v=1):
...             self.x += v
... 
>>> Stick = A()
>>> Dave = A()
>>> Jupiter = A()
>>> ##just a list of objects, no fancy
>>> one_group = [Stick, Dave, Jupiter]
>>> cProfile.run("for i in xrange(0, 2**16): [x.foo(i) for x in one_group]")
         196610 function calls in 0.136 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   196608    0.049    0.000    0.049    0.000 <stdin>:4(foo)
        1    0.087    0.087    0.136    0.136 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


>>> Stick.x
2147450880
>>> Dave.x
2147450880
>>> Jupiter.x
2147450880

Итак, мы знаем, что это работает, и мы знаем, что это довольно быстро для 65 000 итераций. Кажется приличным; давайте посмотрим на WeakSets.

>>> ##now a WeakSet of objects. should be ideal; but...
>>> two_group = WeakSet((Stick, Dave, Jupiter))
>>> cProfile.run("for i in xrange(0, 2**16): [x.foo(i) for x in two_group]")
         851970 function calls in 0.545 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   196608    0.055    0.000    0.055    0.000 <stdin>:4(foo)
        1    0.158    0.158    0.545    0.545 <string>:1(<module>)
    65536    0.026    0.000    0.026    0.000 _weakrefset.py:16(__init__)
    65536    0.043    0.000    0.051    0.000 _weakrefset.py:20(__enter__)
    65536    0.063    0.000    0.095    0.000 _weakrefset.py:26(__exit__)
    65536    0.024    0.000    0.024    0.000 _weakrefset.py:52(_commit_removals)
   262144    0.159    0.000    0.331    0.000 _weakrefset.py:58(__iter__)
    65536    0.009    0.000    0.009    0.000 {method 'add' of 'set' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    65536    0.008    0.000    0.008    0.000 {method 'remove' of 'set' objects}

Преждевременная оптимизация? Неа.:) Это примерно в 4 раза медленнее. Вот это да.

>>> ##now finally, a list of proxy objects
>>> three_group = [proxy(x) for x in one_group]
>>> cProfile.run("for i in xrange(0, 2**16): [x.foo(i) for x in three_group]")
         196610 function calls in 0.139 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   196608    0.050    0.000    0.050    0.000 <stdin>:4(foo)
        1    0.089    0.089    0.139    0.139 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Основываясь на этих числах, я пришел к мысли, что самый простой и быстрый способ ведения списка объектов, который должен быть правильно отмечен как освобожденный, - это использование прокси-объектов. Я думаю, что стратегическое использование try/except как "очиститель списка" гарантирует, что мертвые прокси не вызовут ошибок. Для их удобства я еще могу вернуться к подходу, который использует weakref.ref объектов, но использование прокси-объектов интригует их, казалось бы, "прямым" доступом к объекту.

1 ответ

Решение

Ваш слабый эталонный объект поднимает ReferenceError Исключение снова на mylist.remove(i) строка, а не при зацикливании или доступе, скорее всего, потому что Python пытается использовать __eq__ метод по доверенности.

Ваш второй метод использует индекс и не вызывает это исключение, используйте его в первом цикле:

remove = set()
for index, proxy in enumerate(mylist):
    try:
        getattr(proxy, 'stuff')
    except AttributeError:
        pass
    except ReferenceError:
        remove.add(index)

mylist = [p for i, p in enumerate(mylist) if i not in remove]

Демо-версия:

>>> import weakref
>>> class Foo(object): pass
... 
>>> remove = set()
>>> mylist = [weakref.proxy(Foo())]  # instant dead proxy
>>> for index, proxy in enumerate(mylist):
...     try:
...         getattr(proxy, 'stuff')
...     except AttributeError:
...         pass
...     except ReferenceError:
...         remove.add(index)
... 
>>> remove
set([0])

Если вам конкретно нужна функция, которая проверяет наличие объекта, который еще жив, вы можете использовать:

def proxy_live(p)
    try:
        bool(p)
    except ReferenceError:
        return False
    return True

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

Если порядок не важен, я бы использовал weakref.WeakSet() объект здесь; когда вы зацикливаетесь на них, вы получаете живые объекты; уже разыменовано с мертвыми ссылками, обрезанными для вас, без риска условий гонки.

Другие вопросы по тегам