Фильтрующая часть действительно хитрая. Используя простые приемы, вы можете легко заставить рассол работать. Однако, вы можете отфильтровать слишком много и потерять информацию, которую можете сохранить, когда фильтр выглядит немного глубже. Но огромная возможность того, что может закончиться в .namespace
, затрудняет создание хорошего фильтра.
Однако мы можем использовать части, которые уже являются частью Python, такие как deepcopy
в модуле copy
.
Я сделал копию стандартного copy
модуля и сделал следующие вещи:
- создайте новый тип с именем
LostObject
для представления объекта, который будет потерян при травлении.
- измените
_deepcopy_atomic
, чтобы убедиться, что x
можно отцепить. Если это не так, верните экземпляр LostObject
- объекты могут определять методы
__reduce__
и / или __reduce_ex__
, чтобы дать подсказку о том, нужно ли и как их использовать. Мы уверены, что эти методы не будут генерировать исключение, чтобы дать подсказку, что оно не может быть обработано.
- чтобы избежать создания ненужной копии большого объекта ( а-ля фактическая глубокая копия), мы рекурсивно проверяем, можно ли объект выбрать, и делаем только необратимую часть. Например, для кортежа из списка с возможностью выбора и без возможности выбора объекта мы сделаем копию кортежа - только контейнера - но не его списка членов.
Ниже приведен diff:
[~/Development/scratch/] $ diff -uN /System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/copy.py mcopy.py
--- /System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/copy.py 2010-01-09 00:18:38.000000000 -0800
+++ mcopy.py 2010-11-10 08:50:26.000000000 -0800
@@ -157,6 +157,13 @@
cls = type(x)
+ # if x is picklable, there is no need to make a new copy, just ref it
+ try:
+ dumps(x)
+ return x
+ except TypeError:
+ pass
+
copier = _deepcopy_dispatch.get(cls)
if copier:
y = copier(x, memo)
@@ -179,10 +186,18 @@
reductor = getattr(x, "__reduce_ex__", None)
if reductor:
rv = reductor(2)
+ try:
+ x.__reduce_ex__()
+ except TypeError:
+ rv = LostObject, tuple()
else:
reductor = getattr(x, "__reduce__", None)
if reductor:
rv = reductor()
+ try:
+ x.__reduce__()
+ except TypeError:
+ rv = LostObject, tuple()
else:
raise Error(
"un(deep)copyable object of type %s" % cls)
@@ -194,7 +209,12 @@
_deepcopy_dispatch = d = {}
+from pickle import dumps
+class LostObject(object): pass
def _deepcopy_atomic(x, memo):
+ try:
+ dumps(x)
+ except TypeError: return LostObject()
return x
d[type(None)] = _deepcopy_atomic
d[type(Ellipsis)] = _deepcopy_atomic
Теперь вернемся к травлению. Вы просто делаете глубокую копию с помощью этой новой функции deepcopy
и затем копируете копию. Необрабатываемые детали были удалены в процессе копирования.
x = dict(a=1)
xx = dict(x=x)
x['xx'] = xx
x['f'] = file('/tmp/1', 'w')
class List():
def __init__(self, *args, **kwargs):
print 'making a copy of a list'
self.data = list(*args, **kwargs)
x['large'] = List(range(1000))
# now x contains a loop and a unpickable file object
# the following line will throw
from pickle import dumps, loads
try:
dumps(x)
except TypeError:
print 'yes, it throws'
def check_picklable(x):
try:
dumps(x)
except TypeError:
return False
return True
class LostObject(object): pass
from mcopy import deepcopy
# though x has a big List object, this deepcopy will not make a new copy of it
c = deepcopy(x)
dumps(c)
cc = loads(dumps(c))
# check loop refrence
if cc['xx']['x'] == cc:
print 'yes, loop reference is preserved'
# check unpickable part
if isinstance(cc['f'], LostObject):
print 'unpicklable part is now an instance of LostObject'
# check large object
if loads(dumps(c))['large'].data[999] == x['large'].data[999]:
print 'large object is ok'
Вот вывод:
making a copy of a list
yes, it throws
yes, loop reference is preserved
unpicklable part is now an instance of LostObject
large object is ok
Вы видите, что 1) взаимные указатели (между x
и xx
) сохраняются, и мы не сталкиваемся с бесконечным циклом; 2) необрабатываемый файловый объект преобразуется в экземпляр LostObject
; и 3) не создается новая копия большого объекта, так как он может быть выбран.