Post Reply 
Passing values from PPL to Python and vice versa - an alternative to AVars
12-03-2023, 09:39 PM (This post was last modified: 12-04-2023 07:26 AM by komame.)
Post: #1
Passing values from PPL to Python and vice versa - an alternative to AVars
During my tests of Python, I found a solution that allows passing values from Python to PPL without the need for using AVars. It enables the exchange of information between both environments even in simple hpprgm programs that use the PPL wrapper and I would like to share this with you.

This solution still utilizes hpprime.eval(), but instead of referring to AVars, it writes values directly into variables in the PPL program. However, for this to be possible, the variable on the PPL side must be local in the global scope of the program or exportable. In the case of a local variable, it is sufficient to declare it in the header of the PPL program (outside of any function).
It is very important to use a prefix with the PPL program's name when indicating PPL variables on the Python side (this should not be confused with the exported function name).

In the examples below, I assumed that the program that uses the PPL wrapper for Python is named "PYPPL" (and that's the prefix I mentioned).

Example 1
Code:
LOCAL pyresult;

#PYTHON pycode
from hpprime import eval as ppleval;
numbers = range(10)
ppleval('PYPPL.pyresult:=%s' %numbers);
#END

EXPORT PPL_PY() 
BEGIN
  PYTHON(pycode);
  RETURN pyresult;
END;

The result in "pyresult" variable:
[0 1 2 3 4 5 6 7 8 9]

Note that one of the known issues in Prime is the transfer of numerical values above three digits (more than 999) to Python as a parameter (via the argv[]) - this leads to a problem with garbage appearing in the value on the Python side. However, the methods described above, instead of passing the value, allow you to directly read on the Python side the content of a variable in which you put the value to be transferred. This allows you to pass any values regardless of their size.

Example 2
Code:
LOCAL value;

#PYTHON pycode
from hpprime import eval as ppleval
fromPPL = ppleval('PYPPL.value')
print('The value read in Python: %s' %fromPPL)
#END

EXPORT PPL_PY() 
BEGIN
  PRINT();
  value:=1234567890;
  PYTHON(pycode);
END;

The printed result:
The value read in Python: 1234567890.0

Additionally, you can even pass the name of a variable to Python (somewhat like simulating passing by reference) and read the contents of that variable on the Python side, then return the result back to that variable.

Example 3
Code:
LOCAL var1,var2;

// shuffle string
#PYTHON pycode
from sys import argv
from hpprime import eval as ppleval
from urandom import randint

def shuffle(string):
    char_list = list(string)
    length = len(char_list)
    for i in range(length):
        random_index = randint(0, length - 1)
        char_list[i], char_list[random_index] = char_list[random_index], char_list[i]
    return ''.join(char_list)

string = ppleval('PYPPL.%s' %argv[0])
shuffled = shuffle(string)
ppleval('PYPPL.%s := "%s"' %(argv[0],shuffled))
#END

EXPORT PPL_PY() 
BEGIN
  PRINT();
  var1:="FIRST";
  PYTHON(pycode,"var1");
  var2:="SECOND";
  PYTHON(pycode,"var2");
  RETURN {var1,var2}; // the results in the same variables
END;

This way, it is also possible to call local PPL functions from Python.

Example 4
Code:
#PYTHON pycode
from hpprime import eval as ppleval
from urandom import randint

n = randint(2,10);
print('random value by Python: %d' %n)
nxn = ppleval('PYPPL.PPL_SUBROUTINE(%d)' %n)
print('calculated by PPL subroutine: %d * %d = %d' %(n,n,nxn))
#END

EXPORT PPL_PY() 
BEGIN
  PRINT();
  PYTHON(pycode);
END;

PPL_SUBROUTINE(n)
BEGIN
  RETURN n*n;
END;

Even passing and receiving the result as a matrix works, because the form of the matrix after conversion to a string is the same in both languages.
In general, this eliminates some limitations associated with Python on the HP Prime and opens up some new possibilities.

I hope this information is useful.

Best wishes,
Piotr
Find all posts by this user
Quote this message in a reply
12-05-2023, 06:16 PM (This post was last modified: 12-05-2023 06:48 PM by Guenter Schink.)
Post: #2
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
Quote:Note that one of the known issues in Prime is the transfer of numerical values above three digits (more than 999) to Python as a parameter (via the argv[])

This "problem" is dependent on the digit grouping setting in the Home environment. To avoid it, at least partially, look at "HSeparator" which determines digit grouping.

In one of your examples you could add these three lines as indicated:
Code:

LOCAL pyresult;

#PYTHON pycode
from hpprime import eval as ppleval;
numbers = range(10)
ppleval('PYPPL.pyresult:=%s' %numbers);
#END

EXPORT PPL_PY() 
BEGIN
  LOCAL TSeparator:=HSeparator; // store digit grouping for later reset
  HSeparator:=3;                // set to no digit grouping at all.
  PYTHON(pycode);
  HSeparator:=TSeparator;       // reset digit grouping to what it was before
  RETURN pyresult;
END;
]
Now the 3 digits limit is gone.(haven't tested it though)
Günter

[Edit]
BTW: your program doesn't return the list. Seems that in:
"PYPPL.pyresult:=%s' %numbers" the PYPPL needs to be deleted and pyresult has to be declared as EXPORT instead of LOCAL. Or I did overlook something?
Find all posts by this user
Quote this message in a reply
12-05-2023, 09:09 PM (This post was last modified: 12-05-2023 09:19 PM by komame.)
Post: #3
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
(12-05-2023 06:16 PM)Guenter Schink Wrote:  [...]
This "problem" is dependent on the digit grouping setting in the Home environment. To avoid it, at least partially, look at "HSeparator" which determines digit grouping.

In one of your examples you could add these three lines as indicated:
Code:

LOCAL pyresult;

#PYTHON pycode
from hpprime import eval as ppleval;
numbers = range(10)
ppleval('PYPPL.pyresult:=%s' %numbers);
#END

EXPORT PPL_PY() 
BEGIN
  LOCAL TSeparator:=HSeparator; // store digit grouping for later reset
  HSeparator:=3;                // set to no digit grouping at all.
  PYTHON(pycode);
  HSeparator:=TSeparator;       // reset digit grouping to what it was before
  RETURN pyresult;
END;
Now the 3 digits limit is gone.(haven't tested it though)

Regarding digit grouping, you're right.

(12-05-2023 06:16 PM)Guenter Schink Wrote:  BTW: your program doesn't return the list. Seems that in:
"PYPPL.pyresult:=%s' %numbers" the PYPPL needs to be deleted and pyresult has to be declared as EXPORT instead of LOCAL. Or I did overlook something?

Of course, it returns a list. Just set HSeparator to any of these values: 0, 1, 2, 3, 9, 10. You will see that the program will work exactly as I provided in the example and return a list (or more precisely, a vector). This works for both EXPORT and LOCAL (declared outside of any function) variables.
Regarding the "PYPPL" prefix (the program name), it allows you to refer to local variables and call local PPL functions. Without this prefix, you can only refer to EXPORT variables and functions.
See the Example 4 => the PPL_SUBROUTINE() is a local function.

Piotr
Find all posts by this user
Quote this message in a reply
12-05-2023, 10:35 PM
Post: #4
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
Quote:Of course, it returns a list. Just set HSeparator to any of these values: 0, 1, 2, 3, 9, 10. You will see that the program will work exactly as I provided in the example and return a list (or more precisely, a vector). This works for both EXPORT and LOCAL (declared outside of any function) variables.
Regarding the "PYPPL" prefix (the program name), it allows you to refer to local variables and call local PPL functions. Without this prefix, you can only refer to EXPORT variables and functions.
See the Example 4 => the PPL_SUBROUTINE() is a local function.

Sorry Piotr, I believe it doesn't. To verify this, you first have to delete the user variable "pyresult" because it certainly contains what's left over from the various checks you already have done.
In Home call the Vars Menu, select "User", "User Variables" and delete what's in there. Then run this program again.

Alternatively change "numbers= range(10)" to "numbers = range(7)" and check the result.
In addition I think it's always wise to check the result of an hpprime.eval() statement. I.e change the line:
"ppleval('PYPPL.pyresult:=%s' %numbers);"
to:
"check=ppleval('PYPPL.pyresult:=%s' %numbers); "
and have a look what a consecutive line:
"print(check)" produces. It simply shows you that you have produced "Error: Syntax Error" in the Home environment.

A minor thing: It's superfluous to end a Python line with ";"

The fact that you always see the seemingly correct result is owed to the the variable "pyresult" never being changed during your various tests.

I didn't have a closer look to sample #4, but "print(nxn)" after "nxn= ppleval(...)" also reveals that PPL environment is not happy

Günter
Find all posts by this user
Quote this message in a reply
12-06-2023, 07:56 AM
Post: #5
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
(12-05-2023 10:35 PM)Guenter Schink Wrote:  Sorry Piotr, I believe it doesn't. To verify this, you first have to delete the user variable "pyresult" because it certainly contains what's left over from the various checks you already have done.
In Home call the Vars Menu, select "User", "User Variables" and delete what's in there. Then run this program again.

Hi Günter

I still maintain that it works Smile Just test it on the emulator or on a real Prime. To convince you, I inform you that I restored the factory settings on my Prime, so I have no variables other than the built-in ones. Then I loaded this program, and everything still works as before.

The whole phenomenon of this solution lies in the fact that Prime, from the PPL level, can read local variables from other PPL programs, and they don't have to be of the EXPORT type. In the case of Python on Prime, I use this feature of the PPL language and simply read and write to/from local variables. Calling local functions works the same way. It's not well-documented, but it has been in PPL since the beginning.


(12-05-2023 10:35 PM)Guenter Schink Wrote:  Alternatively change "numbers= range(10)" to "numbers = range(7)" and check the result.

I can change the range value, and each time the list displays correctly.

(12-05-2023 10:35 PM)Guenter Schink Wrote:  In addition I think it's always wise to check the result of an hpprime.eval() statement. I.e change the line:
"ppleval('PYPPL.pyresult:=%s' %numbers);"
to:
"check=ppleval('PYPPL.pyresult:=%s' %numbers); "
and have a look what a consecutive line:
"print(check)" produces. It simply shows you that you have produced "Error: Syntax Error" in the Home environment.

The "print(check)" produces the same list (correctly).

(12-05-2023 10:35 PM)Guenter Schink Wrote:  A minor thing: It's superfluous to end a Python line with ";"

Yes, I'm aware of that. Just switching between programming languages like PPL, Python, C#, C++ or ABAP, I might inadvertently insert an extra character that is required in another language (but it's not a syntax error).

(12-05-2023 10:35 PM)Guenter Schink Wrote:  The fact that you always see the seemingly correct result is owed to the the variable "pyresult" never being changed during your various tests.

I didn't have a closer look to sample #4, but "print(nxn)" after "nxn= ppleval(...)" also reveals that PPL environment is not happy

The "PYPPL.pyresult" is a valid expression in PPL as long as there is a PYPPL program that contains a local variable (in the header, not inside a function) named "pyresult". In reality, there is no difference between LOCAL and EXPORT variables other than the fact that EXPORT appears in "User variables" and can be referenced directly by name, while LOCAL is not visible in "User variables" and requires a prefix with the program's name to reference.
Here's a simple example:

- create new program "PRG"
- enter:
Code:
LOCAL MYVAR:="ABC";

EXPORT PRG()
BEGIN
END;
- go to HOME
- enter:
PRG.MYVAR

and you see the "ABC" as a result.

Best wishes,
Piotr

Piotr Kowalewski
Find all posts by this user
Quote this message in a reply
12-06-2023, 11:46 AM
Post: #6
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
Hi Piotr,
thanks for your patience. Careful reading would avoid wrong conclusions, like mine this time.

I didn't observe your statement of naming the file as "PPLPY", but only referred to the name of the main routine "PPL_PY". As a result I gave the file an arbitrary name and that of course couldn't work.

Thanks again for your insistence which made me read the entire stuff more carefully. Following the "manual" Smile made it work. A very interesting approach.

Cheers, Günter
Find all posts by this user
Quote this message in a reply
12-08-2023, 06:45 PM (This post was last modified: 12-08-2023 06:49 PM by komame.)
Post: #7
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
Hi Günter,

I'm glad that you managed to make my examples work Smile
I noticed another additional benefit of using the approach I proposed, which concerns converting values to numbers. When you pass a numeric value as a parameter using the standard method in a PYTHON command, e.g., PYTHON(pycode, 12345.456), the argv[0] on the Python side is of the string type, not float. Furthermore, the content of argv[0] depends on the HSeparator settings and varies accordingly. For values higher than 999, you must use HSeparator=3 for calculations involving argv[0]. Without this, the values can't be converted to a number, as float(argv[0]) will raise a syntax error due to the digit grouping separator, leading to a failed conversion, even in the absence of the garbage. However, with my proposed method, a value of the float type is directly assigned to the Python variable, allowing for more HSeparator settings (0, 1, 2, 3, 9, 10), with each resulting in a correct number.

The following program demonstrates an attempt to convert the parameter passed in argv[0] to a number vs reading values directly from a PPL variable:
Code:
#pragma mode( separator(.,;) integer(h64) )
LOCAL value;

#PYTHON pycode1
from hpprime import eval as ppleval
value = ppleval('PYPPL.value')
print('\npycode1: ', value, type(value))
#END

#PYTHON pycode2
from sys import argv
value = argv[0];
print('pycode2: ', value, type(value))
try:
 flt = float(value)
 print('value: ', flt, end = '')
except ValueError:
 print('value: syntax error', end = '')
#END

EXPORT PPL_PY() 
BEGIN
  LOCAL hs;
  PRINT;
  FOR hs FROM 0 TO 3 DO
    HSeparator:=hs;
    value:=12345.456;
    PRINT("==> Separator: " + hs);
    PYTHON(pycode1);
    PYTHON(pycode2,value);
  END;
END;

   

Although some garbage appeared in certain iterations during the call using standard method, even without the garbage, the conversion would have failed due to digit grouping separators.
This indicates that reading values directly from a variable is much more flexible than passing them using the standard method.

regards,
Piotr
Find all posts by this user
Quote this message in a reply
12-08-2023, 07:37 PM
Post: #8
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
(12-08-2023 06:45 PM)komame Wrote:  PYTHON(pycode, 12345.456), the argv[0] on the Python side is of the string type, not float.
... For values higher than 999, you must use HSeparator=3

Why bother with HSeparator settings?
Why not do PYTHON(pycode, str(12345.456)) ?
Find all posts by this user
Quote this message in a reply
12-09-2023, 06:24 AM (This post was last modified: 12-09-2023 06:37 AM by komame.)
Post: #9
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
(12-08-2023 07:37 PM)Albert Chan Wrote:  Why bother with HSeparator settings?
Why not do PYTHON(pycode, str(12345.456)) ?

Well, it seems you're right, and in terms of passing values from PPL to Python, this is a sufficient solution. I wasn't aware that "str" always returns the result in the same format, but indeed, as a CAS function, it must ignore the Home=>Settings.
This implies that my approach is only useful in the case of passing/returning values in the other direction, that is from Python to PPL.
Find all posts by this user
Quote this message in a reply
01-07-2024, 04:02 AM
Post: #10
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
I actually took inspiration from your post to implement a feature in my simplex program. Thanks!

- neek
Find all posts by this user
Quote this message in a reply
01-30-2024, 08:38 PM
Post: #11
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
FYI if you go from Python to PPL with a complex number, you will have to substitute the Python imaginary indication 'j' with '*i' (CHAR(57347)) prior to passing it on to PPL.
And from PPL to Python you have to convert the string with '*i' to 'j'.
All within the Python script.

#Python to PPL conversion for a complex number
p=17j
p=str(p)
p=p.replace('j','*') #(CHAR(57347)
ppleval('PYPPL.pyresult:=%s' %p)

#PPL to Python string conversion for a complex number
p='17*i' # ie from argv[0]
p=complex(p.replace('*','j')) #(CHAR(57347)
Find all posts by this user
Quote this message in a reply
02-12-2024, 12:28 PM
Post: #12
RE: Passing values from PPL to Python and vice versa - an alternative to AVars
(01-30-2024 08:38 PM)gehakte_bits Wrote:  #Python to PPL conversion for a complex number
p=17j
p=str(p)
p=p.replace('j','*') #(CHAR(57347)
ppleval('PYPPL.pyresult:=%s' %p)

When passing numbers from Python to PPL (whether real or complex), it is best to use CAS.eval(), as this guarantees independence from formatting settings in HOME (treatment of comma or period as decimal separator, grouping, etc.).
Additionally, CAS.eval() correctly interprets numbers in exponential notation with a positive sign (e.g., 1e+2, 1ᴇ+2 - which is a standard notation in Python for floating-points number, but raises "Invalid expoonent" error in PPL), and for complex numbers, you can use the regular 'i'.

So the safest way to do this is as follows:
Code:
from hpprime import eval as ppleval

#Python to PPL conversion for a complex number
p=100.123+17j 
p=repr(p)
p=p.replace('j','i')
ppleval('PPLPROG.pyresult:=CAS.eval("%s")' %p)

Piotr Kowalewski
Find all posts by this user
Quote this message in a reply
Post Reply 




User(s) browsing this thread: 1 Guest(s)