[Python-ideas] Method chaining notation
Ryan Gonzalez
rymg19 at gmail.com
Fri Feb 21 22:40:27 CET 2014
More information about the Python-ideas mailing list
Fri Feb 21 22:40:27 CET 2014
- Previous message: [Python-ideas] Method chaining notation
- Next message: [Python-ideas] Method chaining notation
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
On Fri, Feb 21, 2014 at 11:30 AM, Chris Angelico <rosuav at gmail.com> wrote: > Yeah, I'm insane, opening another theory while I'm busily championing > a PEP. But it was while writing up the other PEP that I came up with a > possible syntax for this. > > In Python, as in most languages, method chaining requires the method > to return its own object. > > class Count: > def __init__(self): self.n = 0 > def inc(self): > self.n += 1 > return self > > dracula = Count() > dracula.inc().inc().inc() > print(dracula.n) > > It's common in languages like C++ to return *this by reference if > there's nothing else useful to return. It's convenient, it doesn't > cost anything much, and it allows method chaining. The Python > convention, on the other hand, is to return self only if there's a > very good reason to, and to return None any time there's mutation that > could plausibly return a new object of the same type (compare > list.sort() vs sorted()). Method chaining is therefore far less common > than it could be, with the result that, often, intermediate objects > need to be separately named and assigned to. I pulled up one file from > Lib/tkinter (happened to pick filedialog) and saw what's fairly > typical of Python GUI code: > > ... > self.midframe = Frame(self.top) > self.midframe.pack(expand=YES, fill=BOTH) > > self.filesbar = Scrollbar(self.midframe) > self.filesbar.pack(side=RIGHT, fill=Y) > self.files = Listbox(self.midframe, exportselection=0, > yscrollcommand=(self.filesbar, 'set')) > self.files.pack(side=RIGHT, expand=YES, fill=BOTH) > ... > > Ugh! > Every frame has to be saved away somewhere (incidentally, I don't see > why self.midframe rather than just midframe - it's not used outside of > __init__). With Tkinter, that's probably necessary (since the parent > is part of the construction of the children), but in GTK, widget > parenting is done in a more method-chaining-friendly fashion. Compare > these examples of PyGTK and Pike GTK: > > # Cut down version of > http://pygtk.org/pygtk2tutorial/examples/helloworld2.py > import pygtk > pygtk.require('2.0') > import gtk > > def callback(widget, data): > print "Hello again - %s was pressed" % data > > def delete_event(widget, event, data=None): > gtk.main_quit() > return False > > window = gtk.Window(gtk.WINDOW_TOPLEVEL) > window.set_title("Hello Buttons!") > window.connect("delete_event", delete_event) > window.set_border_width(10) > box1 = gtk.HBox(False, 0) > window.add(box1) > button1 = gtk.Button("Button 1") > button1.connect("clicked", callback, "button 1") > box1.pack_start(button1, True, True, 0) > button2 = gtk.Button("Button 2") > button2.connect("clicked", callback, "button 2") > box1.pack_start(button2, True, True, 0) > window.show_all() > > gtk.main() > > //Pike equivalent of the above: > void callback(object widget, string data) {write("Hello again - %s was > pressed\n", data);} > void delete_event() {exit(0);} > > int main() > { > GTK2.setup_gtk(); > object button1, button2; > GTK2.Window(GTK2.WINDOW_TOPLEVEL) > ->set_title("Hello Buttons!") > ->set_border_width(10) > ->add(GTK2.Hbox(0,0) > ->pack_start(button1 = GTK2.Button("Button 1"), 1, 1, 0) > ->pack_start(button2 = GTK2.Button("Button 2"), 1, 1, 0) > ) > ->show_all() > ->signal_connect("delete_event", delete_event); > button1->signal_connect("clicked", callback, "button 1"); > button2->signal_connect("clicked", callback, "button 2"); > return -1; > } > > > Note that in the Pike version, I capture the button objects, but not > the Hbox. There's no name ever given to that box. I have to capture > the buttons, because signal_connect doesn't return the object (it > returns a signal ID). The more complicated the window layout, the more > noticeable this is: The structure of code using chained methods > mirrors the structure of the window with its widgets containing > widgets; but the structure of the Python equivalent is strictly > linear. > > So here's the proposal. Introduce a new operator to Python, just like > the dot operator but behaving differently when it returns a bound > method. We can possibly use ->, or maybe create a new operator that > currently makes no sense, like .. or .> or something. Its semantics > would be: > > 1) Look up the attribute following it on the object, exactly as per > the current . operator > 2) If the result is not a function, return it, exactly as per current. > 3) If it is a function, though, return a wrapper which, when called, > calls the inner function and then returns self. > > This can be done with an external wrapper, so it might be possible to > do this with MacroPy. It absolutely must be a compact notation, > though. > > This probably wouldn't interact at all with __getattr__ (because the > attribute has to already exist for this to work), and definitely not > with __setattr__ or __delattr__ (mutations aren't affected). How it > interacts with __getattribute__ I'm not sure; whether it adds the > wrapper around any returned functions or applies only to something > that's looked up "the normal way" can be decided by ease of > implementation. > > Supposing this were done, using the -> token that currently is used > for annotations as part of 'def'. Here's how the PyGTK code would > look: > > import pygtk > pygtk.require('2.0') > import gtk > > def callback(widget, data): > print "Hello again - %s was pressed" % data > > def delete_event(widget, event, data=None): > gtk.main_quit() > return False > > window = (gtk.Window(gtk.WINDOW_TOPLEVEL) > ->set_title("Hello Buttons!") > ->connect("delete_event", delete_event) > ->set_border_width(10) > ->add(gtk.HBox(False, 0) > ->pack_start( > gtk.Button("Button 1")->connect("clicked", callback, "button > 1"), > True, True, 0) > ->pack_start( > gtk.Button("Button 1")->connect("clicked", callback, "button > 1"), > True, True, 0) > ) > ->show_all() > ) > > gtk.main() > > > Again, the structure of the code would match the structure of the > window. Unlike the Pike version, this one can even connect signals as > part of the method chaining. > > Effectively, x->y would be equivalent to chain(x.y): > > def chain(func): > def chainable(self, *args, **kwargs): > func(self, *args, **kwargs) > return self > return chainable > > Could be useful in a variety of contexts. > > Thoughts? > > ChrisA > _______________________________________________ > Python-ideas mailing list > Python-ideas at python.org > https://mail.python.org/mailman/listinfo/python-ideas > Code of Conduct: http://python.org/psf/codeofconduct/ > Seems weird to me, since I'm used to -> being for C++ pointers. I prefer "..", because it gives the impression that it's something additional. Either that, or I've used Lua too much. -- Ryan If anybody ever asks me why I prefer C++ to C, my answer will be simple: "It's becauseslejfp23(@#Q*(E*EIdc-SEGFAULT. Wait, I don't think that was nul-terminated." -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.python.org/pipermail/python-ideas/attachments/20140221/3c79e6ec/attachment.html>
- Previous message: [Python-ideas] Method chaining notation
- Next message: [Python-ideas] Method chaining notation
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
More information about the Python-ideas mailing list