مثالی آموزشی برای کار کردن با Py4j

با سلام، همان طور که در اینجا در مورد کاربردن کدهای جاوا در پایتون حرف زدم، در این آموزش میخوام مثالی کاربردی برای استفاده از Py4j جهت استفاده از کد های جاوا در پایتون رو برای آموزش بیشتر مرحله به مرحله پیش برم. (مثال پیش رو از سایت مرجع Py4j انتخاب شده است.)

  1. در قدم اول باید برنامه جاوا مورد نظرمان که میخواهیم در پایتون ازش استفاده کنیم رو بنویسیم.

در این مثال یک کلاس  Stack (پشته) که دو سرویس پایه و دو سرویس پیشرفته رو ارائه میده را ایجاد خواهیم کرد.

  1. push  کردن یک element روی پشته (top of Stack)
  2. pop کردن یک element از روی پشته 
  3. گرفتن لیستی شامل پشته.
  4. push کردن تمام elemnt هایی که درون لیست هستن در یک پشته.

در ادامه کد Stack  را می بینید:

package py4j.examples;

import java.util.LinkedList;
import java.util.List;

public class Stack {
    private List<String> internalList = new LinkedList<String>();

    public void push(String element) {
        internalList.add(0, element);
    }

    public String pop() {
        return internalList.remove(0);
    }

    public List<String> getInternalList() {
        return internalList;
    }

    public void pushAll(List<String> elements) {
        for (String element : elements) {
            this.push(element);
        }
    }
}

برای آنکه Stack درون یک برنامه پایتون در دسترس باشد، به چیزی نیاز داریم که به برنامه های پایتون اجازه دهد که:

  1. دسترسی به JVM جهت اجرای برنامه مورد نظرتان.
  2. دسترسی به object هایی که درون JVM ساحتید.

این ویژگی ها توسط دو object فراهم می شوند. اولی GatewayServer  می باشد: به برنامه پایتون اجازه می دهد که با JVM از راه یک سوکت شبکه لوکال ارتباط برقرار کند. object دومی entry point نامیده می شود و میتواند از objectی باشد (برای مثال: Facade، یک لیست، یک Singleton و غیره).

GatewayServer فراهم شده توسط Py4j می تواند موردد استفاده قرار گیرد البته شما می توانید آن را config کرده و آدرس شبکه و پورت مورد نظر خودتان را براش تعیین کنید (پیش فرض آن localhost, 2533  می باشد). سازنده ی GatewayServer یک object به عنوان پارامتر می گیرد. (سازنده = constructor)

اینجا مثالی از نمونه ای از یک entry point هست که به برنامه python اجازه خواخد داد که به stack از پیش کانفیگ شده دسترسی داشته باشد.

package py4j.examples;

import py4j.GatewayServer;

public class StackEntryPoint {

    private Stack stack;

    public StackEntryPoint() {
      stack = new Stack();
      stack.push("Initial Item");
    }

    public Stack getStack() {
        return stack;
    }

    public static void main(String[] args) {
        GatewayServer gatewayServer = new GatewayServer(new StackEntryPoint());
        gatewayServer.start();
        System.out.println("Gateway Server Started");
    }

}

چندین خط مهم در این کد وجود دارد، اولی اینکه شما کلاسی تعریف کرده اید که دسترسی به یک pre-configured stack دارد.

public Stack getStack() {
    return stack;
}

سپس شما متود main را ایجاد می کنید. این متود main می تواند در کلاس دیگری قرار گیرد. اولین کاری که باید در متود main کنید مقدار دهی اولیه GatewayServer و لینک کردن آن به entry point است.

public static void main(String[] args) {
    GatewayServer gatewayServer = new GatewayServer(new StackEntryPoint());

در انتها شما نیاز به شروع gateway دارید که می تواند درخواست های ورودی python را قبول کند.

gatewayServer.start();

حال شما آماده برای امتحان کردن برنامه جاوایتان هستید. فقط کافیه کلاس  StackGateway  را در محیط توسعه مورد علاقه خودتان execute کرده و بررسی کنید که پیغام  Gateway Server Started را می بینید یا خیر.

 

توجه: هنگامی که اپلیکیشن را اجزا می کنید ممکن است با exception زیر مواجه شوید:

java.net.BindException: Address already in use

دو تا دلیل رایج میتونه برای این exception وجود داشته باشه: دارید در حال حاضر یه نمونه دیگر از این برنامه را اجرا می کنید یا پورت مورد نظرتون توسط رایانه تان داره استفاده میشه. برای تغییر پورت این خط را :

GatewayServer gatewayServer = new GatewayServer(new StackEntryPoint());

با این خط جابجا کنید:

GatewayServer gatewayServer = new GatewayServer(new StackEntryPoint(), 25335);

فراموش نکنید که کد پایتون نیز باید تغییر کند:

from py4j.java_gateway import JavaGateway, GatewayParameters

gateway = JavaGateway(gateway_parameters=GatewayParameters(port=25335))

در حال حاضر کارها را انجام داده اید، زیرا که برنامه شما برای اتصال منتظر خواهد ماند، این برنامه exit نخواهد شد. برای پایان دادن به برنامه، باید آن را بُکُشید (kill) (برای مثال Ctrl – C). اگر GatewayServer را با متود دیگری نوشته اید می توانید با فراخوانی gatewayServer.shutdown آن را kill کنید.

2. نوشتن برنامه پایتون

حال نوبت به نوشتن برنامه پایتون برای دسترسی به برنامه javaیتان رسیده است. مفسر پایتون رو راه اندازی کنید و مطمئن شوید که Py4j درون PYTHONPATH قرار دارد.

مرحله اول import کردن کلاس ضروری Py4j می باشد:

>>> from py4j.java_gateway import JavaGateway

در ادامه نوبت به پیاده سازی اولیه JavaGateway می رسد. پارامتز های پیشفرض معمولا برای موارد رایج کافی است. زمانی که شما یک JavaGateway می سازید، پایتون تلاش می کند که به JVM توسط gateway متصل شود (localhost on port 25333)

>>> gateway = JavaGateway()

توجه: اگر شما این ارور را گرفتید (socket.error: [Errno 111]Connection refused)، به این معناست که JVMای برای اتصال منتظر نمی باشد. بررسی کنید که برنامه جاوا هنوز در حال اجرا باشد یا اینکه اصن start نکرده اید.

از طریق gateway object میتوان به entry point توسط ارجاع به عضو entry_point دسترسی داشت: 

>>> stack = gateway.entry_point.getStack()

هم اکنون stack به صورت خالی فرض شده است. اینجا چیزیه که اگز سعی به pop کردن بکنید اتفاق می افتد:

>>> stack.pop()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "py4j/java_gateway.py", line 346, in __call__
    return_value = get_return_value(answer, self.gateway_client, self.target_id, self.name)
  File "py4j/java_gateway.py", line 228, in get_return_value
    raise Py4JJavaError('An error occurred while calling %s%s%s' % (target_id, '.', name))
py4j.java_gateway.Py4JJavaError: An error occurred while calling o0.pop.
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.LinkedList.entry(LinkedList.java:382)
    at java.util.LinkedList.remove(LinkedList.java:374)
    at py4j.examples.Stack.pop(Stack.java:42)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:119)
    at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:392)
    at py4j.Gateway.invoke(Gateway.java:255)
    at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:125)
    at py4j.commands.CallCommand.execute(CallCommand.java:81)
    at py4j.GatewayConnection.run(GatewayConnection.java:175)
    at java.lang.Thread.run(Thread.java:636)

شما ارور Py4JJavaError را گرفته اید، چون که از طرف JVM یک exception رخ داده است. علاوه بر این شما میتوانید توع exception که اط سمت جاوا پرتاب شده و stack trace آن را نیز ببینید. برای مباحث پیشرفته تر توصیه میشه اینجا را بخوانید.