Posts: 8
Threads: 2
Joined: Oct 2021
Oct-17-2021, 04:41 PM
(This post was last modified: Oct-17-2021, 04:41 PM by CyKlop.)
Error in list processing??
Class Diagram:
Bindable (my class) list (built in)
\ /
+------------+----------+
|
V
BindableList
class Bindable:
def __init__(self):
super().__init__()
self._binds = {}
def bind(self, event, callback):
if event not in self._binds.keys:
self._binds[event] = []
self._binds[event].append(callback)
def event_generate(self, event_id, *params):
for callback in self._binds[event_id]:
callback(event_id, self, *params)
class BindableList(Bindable, list):
def __init__(self):
super().__init__()
def append(self, __object) -> None:
super().append(__object)
self.event_generate(BindableList.ITEM_APPENDED, __object)
def insert(self, index, __object) -> None:
super().insert(index, __object)
self.event_generate(BindableList.ITEM_INSERTED, index, __object)
def remove(self, __object) -> None:
super().remove(__object)
self.event_generate(BindableList.ITEM_REMOVED, __object) The purpose of BindableList is to keep a tkinter.ttk.Treeview synchronized with the various lists, even if the list is modified directly by someone else (i.e. a variation on the observer design pattern).
Here's the problem:
if I have a straight list:
my_list = list()
my_list.append(object1)
my_list.append(object2)
my_list.append(object3)
my_list.remove(object2) all works fine. The resulting list only has object1 and object3 in it. HOWEVER, if I do:
my_list = BindableList()
my_list.append(object1)
my_list.append(object2)
my_list.append(object3)
my_list.remove(object2) then my_list always removed the first item in the list - i.e. the result of the above code is that my_list contains object2 and object3
Version of python is 3.9.5 running on MS-Windows (the PyCharm IDE, if that matters)
Anyone see anything obvious that I'm messing up?
Posts: 1,583
Threads: 3
Joined: Mar 2020
I can't generate the events, but if I comment out the event_generate lines, then it appears to work.
...
my_list = BindableList()
my_list.append(1)
my_list.append(2)
my_list.append(3)
print(f"Before: {my_list}")
my_list.remove(2)
print(f"After: {my_list}") Output: Before: [1, 2, 3]
After: [1, 3]
Posts: 8
Threads: 2
Joined: Oct 2021
Sorry, I left off the events. Here's a more complete set of code:
from model import BindableList
class Foo(BindableList):
def __init__(self, name):
super().__init__()
self._name = name
def __repr__(self):
return self._name
def item_removed(sender, *args):
print("in handler:")
for i in sender:
print(i)
if __name__ == "__main__":
my_list = BindableList()
my_list.bind(BindableList.ITEM_REMOVED, item_removed)
o1 = Foo('1')
o2 = Foo('2')
o3 = Foo('3')
my_list.append(o1)
my_list.append(o2)
my_list.append(o3)
print("before remove")
for i in my_list:
print(i)
my_list.remove(o2)
print("in main code")
for i in my_list:
print(i) and the results of the run:
Output: before remove
1
2
3
in handler:
2
3
in main code
2
3
Process finished with exit code 0
As you can see, it was the first object (o1) that was deleted, rather than the second one (o2) despite calling my_list.remove(o2)
Here's the full source of Bindable and BindableList:
class Bindable:
def __init__(self):
self.binds = {}
def bind(self, event: str, callback):
if event not in self.binds.keys():
self.binds[event] = list()
self.binds[event].append(callback)
def event_generate(self, event_type: str, event=None):
if event_type not in self.binds.keys():
return
for callback in self.binds[event_type]:
if event is not None:
callback(self, event)
else:
callback(self)
class BindableList(Bindable, list):
ITEM_APPENDED = "<<ITEM_APPENDED>>"
ITEM_REMOVED = "<<ITEM_REMOVED>>"
ITEM_INSERTED = "<<ITEM_INSERTED>>"
def append(self, __object) -> None:
super().append(__object)
self.event_generate(BindableList.ITEM_APPENDED, __object)
def remove(self, __value) -> None:
super().remove(__value)
self.event_generate(BindableList.ITEM_REMOVED, __value)
def insert(self, __index: int, __object) -> None:
super().insert(__index, __object)
self.event_generate(BindableList.ITEM_INSERTED, (__index, __object))
Posts: 6,810
Threads: 20
Joined: Feb 2020
Oct-18-2021, 03:42 AM
(This post was last modified: Oct-18-2021, 03:42 AM by deanhystad.)
I get the same results if my_list is a list or a BindableList. The problem is that everything you added to the list is the same; o1 == o2 == o3. The reason they are all the same is because they are all empty lists.
If you want to remove items based on the item ID or the name, you need to override some comparison operators in BindableList. These use _name when comparing <, >. ==.
class BindableList(Bindable, list):
ITEM_APPENDED = "<<ITEM_APPENDED>>"
ITEM_REMOVED = "<<ITEM_REMOVED>>"
ITEM_INSERTED = "<<ITEM_INSERTED>>"
def __gt__(self, other):
return self._name > other._name
def __lt__(self, other):
return self._name < other._name
def __eq__(self, other):
return self._name == other._name
def append(self, __object) -> None:
super().append(__object)
self.event_generate(BindableList.ITEM_APPENDED, __object)
def remove(self, __value) -> None:
super().remove(__value)
self.event_generate(BindableList.ITEM_REMOVED, __value)
def insert(self, __index: int, __object) -> None:
super().insert(__index, __object)
self.event_generate(BindableList.ITEM_INSERTED, (__index, __object))
Posts: 8
Threads: 2
Joined: Oct 2021
(Oct-18-2021, 03:42 AM)deanhystad Wrote: I get the same results if my_list is a list or a BindableList. The problem is that everything you added to the list is the same; o1 == o2 == o3. The reason they are all the same is because they are all empty lists.
Oi! I was so sure that the comparison is done via the reference ID. I'll give your suggestions a try and see what happens. Thank you!!!!
Posts: 6,810
Threads: 20
Joined: Feb 2020
Oct-18-2021, 08:44 AM
(This post was last modified: Oct-20-2021, 03:37 PM by deanhystad.)
Remove looks for same value, not same object. If it compared object id it would not be very useful.
Posts: 2,128
Threads: 11
Joined: May 2017
(Oct-17-2021, 04:41 PM)CyKlop Wrote: The purpose of BindableList is to keep a tkinter.ttk.Treeview synchronized with the various lists, even if the list is modified directly by someone else (i.e. a variation on the observer design pattern).
I made an example, but without binding events. If you want to copy the behavior of a list, don't use a list.
Instead you can inherit your class from collections.UserList and work with tkinter.ttk.TreeView as composition.
The problem is, that list (UserList) and TreeView do have methods with equal names.
Here the demo code:
import operator
import random
import time
from collections import UserList
from tkinter import END, Tk
from tkinter.ttk import Treeview
class ListTreeView(UserList):
def __init__(self, master=None, columns=None):
super().__init__()
self.master = master
self.treeview = Treeview(master, columns=columns, show="headings")
def append(self, data):
super().append(data)
self.treeview.insert("", END, values=data)
def extend(self, iterable):
super().extend(iterable)
for item in iterable:
self.treeview.insert("", END, values=item)
def remove(self, item):
index = self.index(item)
del self[index]
selector = operator.itemgetter(index)
children = self.treeview.get_children()
self.treeview.delete(selector(children))
def grid(self, *args, **kwargs):
return self.treeview.grid(*args, **kwargs)
def pack(self, *args, **kwargs):
return self.treeview.pack(*args, **kwargs)
def __delitem__(self, index):
treeview_index = self.treeview.get_children()
super().__delitem__(index)
self.treeview.delete(treeview_index[index])
if __name__ == "__main__":
root = Tk()
tv = ListTreeView(root, ["index", "random"])
tv.pack()
for index in range(100, 106):
time.sleep(0.2)
tv.append([index, random.randint(0, 100)])
root.update()
# delete last
time.sleep(2)
del tv[-1]
root.update()
# delete first
time.sleep(1)
del tv[0]
root.update()
time.sleep(1)
root.destroy()
|