omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    [Share] List Dialog - Simple

    Pythonista
    share ui.tableview
    3
    10
    8260
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Phuket2
      Phuket2 last edited by Phuket2

      Below, is just a super simple list dialog using a ui.TableView. Pythonista contains the dialog module which handles this way better. This is just another way to look at it. If you needed a little bit more control for example. Anyway, I was just playing around and thought i would post it. I did get a little confused accessing variables outside the delegate closure. I followed a pattern that I found on stackflow for python 2. I could not assign to the outter scoped var my_sel the way that I thought I could based on the docs for py3 using nonlocal.
      This is a language construct, so cant believe its a bug. I am just doing something wrong.

      import ui
      
      '''
      ui.ListDataSource = LDS
      
      Very crude example of a list dialog.
      Just to show how easy it can be if you have very simple needs.
      last_name is intentionally not displayed. LDS only knows about title,
      image and accessory_type.
      But the fact the dict contains other keys is not an issue.
      '''
      
      
      def show_list_dialog(items=None, *args, **kwargs):
          '''
          depending on your needs this could be your list dialog, you
          use over and over, although you would not because dialogs
          module has one already.
          '''
          items = items or []  # hmmmm thanks @ccc
          tbl = ui.TableView(**kwargs)
          tbl.data_source = ui.ListDataSource(items)
      
          # i used this because I could not get nonlocal working
          # as I thought it should work, i wanted to just use my_sel = None
          my_sel = {'value': None}
      
          class MyTableViewDelegate (object):
              # nonlocal my_sel (does not work as I understand it should)
              def tableview_did_select(self, tableview, section, row):
                  my_sel['value'] = tableview.data_source.items[row]
                  tableview.close()
      
          tbl.delegate = MyTableViewDelegate()
      
          tbl.present(style='sheet')
          tbl.wait_modal()  # This method is what makes this a dialog(modal)
          return my_sel['value']
      
      if __name__ == '__main__':
          f = (0, 0, 400, 300)
      
          items = [{'title': 'Ian', 'last_name': "Jones"},
                   {'title': 'Christian', 'last_name': "Smith"}]
          # uncomment the line below, to see the difference
          # items = ['Ian', 'John', 'Paul', 'Ringo']
          result = show_list_dialog(items, frame=f, name='Select a Name')
          print(result)
      
      1 Reply Last reply Reply Quote 0
      • zrzka
        zrzka last edited by

        What exactly didn't work for you with nonlocal? This works ...

        import ui
        
        def show_list_dialog(items=None, *args, **kwargs):
            items = items or []  # hmmmm thanks @ccc
            tbl = ui.TableView(**kwargs)
            tbl.data_source = ui.ListDataSource(items)
        
            selection = None
        
            class MyTableViewDelegate (object):
                def tableview_did_select(self, tableview, section, row):
                    nonlocal selection
                    selection = tableview.data_source.items[row]
                    tableview.close()
        
            tbl.delegate = MyTableViewDelegate()
        
            tbl.present(style='sheet')
            tbl.wait_modal()  # This method is what makes this a dialog(modal)
            return selection
        
        if __name__ == '__main__':
            f = (0, 0, 400, 300)
        
            items = [{'title': 'Ian', 'last_name': "Jones"},
                     {'title': 'Christian', 'last_name': "Smith"}]
            # uncomment the line below, to see the difference
            # items = ['Ian', 'John', 'Paul', 'Ringo']
            result = show_list_dialog(items, frame=f, name='Select a Name')
            print(result)
        
        1 Reply Last reply Reply Quote 0
        • zrzka
          zrzka last edited by

          Also you can avoid creating MyTableViewDelegate by using data source as delegate and using data source's action. If you'd like to display something else than just title value, you can create your data source in this way and pass whatever you want as an item.

          import ui
          
          
          class MyDataSource(ui.ListDataSource):
              def tableview_cell_for_row(self, tv, section, row):
                  item = self.items[row]
                  cell = ui.TableViewCell('default')
                  cell.text_label.text = '{} {}'.format(item['first_name'], item['last_name'])
                  return cell
          
          
          def show_list_dialog(items=None, **kwargs):
              result = None
          
              tbl = ui.TableView(**kwargs)
              tbl.data_source = MyDataSource(items or [])
              tbl.delegate = tbl.data_source
          
              def did_select(ds):
                  nonlocal result
                  result = ds.items[ds.selected_row]
                  tbl.close()
          
              tbl.data_source.action = did_select
              tbl.present(style='sheet')
              tbl.wait_modal()
          
              return result
          
          if __name__ == '__main__':
              f = (0, 0, 400, 300)
              items = [{'first_name': 'Ian', 'last_name': "Jones"},
                       {'first_name': 'Christian', 'last_name': "Smith"}]
              result = show_list_dialog(items, frame=f, name='Select a Name')
              print(result)
          
          Phuket2 1 Reply Last reply Reply Quote 1
          • Phuket2
            Phuket2 @zrzka last edited by

            @zrzka , nice. That's why I dont mind doing these simple examples. You can learn a lot from peoples input. I am still not sure why my nonlocal approached did work and yours does. But its the first time I have ever even used it. In my example, I was trying to simplify to a function instead of a class, because I think when guys are learning the ui module/python beginners, including me it can appear very daunting. I think the classes eventually simplify a lot of it. Anyway, its still all fun. And even though I have gotten away with using closures before, I didn't really understand the scoping. In my example I was trying to understand it.

            1 Reply Last reply Reply Quote 0
            • JonB
              JonB last edited by

              @Phuket2 your nonlocal was in the wrong spot -- in the class, not the method.

              Phuket2 2 Replies Last reply Reply Quote 0
              • Phuket2
                Phuket2 @JonB last edited by

                @JonB , oh thanks. Now you point it out, its very clear. Even I haven't tested yet, but i get it! But happy to find out why

                1 Reply Last reply Reply Quote 0
                • Phuket2
                  Phuket2 @JonB last edited by

                  @JonB, just tested it. Works as expected. Thanks.

                  class MyTableViewDelegate (object):
                          # nonlocal my_sel (does not work as I understand it should)
                          def tableview_did_select(self, tableview, section, row):
                              nonlocal my_sel
                              my_sel = tableview.data_source.items[row]
                              tableview.close()
                  
                  1 Reply Last reply Reply Quote 0
                  • zrzka
                    zrzka last edited by zrzka

                    @Phuket2 you did mention that you were trying to understand scopes. Here're some explanations of how global and nonlocal works. It's simplified and it's still recommended to read Python documentation to fully understand it. But this should help a bit.

                    global

                    name = 'Phuket2'  # Global variable
                    
                    def say_hallo():
                        # You're just reading global 'name', thus no need
                        # to use 'global' in this case
                        print('Hallo {}'.format(name))
                    
                    def update_without_global(new_name):
                        # Here we're writing to 'name', so, new local variable
                        # 'name' is created and global 'name' is untouched
                        name = new_name
                        # What can help here is analyzer warning:
                        #  - local variable 'name' is assigned but never used
                    
                    def update_with_global(new_name):
                        # Here we're saying that we would like to modify
                        # global variable 'name'
                        global name
                        name = new_name
                    
                    say_hallo()  # Prints 'Hallo Phuket2'
                    update_without_global('Zrzka')
                    say_hallo()  # Still prints 'Hallo Phuket2' 
                    update_with_global('Zrzka')
                    say_hallo()  # Prints 'Hallo Zrzka'
                    

                    But what about this?

                    attributes = {'name': 'Phuket2'}
                    
                    def say_hallo():
                        print('Hallo {}'.format(attributes['name']))
                    
                    def update_without_global(new_name):
                        attributes['name'] = new_name
                    
                    say_hallo()  # Prints 'Hallo Phuket2'
                    update_without_global('Zrzka')
                    say_hallo()  # Prints 'Hallo Zrzka'
                    

                    Ouch, what's going on one can say. I didn't use global here, but it was modified.

                    What is variable? It's called name in Python and every name refers to an object. This is called binding. You can read more here if you're interested (chapter 4.2.).

                    Basically everything is an object in Python. Really? What about primitive types for example? Python has no primitive types you know from Java or Objective C. Everything is an object in Python, even bool, int, ...

                    What plays big role here is mutability vs immutability and if you're mutating in place or assigning new object. Some types are immutable (bool, int, float, tuple, str, frozenset) and some are mutable (list, set, dict). Immutable objects can't be modified after you create them, mutable can.

                    # Immutable
                    x = 3
                    print(id(x)) # 4322496384
                    x += 1       # New object is created
                    print(id(x)) # 4322496416 != 4322496384
                    
                    # Mutable
                    y = ['a']
                    print(id(y))  # 4454700936
                    y.append('b')
                    print(id(y))  # 4454700936 (equals)
                    y[:] = ['a', 'b', 'c']
                    print(id(y))  # 4454700936 (equals)
                    y = ['a', 'b', 'c']
                    print(id(y))  # 4461963720 (oops, new object b/o =)
                    

                    x is of type int. This type is immutable. It looks like mutable type, but it isn't. Whenever you do x += 1, new object is created and x is rebinded to this new object. If type of your variable is immutable and you want to modify it, you have to use global.

                    y is of type list. This type is mutable. Whenever you do y.append('b'), it's still the same object. .append mutates it in place. Also y[:] = ['a', 'b'] mutates the list in place. It replaces all elements in the list, but it's done in place, no new object is created. So, you don't need global here as well.

                    But don't forget that simple assignment like y = ['a', 'b', 'c'] rebinds y variable to the new object (you're not mutating it in place) and you must use global in this case.

                    Let's pretend that you can't access class instance attributes prefixed with _ (actually you can, but let's pretend you can't). Following example shows immutable type Contact. If you want to modify name, you have to create new object (there's no setter for name property).

                    class Contact():
                        def __init__(self, name):
                            self._name = name
                    
                        @property
                        def name(self):
                            return self._name
                            
                    me = Contact('Zrzka')
                    me = Contact('Phuket2')
                    

                    And here's mutable type MutableContact.

                    class MutableContact():
                        def __init__(self, name):
                            self._name = name
                    
                        @property
                        def name(self):
                            return self._name
                    
                        @name.setter
                        def name(self, new_name):
                            self._name = new_name
                            
                    me = Contact('Zrzka')
                    me.name = 'Phuket2'
                    

                    Immutable types behaves like Contact (int, bool, ...) and mutable behaves like MutableContact (list, dict, ...). One more example, check __add__ method of MyInt.

                    class MyInt():
                        def __init__(self, value):
                            self._value = value
                    
                        @property
                        def value(self):
                            return self._value
                    
                        def __add__(self, o):
                            if not isinstance(o, MyInt):
                                raise ValueError('MyInt can be added with MyInt only')
                            return MyInt(self.value + o.value)
                    
                        def __str__(self):
                            return 'identity: {} value: {}'.format(id(self), self._value)
                    
                    
                    a = MyInt(10)
                    print(a)       # identity: 4600148712 value: 10
                    b = MyInt(20)
                    print(b)       # identity: 4600148600 value: 20
                    a += b
                    print(a)       # identity: 4600150448 value: 30
                    

                    This is how immutable int behaves, __add__ returns new object with new value instead of adding o.value directly to self._value.

                    nonlocal

                    Some quote from nonlocal docs:

                    The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals. This is important because the default behavior for binding is to search the local namespace first. The statement allows encapsulated code to rebind variables outside of the local scope besides the global (module) scope.

                    Example of excluding globals:

                    name = 'Phuket2'
                    
                    def update(new_name):
                        nonlocal name
                        name = new_name
                        
                    update('Zrzka')
                    print(name)
                    

                    It leads to SyntaxError, because no binding for nonlocal name was found. Globals are excluded.

                    Following example just creates local variable name. Same analyzer warning can help (assigned to, but never read).

                    def hallo():
                        name = 'Phuket2'
                        
                        def update(new_name):
                            # Local variable 'name', has nothing to do
                            # with 'name' defined at the beginning of
                            # 'hallo'
                            name = new_name
                            
                        update('Zrzka')
                        print(name)  # Prints 'Phuket2'
                    
                    hallo()
                    

                    And here's the correct one.

                    def hallo():
                        name = 'Phuket2'
                        
                        def update(new_name):
                            nonlocal name
                            name = new_name
                            
                        update('Zrzka')
                        print(name)  # Prints 'Zrzka'
                    
                    hallo()
                    

                    name in update refers to the name in hallo. You can rebind it. Same dance with mutable / immutable types can be reused here as well. See following example.

                    def hallo():
                        attributes = {'name': 'Phuket2'}
                        
                        def update(new_name):
                            attributes['name'] = new_name
                            
                        update('Zrzka')
                        print(attributes['name'])  # Prints 'Zrzka'
                    
                    hallo()
                    

                    I didn't use nonlocal, but attributes were still modified. That's because I did use in place mutation. No new object, no need to rebind.

                    Also nonlocal search enclosing scopes (not just one scope) until it finds the right variable.

                    def level1():
                        name = 'Phuket2'
                        
                        def level2():
                            def level3():
                                nonlocal name  # name in level1
                                name = 'Zrzka'
                            level3()
                    
                        level2()    
                        print(name)  # Prints 'Zrzka'
                    
                    level1()
                    

                    And the nearest enclosing scope is used.

                    import console
                    console.clear()
                    
                    def level1():
                        name = 'Phuket2'
                        
                        def level2():
                            name = 'Ole'        
                            def level3():
                                nonlocal name  # name in level2
                                name = 'Zrzka'
                            level3()
                    
                        level2()    
                        print(name)  # Prints 'Phuket2'
                    
                    level1()
                    

                    These are contrived examples. Just to demostrate how it works. You do not want these multilevel functions where each has name and nonlocal somewhere :)

                    But the most important thing about nonlocal is that this lexical scoping applies to function namespaces only.

                    def classy():
                        name = 'Phuket2'
                        
                        class Hallo():
                            name = 'Batman'
                            
                            def __init__(self):
                                self.name = 'Ole'
                            
                            def update(self, new_name):
                                nonlocal name  # name in classy
                                name = new_name
                                
                        h = Hallo()
                        
                        print(name)       # Prints 'Phuket2'
                        print(Hallo.name) # Prints 'Batman'
                        print(h.name)     # Prints 'Ole'
                    
                        h.update('Zrzka')
                    
                        print(name)       # Prints 'Zrzka'
                        print(Hallo.name) # Prints 'Batman'
                        print(h.name)     # Prints 'Ole'    
                        
                    classy()
                    

                    As I already wrote, this is simplified explanation with contrived examples. Anyway, hope it helps to understand what's going on.

                    Phuket2 1 Reply Last reply Reply Quote 0
                    • Phuket2
                      Phuket2 @zrzka last edited by

                      @zrzka, wow. Thanks for the fantastic explanation. I haven't gone though it in detail yet, but I will. I was a pascal/c/vb programmer many many years ok. Even though its so long ago, I often do things how I remember I think they should be done. Maybe global is ok to use in Python for example, but i would hate myself if I found I had too use it for some reason. The nonlocal seemed to make sense to me when I wanted to start to use closures and be able to interact with the outter scopes vars in a somewhat correct manner (but its possibly also considered spaghetti code, not sure) . But I have been cheating :) eg, i would add a runtime attr to an object to read and write because on not understanding these scope rules.
                      I find it a little funny how they say Python is one of the easiest languages to learn. I am sure its correct in some contexts. But the richness of the language makes it somewhat challenging in my mind. C was hard, but it was fairly rigid. In someways that was nice once you got the hang of it. Anyway, I am waffling on.
                      Really thanks again. I will go though your sample code carefully. Hopefully other newbies here will also.

                      1 Reply Last reply Reply Quote 0
                      • zrzka
                        zrzka last edited by

                        @Phuket2 said:

                        Maybe global is ok to use in Python for example, but i would hate myself if I found I had too use it for some reason.

                        Why? Sometimes it's perfectly ok to use it. There're other ways how to achieve your goal sometimes. Depends. Globals are here and I'm not fan of globals are evil and also not fan of globals are perfect, let's use them everywhere. Just be pragmatic and use the simplest way.

                        The nonlocal seemed to make sense to me when I wanted to start to use closures and be able to interact with the outter scopes vars in a somewhat correct manner (but its possibly also considered spaghetti code, not sure) . But I have been cheating :) eg, i would add a runtime attr to an object to read and write because on not understanding these scope rules.

                        In the end the only thing which matters is readability / maintainability. If it's clear what the code is doing, it's fine. Everyone has it's own style, patterns, ... Just don't be too clever or you (or others) will not understand your code after week, month, ...

                        I find it a little funny how they say Python is one of the easiest languages to learn. I am sure its correct in some contexts. But the richness of the language makes it somewhat challenging in my mind.

                        Yes, it's easy. The problem here is discipline. Python allows you to do everything and you have to maintain discipline to be a good citizen. And sometimes people don't maintain it, stuff brakes, ... Sometimes they have no clue why, because they didn't read documentation, ...

                        C was hard, but it was fairly rigid.

                        I don't consider C as hard, but I think it's harder compared to Python. Not because it's really hard itself, but because code can contain lot of pointer arithmetics, manual memory management, ... and it's unclear what it does when you look at it for the first time. Anyway it was one of my first languages (when I skip BASIC, ...), I like it and I still use it. Remember good old times with Watcom C, protected mode, ... fun without documentation & internet :)

                        1 Reply Last reply Reply Quote 1
                        • First post
                          Last post
                        Powered by NodeBB Forums | Contributors