6

I'm looking for the best recipie to allow inline definition of functions, or multi-line lambda, in python.

For example, I'd like to do the following:

def callfunc(func):
   func("Hello")

>>> callfunc(define('x', '''
...     print x, "World!"
... '''))
Hello World!

I've found an example for the define function in this answer:

def define(arglist, body):
    g = {}
    exec("def anonfunc({0}):\n{1}".format(
        arglist,
        "\n".join("    {0}".format(line) for line in body.splitlines())), g)
    return g["anonfunc"]

This is one possible solution, but it is not ideal. Desireable features would be:

  • be smarter about indentation,
  • hide the innards better (e.g. don't have anonfunc in the function's scope)
  • provide access to variables in the surrounding scope / captures
  • better error handling

and some things I haven't thought of. I had a really nice implementation once that did most of the above, but I lost in unfortunately. I'm wondering if someone else has made something similar.

Disclaimer:

I'm well aware this is controversial among Python users, and regarded as a hack or unpythonic. I'm also aware of the discussions regaring multi-line-lambdas on the python-dev mailing list, and that a similar feature was omitted on purpose. However, from the same discussions I've learned that there is also interest in such a function by many others.

I'm not asking whether this is a good idea or not, but instead: Given that one has decided to implement this, (either out of fun and curiosity, madness, genuinely thinking this is a nice idea, or being held at gunpoint) how to make anonymous define work as close as possible to def using python's (2.7 or 3.x) current facilities?

Examples:

A bit more as to why, this can be really handy for callbacks in GUIs:

# gtk example:
self.ntimes = 0
button.connect('clicked', define('*a', '''
    self.ntimes += 1
    label.set_text("Button has been clicked %d times" % self.ntimes)
''')

The benefit over defining a function with def is that your code is in a more logical order. This is simplified code taken from a Twisted application:

# twisted example:
def sayHello(self):
    d = self.callRemote(HelloCommand)
    def handle_response(response):
        # do something, this happens after (x)!
        pass
    d.addCallback(handle_response) # (x)

Note how it seems out of order. I usually break stuff like this up, to keep the code order == execution order:

def sayHello_d(self):
    d = self.callRemote(HelloCommand)
    d.addCallback(self._sayHello_2)
    return d

def _sayHello_2(self, response):
    # handle response
    pass

This is better wrt. ordering but more verbose. Now, with the anonymous functions trick:

d = self.callRemote(HelloCommand)
d.addCallback(define('response', '''
    print "callback"
    print "got response from", response["name"]
'''))
Community
  • 1
  • 1
jdm
  • 9,470
  • 12
  • 58
  • 110
  • 12
    Ick! Just define a normal function; this makes debugging so much the harder. – Martijn Pieters Mar 27 '13 at 11:27
  • 2
    I vehemently detest this. – jamylak Mar 27 '13 at 11:30
  • 1
    You'd better use callbacks and/or decorator rather than python code in strings. – El Bert Mar 27 '13 at 11:30
  • I don't see how the code is in "more logical order". I'm not a GTK user but when using `Qt` I pretty much always connect signals/events to methods of the object. This allow easier future refactoring/extending and allow to use the method in different situations(being more DRY). – Bakuriu Mar 27 '13 at 11:39
  • Got to agree across the board here - Just define a normal function. That way you are more likely to reuse code, and a host of other benefits (actually getting your IDE's help with code highlighting, speed, readability, etc...). – Gareth Latty Mar 27 '13 at 11:50
  • You may want to switch to lisp ^^. – christophe31 Mar 27 '13 at 11:54
  • 4
    When in Rome, do as romans do. When in javascript, do as javascript does. When in python, do as python does. – Stefano Borini Mar 27 '13 at 12:01
  • 3
    Incredible. When posting, I thought I'll refrain from saying "I know this is controversial, and not the pythonic way of doing things. Still, I find this an interesting problem, and would like a solution. Please *don't* tell me not to do this, but answer the actual question." Seems like a disclaimer like that is always neccessary on SO. – jdm Mar 27 '13 at 12:03
  • Some questions just can't be productively asked, and for good reason. – Karl Knechtel Mar 27 '13 at 12:16
  • 3
    @Karl Knechtel and others: Why has this been downvoted on and *closed*? I understand that the technique I'm asking about is not best practice, but I believe the question itself is valid on SO. It's like asking "How do I get a variable given its name as a string in PHP?" - probably not a good idea, but nevertheless answerable. If you have a concrete suggestion how to improve the question, please tell me (e.g. break it up in several questions). Otherwise please reopen it. – jdm Mar 27 '13 at 13:52
  • 2
    Regarding twisted specifically, [inline callbacks](http://hackedbellini.org/development/writing-asynchronous-python-code-with-twisted-using-inlinecallbacks/) look quite pythonic. – georg Mar 27 '13 at 17:46
  • 1
    @thg435 That's true, inline callbacks very nice and give you pretty clean code. I should be using them more often. – jdm Apr 04 '13 at 19:47
  • 1
    I agree with the orignal poster, that lambdas are unnecessarily limited in Python. Yes, multiline lambdas can lead to ugly programming. But having to use a "def" everywhere, where one needs just a two-line callback function, also leads to ugly programming. – Kai Petzke Aug 04 '20 at 09:12
  • "I had a really nice implementation once that did most of the above, but I lost in unfortunately." Since it's been a while, have you by any chance re-discovered the solution, or found one that works better than your example? – Felix Fourcolor Jul 05 '23 at 08:17

1 Answers1

-1

If you come from a javascript or ruby background, python's abilities to deal with anonymous functions may indeed seem limited, but this is for a reason. Python designers decided that clarity of code is more important than conciseness. If you don't like that, you probably don't like python at all. There's nothing wrong about that, there are many other choices - why not to try a language that tastes better to you?

Putting chunks of code into strings and interpreting them on the fly is definitely a wrong way to "extend" a language, just because none of tools you're working with - from syntax highlighters to the python interpreter itself - would be able to deal with "stringified" code in a sensible way.

To answer the question as asked: what you're doing there is essentially an attempt to construct some better-than-python programming language and compile it to python on the fly. The idea is not new in the world of scripting languages and can be productive or not (CoffeeScript is an example of a successful implementation), but your very approach is wrong. format() not the tool you're looking for when working with code. If you're writing a compiler, do it properly: use a parser (e.g. pyparsing) to read your code in an AST, walk through the AST to generate python code (or even bytecode), catch syntax errors as you go and take measures to provide better runtime feedback (e.g. error context, line numbers etc). Finally, make sure your compiler works across different python versions and implementations.

Or just use ruby.

georg
  • 211,518
  • 52
  • 313
  • 390