For a larger project, I need to build a bridge between a Python and a Java programme. I decided to use Py4J and constructed a smaller problem to familiarise myself with the first steps. Java is started first. When Python is started, it continuously delivers a pair of values that are displayed graphically in Java. The Java programme has the option of changing a parameter in Python. Is there anything that can be improved?
Java main program:
package py4j.examples;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import py4j.examples.gui.AusgabeController;
public class PythonTrigonometrie extends Application {
/**
* @param args Programparameters
*/
public static void main(final String[] args) {
launch(args);
}
@Override
public void start(final Stage primaryStage) throws Exception {
final Scene scene = new Scene(new AusgabeController(primaryStage).getView(), 640, 480);
primaryStage.setTitle("JavaFX Py4J Beispiel");
primaryStage.setAlwaysOnTop(true);
primaryStage.setScene(scene);
primaryStage.show();
}
}
The java controller:
package py4j.examples.gui;
import javafx.application.Platform;
import javafx.scene.Parent;
import javafx.stage.Stage;
import py4j.ClientServer;
public class AusgabeController extends GatewayImpl {
private final ClientServer clientServer = new ClientServer(null);
private final AusgabeView view = new AusgabeView();
public AusgabeController(final Stage stage) {
view.addFrequenzListener((obs, oldValue, newValue) -> {
notifyAllListeners(new Object[] { "frequenz", newValue });
});
clientServer.getPythonServerEntryPoint(new Class[] { IGateway.class });
stage.setOnCloseRequest(e -> {
notifyAllListeners("close");
});
}
/**
* @return the view
*/
public Parent getView() {
return view;
}
@Override
public void setOnClose() {
Platform.runLater(() -> clientServer.shutdown());
}
}
The JavaFX Viewer:
package py4j.examples.gui;
import java.util.Map;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.SpinnerValueFactory.DoubleSpinnerValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
public class AusgabeView extends StackPane {
private static final double SCALE = 100.0;
private final static Line lineSinus = new Line(SCALE, 0, SCALE, 0);
private final static Line lineCosinus = new Line(0, 0, SCALE, 0);
private final static Line lineHypothenuse = new Line(0, 0, 0, 0);
private final Circle circle = new Circle(0, 0, SCALE, Color.TRANSPARENT);
private final Group gArc = new Group(circle, lineHypothenuse, lineSinus, lineCosinus);
private final Text textFreq = new Text("f:");
private final SpinnerValueFactory<Double> frequenzValueFactory = //
new SpinnerValueFactory.DoubleSpinnerValueFactory(0.01, Double.MAX_VALUE, 1d);
private final Spinner<Double> spinnerFrequenz = new Spinner<>();
private final HBox hBox = new HBox(10, textFreq, spinnerFrequenz);
private final VBox vBox = new VBox(20, gArc, hBox);
public AusgabeView() {
circle.setStroke(Color.BLACK);
lineSinus.setStroke(Color.RED);
lineCosinus.setStroke(Color.BLUE);
((DoubleSpinnerValueFactory) frequenzValueFactory).setAmountToStepBy(0.5);
spinnerFrequenz.setValueFactory(frequenzValueFactory);
GatewayImpl.setValueProperty(spinnerFrequenz.valueProperty());
spinnerFrequenz.setEditable(true);
spinnerFrequenz.setPrefWidth(70);
hBox.setPadding(new Insets(0, 0, 0, 55));
getChildren().add(vBox);
}
/**
* @param value Map with sin and cos value
*/
public static void setArc(final Map<String, Double> value) {
final Double sin = value.get("sin") * -SCALE;
final Double cos = value.get("cos") * SCALE;
lineCosinus.setEndX(cos);
lineSinus.setStartX(cos);
lineSinus.setEndX(cos);
lineSinus.setEndY(sin);
lineHypothenuse.setEndX(cos);
lineHypothenuse.setEndY(sin);
}
/**
* @param changeListener to set to spinnerFrequenz
*/
public void addFrequenzListener(final ChangeListener<Double> changeListener) {
spinnerFrequenz.valueProperty().addListener(changeListener);
}
/**
* @return the root node
*/
public Node getRootNode() {
return vBox;
}
}
The java gateway interface:
package py4j.examples.gui;
import java.util.Map;
public interface IGateway {
/**
* @param listener PhytonListener to register
*/
void registerListener(final IPhytonListener listener);
/**
* @param listener PhytonListener to remove
*/
void removeListener(final IPhytonListener listener);
/**
* @param source source id
*/
void notifyAllListeners(Object source);
void setOnClose();
/**
* @return the frequenz
*/
double getFrequenz();
/**
* @param value Map<String, Double> to set
*/
void setValue(final Map<String, Double> value);
}
The implementation of java gateway:
package py4j.examples.gui;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
public class GatewayImpl implements IGateway {
private final static List<IPhytonListener> listeners = new ArrayList<>();
private static ReadOnlyObjectProperty<Double> frequenzProperty;
@Override
public void registerListener(final IPhytonListener listener) {
Platform.runLater(() -> listeners.add(listener));
}
@Override
public void removeListener(final IPhytonListener listener) {
listeners.remove(listener);
}
@Override
public void notifyAllListeners(final Object source) {
for (final IPhytonListener listener : listeners) {
listener.notify(source);
}
}
@Override
public void setValue(final Map<String, Double> value) {
Platform.runLater(() -> AusgabeView.setArc(value));
}
@Override
public double getFrequenz() {
final double freq = frequenzProperty.get();
return freq;
}
/**
* @param frequenzProperty the valueProperty to set
*/
public static void setValueProperty(final ReadOnlyObjectProperty<Double> frequenzProperty) {
GatewayImpl.frequenzProperty = frequenzProperty;
}
@Override
public void setOnClose() {/* Overridden in Controller */
}
}
The java python listener:
package py4j.examples.gui;
public interface IPhytonListener {
Object notify(Object source);
}
And the Python program:
from py4j.clientserver import ClientServer, JavaParameters, PythonParameters
import math
import time
DELTA_ARC = math.pi / 180
DELTA_TIME = 1.0 / 360
FULL_CIRCLE = math.pi * 2
class PythonListener(object):
def __init__(self, gateway):
self.gateway = gateway
def notify(self, obj):
if obj == "close":
Trigonometrie.end()
return
elif obj[0] == "frequenz":
frequenz = obj[1]
Trigonometrie.setFrequenz(frequenz)
return
class Java:
implements = ["py4j.examples.gui.IPhytonListener"]
class Trigonometrie(PythonListener):
def start(self, frequenz=1, phi=0):
Trigonometrie.frequenz = frequenz
Trigonometrie.phi = phi
Trigonometrie.running = True
while Trigonometrie.running:
# phi = phi if phi <= FULL_CIRCLE else phi - FULL_CIRCLE
sinus = math.sin(phi)
cosinus = math.cos(phi)
try:
if Trigonometrie.running == True:
input_dict = gateway.jvm.java.util.HashMap()
input_dict["sin"] = sinus
input_dict["cos"] = cosinus
java_app.setValue(input_dict)
phi += DELTA_ARC
time.sleep(DELTA_TIME / Trigonometrie.frequenz)
except:
Trigonometrie.running = False
@staticmethod
def setFrequenz(frequenz):
Trigonometrie.frequenz = frequenz
@staticmethod
def end():
Trigonometrie.running = False
class Java:
implements = ["py4j.examples.gui.IGateway"]
gateway = ClientServer(
java_parameters=JavaParameters(),
python_parameters=PythonParameters())
java_app = gateway.jvm.py4j.examples.gui.GatewayImpl()
listener = PythonListener(gateway)
java_app.registerListener(listener)
frequenz = java_app.getFrequenz()
trigonometrie = Trigonometrie(listener)
trigonometrie.start(frequenz)
java_app.setOnClose();
gateway.shutdown()
-
\$\begingroup\$ The protocol is so simple that I would first try running one program as a subprocess of the other and sending stdout content. Have you tried this? \$\endgroup\$Reinderien– Reinderien2025年06月30日 12:15:26 +00:00Commented Jun 30 at 12:15
-
\$\begingroup\$ @Reinderien No, I haven't. I'll do it. \$\endgroup\$Misi– Misi2025年06月30日 13:29:28 +00:00Commented Jun 30 at 13:29
2 Answers 2
The while
loop runs on the main thread, blocking any chance of clean interruption.
Run the start()
loop in a background thread using Python’s threading.Thread
import threading
def start(self, frequenz=1, phi=0):
def loop():
...
while Trigonometrie.running:
...
Trigonometrie.running = True
threading.Thread(target=loop, daemon=True).start()
Also, the constant creation of HashMap objects in the loop could be optimized by reusing the same object.
input_dict = gateway.jvm.java.util.HashMap()
This is created every loop iteration. That's relatively expensive. Consider creating it once and updating it if the object type allows.
Here are some minor coding style suggestions for only the Python code.
Comments
Delete commented-out code to reduce clutter:
# phi = phi if phi <= FULL_CIRCLE else phi - FULL_CIRCLE
Documentation
The PEP 8 style guide recommends adding docstrings for classes and functions. For example,
def notify(self, obj):
"""Notify who about what"""
It would be helpful to document the obj
variable type.
Simpler
Lines like this:
if Trigonometrie.running == True:
are simpler without the comparison to True
and are more commonly written as:
if Trigonometrie.running:
This line:
class PythonListener(object):
is simpler without object
and is more commonly written as:
class PythonListener():
try/except
PEP 760 advises against bare except
:
except:
It is better to specify an expected exception type.
The except
statement is many lines away from the try
line. PEP 8
recommends that you limit the try
clause to the absolute minimum amount
of code necessary to avoid masking bugs. It is hard to keep track of what
line (or lines) are expected to result in the exception.
return
Instead of a bare return
:
return
the code may be more readable with an explicit None
:
return None
Naming
In the notify
function, obj
is not a very meaningful name for a variable.
In the start
function, self
is used in the def
line, but nowhere else.
Instead, the name of the class is used:
Trigonometrie.running = True
Consider using self
:
self.running = True
Tools
You could run code development tools to automatically find some style issues with your code.
ruff
shows:
E703 [*] Statement ends with an unnecessary semicolon
|
| trigonometrie.start(frequenz)
|
| java_app.setOnClose();
| ^ E703
| gateway.shutdown()
|
= help: Remove unnecessary semicolon