B4J Tutorial [PyBridge] The very basics

Status
Not open for further replies.
PyBridge is a framework that allows accessing Python libraries from within B4J.

It works by starting a Python process that connects to the B4J process. The Python process receives commands from the B4J process and executes them.
From the developer perspective it is very similar to accessing native APIs with JavaObject or with inline Java. There are two important differences:
1. The execution happens in a different process.
2. Python is nicer than Java. Especially with small code snippets and dynamic context.

1739776057364.png

All the communication happens under the hood with AsyncStreams + B4XSerializator. This is important as not all types can be serialized (it is possible to extend the serialization process with custom types).

A command looks like this:

1739776176991.png


Note that the flow is generally unidirectional - B4J sends commands to Python. The commands are also unidirectional. The allows the commands to be queued, and B4J doesn't wait for the commands to actually be executed. It is very important in terms of performance.

Lets start with an example:
B4X:
Dim Tuple As PyWrapper = Py.WrapObject(Array(1, 2, 3, 4, 5))
Tuple.Print
Tuple.TypeOf.Print2("type is:", "", False)
Dim list As PyWrapper = Tuple.ToList
list.Run("append").Arg(6)
list.Print
Log("after")

Py.WrapObject - sends the object to Python. The result of sending a B4J array is a Python Tuple. Tuple ~= read only array.
Tuple.Print - tells Python to print the object.
Tuple.TypeOf.Print2 - Get the type of the object and print it.
ToList - convert the object to a list. This is very useful in Python. All iterable objects can be converted to a list.
And now for something important:
B4X:
list.Run("append").Arg(6)
list.Run - starts a call to the append method. After this call we can add positional or named arguments using Arg or ArgNamed. This makes it easier to access Python methods that sometimes have many many arguments.
So the output of this code:
1739776979335.png

As you can see, the Python process output (captured by Shell library) is logged with blue color.
Note that "after" was logged before everything else. As previously stated, the B4J process doesn't wait for the Python process. It runs ahead.
If for some reason we do want to wait for all commands to complete:
B4X:
list.Print
Wait For (Py.Flush) Complete (Success As Boolean)
Log("after")
Now Log("after") will only run after all previous commands were actually processed.

We have a list in the python process. We want to fetch it back to B4J. Tip: Don't fetch too much. It will slow down the execution.
B4X:
Wait For (list.Fetch) Complete (list As PyWrapper)
Log(list.Value)
Dim MyList As List = list.Value
Don't forget to call Fetch as the execution will not continue without it.

Calling PyWrapper.Value will raise an exception if there was an error in the Python process. You can catch it and there are also methods to check it.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
There are two main ways to interact with the Python process - the various "Run" methods, which are similar to making calls with JavaObject and the family of "RunCode" methods that are more similar to using inline Java.
PyBridge includes several code snippets to make it simple to wrap Python code in reusable subs.

Lets start with the task of summing two numbers. In Python the + operator can be executed using a method call.
B4X:
Dim x As PyWrapper = Py.WrapObject(100)
x.OprAdd(200).Print '300
The power of PyBridge starts here:
B4X:
Dim x As PyWrapper = Py.WrapObject(100)
Dim y As PyWrapper = x.OprAdd(200) '300
Dim z As PyWrapper = x.OprAdd(y) '400 <------------------
z.Print
Although we didn't fetch the value of y, we can still use it.

Now for RunCode. We will create a sub with two parameters:
1739779238981.png

B4X:
Private Sub Sum (X As Object, Y As Object) As PyWrapper
    Dim Code As String = $"
def Sum (X, Y):
    return X + Y
"$
    Return Py.RunCode("Sum", Array(X, Y), Code)
End Sub

B4X:
Dim Z As PyWrapper = Sum(100, 200)
Z.Print '300
Z = Sum(Z, Z)
Z.Print  '600

Again, we don't need to fetch the values in order to use them for further calls.

When we call Py.RunCode - the code is executed once. On future calls, the function (or any other member) is retrieved and called with the passed parameters.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Requirements:
PyBridge will work with Python 3.10+. On Windows, Mac and Linux.

Python 3.10 is compatible with Windows 10+. If you are running Windows 7 then you might be able to get it working with this unofficial distribution: https://github.com/adang1345/PythonWin7/tree/master/3.12.8
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Python runtime - Where does it come from? How is it located at runtime? How to distribute it with a standalone package?

B4J comes preinstalled with a Python runtime. You can find it under: <drive>:\Program Files\Anywhere Software\B4J\Libraries\Python
This is the internal Python runtime. You can run PyBridge programs with the internal runtime, however as it is stored in a read-only folder, is has some limitations.

It is recommended to make a copy of this folder in a different location and then configure the Python field under Tools - Configure Paths:
1742202993859.png

This will become the global Python.
At runtime, the path to the local Python is passed to Py.CreateOptions:
B4X:
Dim opt As PyOptions = Py.CreateOptions("Python/python/python.exe")
If it doesn't exist then the global Python will be used. And if the global Python wasn't configured then the internal Python will be used.
Note that the global Python is internally set using an environment variable named B4J_PYTHON, which points to the folder where python.exe is located.

The following comment links are included in PyBridge template:
B4X:
'Create a local Python runtime:   ide://run?File=%WINDIR%\System32\Robocopy.exe&args=%B4X%\libraries\Python&args=Python&args=/E
'Open local Python shell: ide://run?File=%PROJECT%\Objects\Python\WinPython+Command+Prompt.exe
'Open global Python shell - make sure to set the path under Tools - Configure Paths. Do not update the internal package.
'ide://run?File=%B4J_PYTHON%\..\WinPython+Command+Prompt.exe
Link #1 - Makes a copy of the internal Python and creates a local Python. Local Python = project specific package.
Link #2 - Opens the local Python shell. You can then run pip install <library> to install Python libraries in the local Python runtime.
Link #3 - Opens the global Python shell and allows installing libraries in the global Python.

The project template includes a custom build action that copies the local Python package to the installation folder:
B4X:
#CustomBuildAction: after packager, %WINDIR%\System32\robocopy.exe, Python temp\build\bin\python /E /XD __pycache__ Doc pip setuptools tests
 
Status
Not open for further replies.
Top