6

I am trying to define an object master whose attribute/method decide can be called with an expression ex, but evaluation of that expression is postponed until some logic in decide asks for it (and it may even not do so). So, in code like this:

master.decide(ex)

it should be possible for ex to remain unevaluated if the logic in decide says so. In the code, ex stands for "arbitrary expression supplied by the client of my library." It can be as simple as 1 + 2 (where I wouldn't really care about lazy evaluation) to as complex as do_http_request().delete_record_from_database() (where I certainly do care).

Is such a thing possible? I am flexible on the syntax side, so if a solution exists which has to involve extra operators etc. around ex, I can consider it. But I would like ex to remain an expression.

I was thinking about (ab)using short-circuiting operators like and and or, but there doesn't seem to be a way to make those return something other than a boolean value.

The best I could come up with is wrapping ex in a lambda:

master.decide(lambda: ex)

def decide(ex):
  if decide_to_do_it():
    result = ex()
    somehow_use(result)

Or passing it as a string and using eval:

master.decide('ex')

def decide(ex):
  if decide_to_do_it():
    result = eval(ex)
    somehow_use(result)

(Yes, this one would have scoping issues).

Is there perhaps a magic function, trick, or something else which would allow me to keep ex as a plain expression?

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Check this link: http://stevenloria.com/lazy-evaluated-properties-in-python/ and this one http://stackoverflow.com/questions/5295038/python-lazy-evaluator – Andrii Rusanov Feb 04 '16 at 08:24
  • It is unclear what `ex` is in your quest. Is it a previously-coded function to call? A user-passed string to evaluate? In the first ase, you don't need a lambda, you can just call `master.decide(ex)`, python functions being objects you can pass as arguments. – DainDwarf Feb 04 '16 at 08:25
  • @DainDwarf Tried to clarify – Angew is no longer proud of SO Feb 04 '16 at 08:34
  • Expressions are not objects, therefore they cannot be passed as arguments. Passing a callable (lambda or another function) and calling it inside consuming function is a way to go. – Łukasz Rogalski Feb 04 '16 at 08:42

2 Answers2

5

You can define a coroutine using yield here

def decide():
  ex=yield()
  if ex:
     result = ex()
     somehow_use(result)

master=decide()
next(master)
master.send(ex1)
master.send(ex2)
vks
  • 67,027
  • 10
  • 91
  • 124
  • Let's say the client wants an `ex` of the form `insert_record_into_database()`. Can you please explain how this will prevent such an expression from evaluating prematurely? – Angew is no longer proud of SO Feb 04 '16 at 09:05
  • @Angew after the first `next` call the program will keep on waiting on `ex=yield()` and will move ahead only when u do a `send` operation.After that the program will again keep waiting till u do another `send`.I guess that is what u want.You can customise it as per ur needs – vks Feb 04 '16 at 09:09
  • From what I understand, this will always evaluate `ex` when the client sends it. That is what I am trying to prevent. I guess there is no way. – Angew is no longer proud of SO Feb 04 '16 at 09:14
  • @Angew u can control `ex` from outside and rather send a `single` to `yield` which will run the function.u can control everything here. – vks Feb 04 '16 at 09:22
3

You can pass a callable, just a function without arguments that is called when the expression needs to be evaluated.

Your lambda is one way to make such a callable, but you could just as easily pass a normal function or an instance method or whatever.

That's is the perfectly normal way to do it.

RemcoGerlich
  • 30,470
  • 6
  • 61
  • 79
  • Would you mind giving an example that doesn't use a `lambda`. The problem is that @Agnew wants to delay evaluation of an **expression**, not delay the calling of a function. How can you pass an **expression** to anything and prevent it from being evaluated? Isn't it the case that whenever you call a function (decorated or not) the argument is evaluated before the (possibly decorated) function is called? The only way I can see doing it is to wrap the expression in something like `lambda` whose evaluation leaves the expression unevaluated. What options besides `lambda` are there? – RussAbbott May 19 '19 at 16:46
  • @RussAbbott: yes, you have to wrap the expression into a function. Lambda is one such way, or using a def: statement, possibly as a class method. What you mean when you say "something like" lambda. – RemcoGerlich May 19 '19 at 18:20
  • @ RemcoGerlich: Would you mind providing an example of the `def`-method alternative. I don't think you can pass a function *definition* (using `def` rather than `lambda`) or a class *definition* as an argument. Thanks. – RussAbbott May 20 '19 at 04:28