You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If we make a copy.copy() (or a deepcopy()) of an object with a bindableProperty, the resulting copy's bindable attribute is not really bindable anymore (until its value is reassigned manually).
The copy module techniques do not invoke __setattr__() at any point, so the BindableProperty.__set__() method never gets to update binding.bindable_properties with the new object's id().
And therefore, when binding, unexpectedly an active link will be created.
A workaround
A possible obvious approach might involve defining __copy__ and __deepcopy__ methods for a target class and updating bindable_properties in the process.
This works just fine for a trivial case. But I personally don't like it much, as it lacks universality and might get trickier if complicated by descriptors or unnecessary __init__() logic.
What copy uses under the hood is the pickle approach. From the docs:
In fact, the copy module uses the registered pickle functions from the copyreg module.
That leads us to the following solution:
importcopyimportcopyregfromtypingimportTypeVar, Type, AnyfromniceguiimportbindingT=TypeVar('T')
defregister_bindables(original_obj: T, copy_obj: T) ->None:
""" Here we replicate the "`bindable_properties` representation" of an object WITHOUT any unnecessary attribute accessing in process. """forattr_nameindir(original_obj):
if (id(original_obj), attr_name) inbinding.bindable_properties:
binding.bindable_properties[(id(copy_obj), attr_name)] =copy_objdefpickle_hooked(obj: Any):
""" This is a `obj.__reduce__()` wrapper that invoke bindables registration """reduced=obj.__reduce__()
creator=reduced[0]
defcreator_with_hook(*args, **kwargs):
cls_copy=creator(*args, **kwargs)
register_bindables(obj, cls_copy)
returncls_copyreturn (creator_with_hook,) +reduced[1:]
defcopyable(cls: Type[T]) ->Type[T]:
""" Decorate your class with this to register the modified `pickle` function Note that if you derive from `cls`, you have to decorate the child class with `@copyable` again. """copyreg.pickle(cls, pickle_hooked)
returncls@copyableclassMyClass:
x=binding.BindableProperty()
def__init__(self):
self.x=1original=MyClass()
duplicate=copy.copy(original)
assert (id(original), 'x') inbinding.bindable_properties# okassert (id(duplicate), 'x') inbinding.bindable_properties# ok
This also is sufficient for the __deepcopy__() to work right.
Possible solutions
If we would agree on bindable_properties being useful only when binding functions invoked, than we could fix it "on fly" when binding is happening, like so:
I'm not sure that is the case though. bindable_properties is a public variable, it can be used for, say, gathering binding statistics (like here) and therefore deserves more consistency.
It seems like If we are to stick with bindable_properties dictionary as it is, then we have to update it when copying happens.
Maybe we should keep track of classes, where bindableProperty was ever used, and change the way that class is copied (for instance like was proposed in a workaround above). This might look something like this then:
Description
If we make a
copy.copy()
(or adeepcopy()
) of an object with abindableProperty
, the resulting copy's bindable attribute is not really bindable anymore (until its value is reassigned manually).Example
Why is that?
The
copy
module techniques do not invoke__setattr__()
at any point, so theBindableProperty.__set__()
method never gets to updatebinding.bindable_properties
with the new object'sid()
.And therefore, when binding, unexpectedly an active link will be created.
A workaround
A possible obvious approach might involve defining
__copy__
and__deepcopy__
methods for a target class and updatingbindable_properties
in the process.This works just fine for a trivial case. But I personally don't like it much, as it lacks universality and might get trickier if complicated by descriptors or unnecessary
__init__()
logic.What
copy
uses under the hood is thepickle
approach. From the docs:That leads us to the following solution:
This also is sufficient for the
__deepcopy__()
to work right.Possible solutions
If we would agree on
bindable_properties
being useful only when binding functions invoked, than we could fix it "on fly" when binding is happening, like so:I'm not sure that is the case though.
bindable_properties
is a public variable, it can be used for, say, gathering binding statistics (like here) and therefore deserves more consistency.It seems like If we are to stick with
bindable_properties
dictionary as it is, then we have to update it when copying happens.Maybe we should keep track of classes, where
bindableProperty
was ever used, and change the way that class is copied (for instance like was proposed in a workaround above). This might look something like this then:The text was updated successfully, but these errors were encountered: