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()
объект здесь; когда вы зацикливаетесь на них, вы получаете живые объекты; уже разыменовано с мертвыми ссылками, обрезанными для вас, без риска условий гонки.