调用两次的方法:一次有参数,一次没有参数;如何防止这种行为?

methods called twice: once with and once without parameters; how to prevent this behaviour?

我尝试实现排序操作方法以显示不同种类的数据。 代码似乎有效,它做了我期望的事情,

但是class中的方法“sort_by_columnName”(例如:sort_by_customer、sort_by_order等)被调用:“Order_DataSource”两次 - 一次有参数,一次没有参数,

我正在考虑 - 这段代码出了什么问题以及如何防止这种行为?

python 3.8

import abc
from abc import ABCMeta
from typing import List
# kivy 2.0
from kivy.app import App
from kivy.core.window import Window
from kivy.event import EventDispatcher
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label


kv = '''
#: import cust_rgba kivy.utils.get_color_from_hex


<Concept_Datagrid>:
    id: gridview
    orientation: 'vertical'
    canvas.before:
        Color:
            rgba: cust_rgba('#8a8e8a')
        Rectangle:
            pos: self.pos
            size: self.size


<Custom_HeaderCell>:
    BoxLayout:
        orientation: 'vertical'
        canvas.before:
            Color:
                rgba: cust_rgba('#0b0f0a')
            Line:
                width: 1
                rectangle: (self.x, self.y, self.width, self.height)
        Label:
            size_hint: 1, 0.5
            id: header_label
            color: [0, 0, 0, 1]
            canvas.before:
                Color:
                    rgba: cust_rgba('#eaeaea')
                Rectangle:
                    pos: self.pos
                    size: self.size
        BoxLayout:
            id: sort_box
            orientation: 'horizontal'
            size_hint: 1, 0.5
            Button:
                id: sort_asc_btn
                background_color: cust_rgba('#3aeaea')
                text: 'sort Asc'
                on_release: root.sort_by(sort_id=self.text, sort_value=header_label.text)
            Button:
                id: sort_desc_btn
                background_color: cust_rgba('#3aeaea')
                text: 'sort Desc'
                on_release: root.sort_by(sort_id=self.text, sort_value=header_label.text)

'''


class Base_DataSource(metaclass=ABCMeta):
    @classmethod
    def __subclasshook__(cls, subclass):
        return (
            hasattr(subclass, 'source_data') and callable(subclass.source_data) and
            hasattr(subclass, 'table_keys') and callable(subclass.table_keys) and
            hasattr(subclass, 'sort_methods') and callable(subclass.sort_methods) or
            NotImplemented
        )
        pass

    @abc.abstractmethod
    def source_data(self):
        # returns data as List[dict]
        raise NotImplementedError

    @abc.abstractmethod
    def table_keys(self):
        # returns table's columns name
        raise NotImplementedError

    @abc.abstractmethod
    def sort_methods(self):
        # returns datasource column specific sorting methods name
        raise NotImplementedError

    def sort_dummy(self):
        # special method for sorting: nothing to do but important to be here;"
        pass


class Order_DataSource(EventDispatcher, Base_DataSource):

    data: List[dict] = ListProperty()

    def __init__(self):
        super(Order_DataSource, self).__init__()
        self.data = self.source_data()
        pass

    @staticmethod
    def source_data() -> List[dict]:
        return list([
            {'id': 1, 'customer': 'otto b', 'order_group': 'og_1', 'destination': 'DE_BER'},
            {'id': 2, 'customer': 'boris a', 'order_group': 'og_2', 'destination': 'UK_LD'},
            {'id': 3, 'customer': 'francine b', 'order_group': 'og_1', 'destination': 'FR_PAR'},
            {'id': 4, 'customer': 'franz w', 'order_group': 'og_1', 'destination': 'AT_W01'},
            {'id': 5, 'customer': 'cleopatra a', 'order_group': 'og_5', 'destination': 'EGY_KAI'},
            {'id': 6, 'customer': 'jorge c', 'order_group': 'og_1', 'destination': 'SP_MAD'},
            {'id': 7, 'customer': 'aras l', 'order_group': 'og_1', 'destination': 'LT_VIL'},
            {'id': 8, 'customer': 'uri m', 'order_group': 'og_8', 'destination': 'RUS_MOC'},
            {'id': 9, 'customer': 'joseph s', 'order_group': 'og_1', 'destination': 'DE_BAV'},
            {'id': 10, 'customer': 'julie g', 'order_group': 'og_2', 'destination': 'CH_URI'},
            {'id': 11, 'customer': 'cindy l', 'order_group': 'og_3', 'destination': 'US_NY'},
            {'id': 12, 'customer': 'jair b', 'order_group': 'og_4', 'destination': 'BR_BRA'},
            {'id': 13, 'customer': 'akiko a', 'order_group': 'og_5', 'destination': 'JAP_HON'},
            {'id': 14, 'customer': 'lana s', 'order_group': 'og_1', 'destination': 'SLO_LUB'},
            {'id': 15, 'customer': 'adanna u', 'order_group': 'og_7', 'destination': 'NIG_UL'},
            {'id': 16, 'customer': 'ljudmila c', 'order_group': 'og_8', 'destination': 'RUS_PET'},
        ])

    def table_keys(self) -> List[str]:
        dict_keys = []
        for key_, value_ in self.source_data()[0].items():
            dict_keys.append(key_)
        return dict_keys

    def sort_methods(self) -> dict:
        return {'id': None, 'customer': self.sort_by_customer,
                'order_group': self.sort_by_order, 'destination': self.sort_by_destination}

    def sort_by_id(self, *args) -> List[dict]:
        print(f'{self.sort_by_id.__name__} with args: {args}')
        if args:
            the_data = self.source_data()
            if args[0]['sort_id'] in 'sort Asc':
                sorted_data = sorted(the_data, key=lambda sCol: sCol['id'], reverse=False)
            else:
                sorted_data = sorted(the_data, key=lambda sCol: sCol['id'], reverse=True)
            return sorted_data
        pass

    def sort_by_customer(self, *args) -> List[dict]:
        print(f'{self.sort_by_customer.__name__} with args: {args}')
        if args:
            the_data = self.source_data()
            if args[0]['sort_id'] in 'sort Asc':
                sorted_data = sorted(the_data, key=lambda sCol: sCol['customer'], reverse=False)
            else:
                sorted_data = sorted(the_data, key=lambda sCol: sCol['customer'], reverse=True)
            return sorted_data
        pass

    def sort_by_order(self, *args) -> List[dict]:
        print(f'{self.sort_by_order.__name__} with args: {args}')
        if args:
            the_data = self.source_data()
            if args[0]['sort_id'] in 'sort Asc':
                sorted_data = sorted(the_data, key=lambda sCol: sCol['order_group'], reverse=False)
            else:
                sorted_data = sorted(the_data, key=lambda sCol: sCol['order_group'], reverse=True)
            return sorted_data
        pass

    def sort_by_destination(self, *args) -> List[dict]:
        print(f'{self.sort_by_destination.__name__} with args: {args}')
        if args:
            the_data = self.source_data()
            if args[0]['sort_id'] in 'sort Asc':
                sorted_data = sorted(the_data, key=lambda sCol: sCol['destination'], reverse=False)
            else:
                sorted_data = sorted(the_data, key=lambda sCol: sCol['destination'], reverse=True)
            return sorted_data
        pass

    pass


class Custom_HeaderCell(BoxLayout):

    def __init__(self, **kwargs):
        super().__init__()
        self.datagrid = kwargs['master']
        self.datasource = kwargs['source']
        self.cell_text = kwargs['cell_text']
        self.ids.header_label.text = self.cell_text
        if kwargs['sort_method'] is None:
            self.ids.sort_box.remove_widget(self.ids.sort_asc_btn)
            self.ids.sort_box.remove_widget(self.ids.sort_desc_btn)
            self.ids.sort_box.add_widget(Button(text='no sort'))
            self.wanted_method = self.datasource.sort_dummy
        else:
            if kwargs['sort_method'] not in ['None', None]:
                for method in dir(kwargs['sort_method'].__self__):
                    if method in kwargs['sort_method'].__name__:
                        self.wanted_method = kwargs['sort_method']
                self.ids.sort_asc_btn.on_release = self.wanted_method
                self.ids.sort_desc_btn.on_release = self.wanted_method

    def sort_by(self, **kwargs):
        print(f'Custom_HeaderCell.sort_by kwargs {kwargs}')
        if callable(self.wanted_method):
            self.datagrid.data_view.data = self.wanted_method(kwargs)
        pass


class Concept_Datagrid(BoxLayout):

    def __init__(self, datasource):
        super(Concept_Datagrid, self).__init__()
        self.datasource = datasource
        self.add_widget(Header(self, list(self.datasource.table_keys())))
        self.data_view = DataView(self.datasource)
        self.add_widget(self.data_view)

    pass


class Header(BoxLayout):
    size_hint = 1, None

    def __init__(self, dataGrid, columns, **kwargs):
        super(Header, self).__init__(**kwargs)
        self.height = self.size[1] * 2 / len(columns)  # this is not what I really want!
        the_header = HeaderView(dataGrid, columns)
        self.add_widget(the_header)
    pass


class HeaderView(GridLayout):

    def __init__(self, dataGrid, columns: list, **kwargs):
        super().__init__(**kwargs)
        self.rows = 1
        self.columns = columns

        for column_name in self.columns:

            if dataGrid.datasource.sort_methods()[column_name]:
                sortMethod = dataGrid.datasource.sort_methods()[column_name]
            else:
                sortMethod = None

            self.add_widget(Custom_HeaderCell(
                cell_text=str(column_name),
                sort_method=sortMethod,
                source=dataGrid.datasource,
                master=dataGrid)
            )

    pass


class DataView(GridLayout):
    data = ListProperty()

    def __init__(self, datasource: Order_DataSource):
        super(DataView, self).__init__()
        self.data = datasource.source_data()
        self.cols = len(datasource.table_keys())  # set number of columns to GridLayout,
        pass

    def _update_view(self):
        if self.children:
            self.clear_widgets()
        for entity in self.data:
            for item in entity.values():
                lbl = Label()
                lbl.text = str(item)
                self.add_widget(lbl)

    def on_data(self, *args):
        self._update_view()

    pass


class Test_App(App):
    Window.size = (800, 600)
    title = str('minimal datagrid app')

    def build(self):
        Builder.load_string(kv)
        source = Order_DataSource()
        concept_test = Concept_Datagrid(source)
        return concept_test
        pass
    pass


if __name__ == '__main__':
    Test_App().run()
    pass

您似乎指定了两次 on_release 操作,因此指定的方法被调用了两次。在 Custom_HeaderCell__init__() 方法中,您有:

            self.ids.sort_asc_btn.on_release = self.wanted_method
            self.ids.sort_desc_btn.on_release = self.wanted_method

并且在您的 kv 中您有:

        Button:
            id: sort_asc_btn
            background_color: cust_rgba('#3aeaea')
            text: 'sort Asc'
            on_release: root.sort_by(sort_id=self.text, sort_value=header_label.text)
        Button:
            id: sort_desc_btn
            background_color: cust_rgba('#3aeaea')
            text: 'sort Desc'
            on_release: root.sort_by(sort_id=self.text, sort_value=header_label.text)

因此您设置了 on_release 两次,这导致它被调用了两次。