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:
Thanks for reporting this problem, @balex89! And thanks for including a workaround.
I've been thinking about it again and again over the past two months and I'm still undecided what to do about it. So far I see three options:
Provide a decorator like @binding.copyable to allow the user to fix this problem explicitly if needed.
Add bindable properties "on the fly" when invoking a binding function.
Automatically make the class copyable when setting a bindable property.
Option 1 is nice because it doesn't require any change to the existing binding implementation, but is hard to discover. Users will probably run into the problem first before noticing the need for this decorator.
Option 2 is nice because it works automatically and requires very little change to the implementation. Sure, the dictionary is filled a little inconsistently, but it still allows precise statements about how many bindable properties are registered - even if copied classes will register their bindable properties later.
Option 3 might be the most correct(?) but seems to be the most complex one and adds an additional dictionary lookup for every write access to a bindable property which could add up.
For simplicity's sake I tend towards option 2. What do you think? Would you like to create a pull request and see how it turns out?
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: