6
\$\begingroup\$

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()

enter image description here

asked Jun 26 at 15:36
\$\endgroup\$
2
  • \$\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\$ Commented Jun 30 at 12:15
  • \$\begingroup\$ @Reinderien No, I haven't. I'll do it. \$\endgroup\$ Commented Jun 30 at 13:29

2 Answers 2

8
\$\begingroup\$

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.

answered Jun 26 at 16:17
\$\endgroup\$
6
\$\begingroup\$

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
answered Jun 26 at 17:22
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.