But i’d like to be able to write lines like these: These are expressions which create a value,
which is apparently NOT stored in a variable
solid *3
other_solid - solid
instead of wasting them, I’d like to collect them in an array and display send it to the display engine
after python evaluation has finished.
Is there some way how i can collect these orphaned expressions for later use?
Yes, I understand the idea!
But in this case I am especially lazy , I don’t want to have this explicit array and want python to do this task for me.
Background is:
in original openscad you can also write:
cube();
here the result is also considered without any variable assignment.
@gsohler, in the following example statement, would width * 2 + 4 be considered a result that was not assigned and that you wish to catch?:
solid = make_nice_solid(width * 2 + 4)
While the result was passed to a function and presumably used to do some work, it was not assigned to a variable. For clarification, perhaps we need to be provided with some additional examples of statements that contain expressions that need to be caught.
you are right , I did not express myself good enough
I am interested in expressions which are not reused. this means neighter assigned nor passed to a function.
in the example :
‘’‘’
solid = make_nice_solid(width * 2 + 4)
‘’’
I don’t want to catch anything
I am basically interested in Statement Results on the stack toplevel which are created and could be immediately fed to the garbage collector, because nothing makes use of them
I am not exactly sure if print also returns something, but in case it did, this is something I want to collect
@gsohler Is this mean to be happening with a python script/program (i.e., a *.py file)? This would have to be done via some magic – it is not something that python does automatically (and probably not a good idea, honestly!).
But if you mean this to be happening at the command line, then if you run under ipython or jupyter, all of the individual results can be collected and accessed later as an array Out[] (but then you would have to be careful to put each into its own cell).
The print function always returns the value None, regardless of what is output. Nothing needs to make use of it. However, is that really something you would need to collect?
If garbage collection is a concern, there’s actually nothing to worry about; if a value is simply abandoned, the garbage collector will do its job without your having to do anything to invoke it.
By that, do you mean the None that is returned by the print function? Note that quite a number of built-in functions return None. You may wind up with a collection that contains lots of None values.
In Python itself, as you know, those returned values are just dropped since they’re not assigned to anything. But you could do this perhaps by defining your own REPL keeping track of the value of any expression (if not None). So, you’d have to add a little layer to your embedded Python to go through this slightly modified REPL. Only the exec function needs to be overridden, and you only need to do something special for non-assignments (which can be detected most easily probably by using ast.parse), since assignments are already cached in the globals dictionary.
$ ipython3
Python 3.11.5 (main, Aug 24 2023, 15:09:45) [Clang 14.0.3 (clang-1403.0.22.14.1)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.16.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: x=2
In [2]: y=7
In [3]: x*y
Out[3]: 14
In [4]: 1+x
Out[4]: 3
In [5]: 37.5
Out[5]: 37.5
In [6]: size=137
In [7]: print([val for val in Out.values() if val is not None])
[14, 3, 37.5]
Hans, this sounds very interesting. I feel that i need something like that.
As mentioned before, i use embedded python linking to libpython.
Actually I call PyRun_String somewhere in my program.
How can I overwrite the exec function ? Do I need to dynamically load libpython using dlopen,
or are there easier ways ?
Is it possible to provide me with little more detailled strategy ?
The strategy is actually exactly the same one that iPython (or Jupyter) is also following in its REPL.
You could either embed this in the C-code or perhaps as a pure-Python “shell” which you would always load in your embedded code.
As pure-Python code you could do sth like this (I’m not adding much error checking here, so you might want to make this more robust):
import ast
Out = dict()
def my_exec(stm, globals=None, locals=None, /):
try:
ast.parse(stm, mode="eval")
except SyntaxError: # is triggered by assignments
# may want to double-check other possible exceptions - off the top of my head
# I don't know if this code is sufficient as-is
return exec(stm, globals, locals)
n = len(Out)
stm = f"_t{n} = {stm}"
return exec(stm, globals, locals)
And you would then call this always with locals=Out.
So, you’re full shell would simply be running this extra REPL in the normal Python interpreter:
read input
call my_exec, instead of builtin exec
Don’t know how iPython implements this - So it might be worthwhile to quickly check that. Perhaps they have built this in to their embedded interpreter in the C-level. If so, then you could have a peek how they keep track of Out.
This seems curious - why use mode=“eval” and then exec()? Wouldn’t it be more normal to either use mode=“exec” (the default), or to subsequently eval() the result?
You’re right. This was sth I just quickly hacked out. It would indeed be simpler to use eval… My thought process was a bit convoluted…
So, you could do sth like:
Just for understanding the strategy: when my_eval gets a python program with several statements, functions, will this eval all at once, or once for each statement ?
Now i am happy with my solution. However i need to develop a patch to apply it with every new python version. doubt that a PEP will make its way in near future