Python Forum
Problem in list manipulation
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Problem in list manipulation
#1
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?
Reply
#2
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]
Reply
#3
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))
Reply
#4
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))
Reply
#5
(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!!!!
Reply
#6
Remove looks for same value, not same object. If it compared object id it would not be very useful.
Reply
#7
(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()
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Problem with "Number List" problem on HackerRank Pnerd 5 2,130 Apr-12-2022, 12:25 AM
Last Post: Pnerd
  optimization problem for dataframe manipulation fimmu 0 1,474 Aug-31-2020, 06:02 PM
Last Post: fimmu
  How to pass a list by value for manipulation within a process? bRitch022 4 2,733 Jul-09-2020, 07:13 PM
Last Post: bRitch022
  IP string manipulation problem TheRealNoob 12 7,341 Feb-04-2019, 09:29 AM
Last Post: perfringo
  list manipulation cameronwood611 3 3,594 Oct-03-2017, 02:58 PM
Last Post: ichabod801
  list or dictionary manipulation dtigue 5 101,311 Jul-21-2017, 03:14 PM
Last Post: ichabod801

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020