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
-
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)
-
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)
-
Also you can avoid creating
MyTableViewDelegate
by using data source as delegate and using data source'saction
. If you'd like to display something else than justtitle
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)
-
@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.
-
@Phuket2 your nonlocal was in the wrong spot -- in the class, not the method.
-
@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
-
@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()
-
@Phuket2 you did mention that you were trying to understand scopes. Here're some explanations of how
global
andnonlocal
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 typeint
. This type is immutable. It looks like mutable type, but it isn't. Whenever you dox += 1
, new object is created andx
is rebinded to this new object. If type of your variable is immutable and you want to modify it, you have to useglobal
.y
is of typelist
. This type is mutable. Whenever you doy.append('b')
, it's still the same object..append
mutates it in place. Alsoy[:] = ['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 needglobal
here as well.But don't forget that simple assignment like
y = ['a', 'b', 'c']
rebindsy
variable to the new object (you're not mutating it in place) and you must useglobal
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 typeContact
. If you want to modifyname
, you have to create new object (there's no setter forname
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 likeMutableContact
(list
,dict
, ...). One more example, check__add__
method ofMyInt
.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 addingo.value
directly toself._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 nonlocalname
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
inupdate
refers to thename
inhallo
. 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
, butattributes
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
andnonlocal
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.
-
@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. -
@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 :)