Pyjnius: PyJnius vs JPype

Criado em 16 jul. 2020  ·  27Comentários  ·  Fonte: kivy/pyjnius

Eu sou o autor principal do JPype. Como parte da atualização da documentação do JPype, adicionei o PyJnius à lista de códigos alternativos ao JPype. Infelizmente, depois de duas horas brincando com o PyJnius, não consegui pensar em nada que parecesse uma vantagem do PyJnius sobre o JPype. Todos os aspectos que eu olhei de proxies, personalizadores, manipulação de array multidimensional, integração javadoc, manipulação de GC, buffers, integração de código científico (numpy, matplotlib, etc), métodos sensíveis ao chamador, documentação e até mesmo velocidade de execução na maioria dos casos estão atualmente cobertos em JPype mais completamente que PyJnius. No entanto, sendo o autor do JPype, talvez eu esteja olhando para aspectos que valorizo, em vez dos da equipe PyJnius. Você pode articular melhor as vantagens deste projeto? Qual é a proposta de valor deste projeto, qual é o seu público-alvo e o que se pretende fazer que as alternativas ainda não contemplem?

Comentários muito úteis

Eu fiz um contato com todos os códigos de ponte Java cerca de 2 anos atrás. Infelizmente, o código PyJnius aparentemente foi esquecido, pois nunca apareceu em minha pesquisa. Eu teria perdido nesta rodada também, exceto que eu estava procurando pela página que fez o ótimo comunicado à imprensa sobre tecnologia da última vez e encontrei um blog discutindo os dois projetos. Não tenho certeza de como perdi outro projeto ativo na mesma área por dois anos, mas a culpa foi claramente minha.

Parece que você confundiu JPype com Py4J, que é o outro código de ponte principal. Eles fazem tudo usando soquetes com as vantagens e desvantagens que isso acarreta. Da mesma forma, descobri que o projeto não atendia aos meus requisitos.

Eu não fiz nenhuma pesquisa sobre o que é necessário para oferecer suporte ao Android. Porém, se eu tiver algumas especificações técnicas, será possível. Não há nada que estejamos fazendo que não seja o JNI nativo e as chamadas antigas para a API C do Python.

Quanto à abordagem, o JPype usa JNI exclusivamente para casar um JVM com Python usando o comando "startJVM ()". Embora a próxima versão (2.0) também forneça a capacidade de fazer o oposto, em que Python pode ser iniciado a partir de Java. Isso é feito por meio de uma abordagem de camadas. Há uma camada Python que contém todas as classes de alto nível que servem como front-end, um módulo privado CPython com classes básicas que mantêm os pontos de entrada, uma camada de suporte C ++ que lida com todas as conversões e correspondências de tipo, além de atuar como o módulo nativo para a biblioteca Java e uma biblioteca Java que contém todas as tarefas de utilitários (mantendo vidas úteis de objetos, criando classes de apoio para fatias e exceções e extratores / renderização javadoc).

8 anos atrás, o JPype era uma bagunça. Ele estava tentando oferecer suporte a Ruby e Python, então a camada C ++ era um emaranhado de wrappers para trabalhar e o front end estava todo em Python, então era muito lento. Ele também foi bifurcado nos tipos de retorno, pois o suporte numpy pode ser compilado resultando no retorno de objetos diferentes. Ele precisava de muitas classes de adaptador como JException para atuar como proxies onde o Python nativo e o objeto Java diferiam. Mas todos esses problemas foram resolvidos nos 3 anos desde que entrei no projeto. Os dois objetivos principais (para mim) no JPype é fornecer uma sintaxe simples o suficiente para que os físicos estejam familiarizados com a programação para poder usar Java e ter altos níveis de integração com o código Python científico. Fazemos isso fazendo com que os objetos que são apoiados por Java tenham "todos os enfeites" invólucros de objeto CPython. Em vez de converter um array primitivo Java, fazemos um array primitivo Java um novo tipo Python nativo que implementa todos os mesmos pontos de entrada como numpy e todos estes são apoiados por transferências de buffer de memória. Assim, temos conversões rápidas chamando list(jarray) ou np.array(jarray) .

Para adaptá-lo ao Android, a sequência de inicialização provavelmente precisaria ser retrabalhada e o código de conversão que ele usa para carregar sua biblioteca interna precisaria ser substituído por um modelo JNI mais tradicional. Já removi o código de conversão na próxima versão, portanto, a posterior já foi encontrada. Apenas o primeiro seria necessário.

As principais diferenças na abordagem que posso ver é que o PyJnius converte arrays de e para. Isso parece ser muito proibitivo para a codificação científica, em que passar grandes matrizes de um lado para o outro (geralmente que nunca são convertidas) é o estilo JPype preferido. A decisão de exigir a conversão força opções como passagem por valor e passagem por referência, mas isso potencialmente leva a problemas maiores, como se você tivesse uma chamada de vários argumentos e estivesse escolhendo uma política para todos os argumentos. Também tornaria difícil lidar com matrizes multidimensionais. (Eu acho que uma classe de adaptador usada como obj.method(1, jnius.byref(list1), list2) teria fornecido melhor controle se pudesse ser alcançada). Além disso, há muitos problemas que o JPype resolveu, como ligação de GC, métodos sensíveis ao chamador e outros. No mínimo, examine o código JPype e veja se há boas idéias que você possa usar.

Todos 27 comentários

Agradecemos seu contato!

Posso me lembrar mal porque já faz anos que não olhei para o jpype, mas achei que estava usando uma abordagem diferente, de falar com a JVM por meio de um servidor (então IPC) em vez de memória compartilhada (mas seu leia-me sugere o contrário, e não vejo dicas que refutem isso a partir de uma rápida olhada no código, então provavelmente estou totalmente errado), e essa abordagem a tornou impraticável no Android, por exemplo (que foi o principal motivo para desenvolver o pyjnius, embora algumas pessoas usam em plataformas de desktop). Por outro lado, não vejo muitas dicas de suporte para Android na base de código JPype, a menos que seja sobre isso o diretório native , já que seu jni.h parece ter sido retirado do AOSP.

Mas, honestamente, não tínhamos conhecimento do JPype quando o projeto foi iniciado, era apenas um passo além de ter que codificar o código jni manualmente para fazer a interface com classes específicas do Android para suporte ao kivy. Acredito que @tito fez algumas pesquisas para comparar mais tarde, quando soubemos disso, mas não me lembro se ele viu razões específicas para não tentar mudar.

Oi. Lembro-me do nome JPype, mas na hora de pesquisar o que poderíamos usar, não me lembro por que não foi usado honestamente, 8 anos atrás :) O único objetivo no início era ser capaz de se comunicar com o Android API, mas não usando um servidor RPC intermediário como o projeto P4A estava fazendo naquele momento.

Eu fiz um contato com todos os códigos de ponte Java cerca de 2 anos atrás. Infelizmente, o código PyJnius aparentemente foi esquecido, pois nunca apareceu em minha pesquisa. Eu teria perdido nesta rodada também, exceto que eu estava procurando pela página que fez o ótimo comunicado à imprensa sobre tecnologia da última vez e encontrei um blog discutindo os dois projetos. Não tenho certeza de como perdi outro projeto ativo na mesma área por dois anos, mas a culpa foi claramente minha.

Parece que você confundiu JPype com Py4J, que é o outro código de ponte principal. Eles fazem tudo usando soquetes com as vantagens e desvantagens que isso acarreta. Da mesma forma, descobri que o projeto não atendia aos meus requisitos.

Eu não fiz nenhuma pesquisa sobre o que é necessário para oferecer suporte ao Android. Porém, se eu tiver algumas especificações técnicas, será possível. Não há nada que estejamos fazendo que não seja o JNI nativo e as chamadas antigas para a API C do Python.

Quanto à abordagem, o JPype usa JNI exclusivamente para casar um JVM com Python usando o comando "startJVM ()". Embora a próxima versão (2.0) também forneça a capacidade de fazer o oposto, em que Python pode ser iniciado a partir de Java. Isso é feito por meio de uma abordagem de camadas. Há uma camada Python que contém todas as classes de alto nível que servem como front-end, um módulo privado CPython com classes básicas que mantêm os pontos de entrada, uma camada de suporte C ++ que lida com todas as conversões e correspondências de tipo, além de atuar como o módulo nativo para a biblioteca Java e uma biblioteca Java que contém todas as tarefas de utilitários (mantendo vidas úteis de objetos, criando classes de apoio para fatias e exceções e extratores / renderização javadoc).

8 anos atrás, o JPype era uma bagunça. Ele estava tentando oferecer suporte a Ruby e Python, então a camada C ++ era um emaranhado de wrappers para trabalhar e o front end estava todo em Python, então era muito lento. Ele também foi bifurcado nos tipos de retorno, pois o suporte numpy pode ser compilado resultando no retorno de objetos diferentes. Ele precisava de muitas classes de adaptador como JException para atuar como proxies onde o Python nativo e o objeto Java diferiam. Mas todos esses problemas foram resolvidos nos 3 anos desde que entrei no projeto. Os dois objetivos principais (para mim) no JPype é fornecer uma sintaxe simples o suficiente para que os físicos estejam familiarizados com a programação para poder usar Java e ter altos níveis de integração com o código Python científico. Fazemos isso fazendo com que os objetos que são apoiados por Java tenham "todos os enfeites" invólucros de objeto CPython. Em vez de converter um array primitivo Java, fazemos um array primitivo Java um novo tipo Python nativo que implementa todos os mesmos pontos de entrada como numpy e todos estes são apoiados por transferências de buffer de memória. Assim, temos conversões rápidas chamando list(jarray) ou np.array(jarray) .

Para adaptá-lo ao Android, a sequência de inicialização provavelmente precisaria ser retrabalhada e o código de conversão que ele usa para carregar sua biblioteca interna precisaria ser substituído por um modelo JNI mais tradicional. Já removi o código de conversão na próxima versão, portanto, a posterior já foi encontrada. Apenas o primeiro seria necessário.

As principais diferenças na abordagem que posso ver é que o PyJnius converte arrays de e para. Isso parece ser muito proibitivo para a codificação científica, em que passar grandes matrizes de um lado para o outro (geralmente que nunca são convertidas) é o estilo JPype preferido. A decisão de exigir a conversão força opções como passagem por valor e passagem por referência, mas isso potencialmente leva a problemas maiores, como se você tivesse uma chamada de vários argumentos e estivesse escolhendo uma política para todos os argumentos. Também tornaria difícil lidar com matrizes multidimensionais. (Eu acho que uma classe de adaptador usada como obj.method(1, jnius.byref(list1), list2) teria fornecido melhor controle se pudesse ser alcançada). Além disso, há muitos problemas que o JPype resolveu, como ligação de GC, métodos sensíveis ao chamador e outros. No mínimo, examine o código JPype e veja se há boas idéias que você possa usar.

@Thrameos, uma coisa recente que estamos procurando fundir é o uso de lambdas do Python para interfaces funcionais Java. Consulte https://github.com/kivy/pyjnius/pull/515 ; Eu não vi isso no JPype?

JPype tem suporte para lambdas de interfaces funcionais a partir de 1.0.0. Foi parte das 30 puxadas em 30 dias de março para 1,0.

JPype teve um longo período de incubação. Originalmente começou em 2004 rodando até 2007. Então teve um grande impulso quando o grupo de usuários o ressuscitou para portá-lo para Python 3 por volta de 2015. Então, em 2017, ele foi retirado para uso em um laboratório nacional que o transportou a partir de 0,6. 3 a 0.7.2. Durante esse período, todos os esforços se concentraram em melhorar e fortalecer a tecnologia central que fornece a interface. Mas isso foi finalmente concluído em março, após a segunda reescrita do núcleo, para que pudéssemos finalmente avançar para 1.0.0. Desde então, temos adicionado tudo o que estava "faltando" durante a minha campanha de lista de desejos "30 puxadas em 30 noites". O acúmulo de tudo que eu simplesmente não pude implementar porque seria muito trabalhoso foi finalmente resolvido (problemas caíram de 50 para 20, solicite aos usuários o que eles precisam, etc). Portanto, pode haver uma série de recursos que você não encontrou nas versões anteriores que agora estão disponíveis. Venho entregando a maioria das solicitações de recursos em menos de uma semana, de modo que tudo o que me resta são os três grandes (ponte reversa, estender classes em Python, capacidade de iniciar uma segunda JVM).

O projeto vai voltar a adormecer, pois estou 2 meses em um esforço de 6 meses para completar o código de ponte reversa que permitirá ao Java chamar Python e gerar stubs para bibliotecas Python para que possam ser usados ​​como bibliotecas nativas Java. Ele usa ASM para construir classes Java dinamicamente para que o suporte nativo para Python possa ser alcançado. Ainda não totalmente integrado como o Jython, mas talvez próximo o suficiente para que não haja muita diferença.

Muito obrigado pelas explicações detalhadas, e de fato, se alguma coisa, certamente há ideias que poderíamos usar, e valeria a pena estudar o código, olhando o código antes, o que vi parece bastante limpo tanto na qualidade do código quanto na estrutura do o projeto, então parabéns por todo o trabalho. Você está certo sobre a minha confusão com o Py4J, eu acho que quando olhei para o JPype, deve ter estado no estado confuso que você descreveu, e usá-lo deve ter sido muito mais complicado do que o PyJNIus neste momento.

Seus pontos sobre passagem por valor / conversão são muito verdadeiros e desencadearam algumas discussões recentes aqui também, já que @ hx2A investigou como melhorar o desempenho e tornou opcional a conversão para tipos de python (como passar uma lista descartável para java, obtê-la convertido para uma lista de java, modificado por java ou não, e convertido de volta para python, apenas para coletar o lixo, era certamente subótimo, podemos agora pelo menos evitar a segunda parte, ao custo de usar argumentos de palavra-chave, o que é seguro, pois java não os suporta, então não há conflito de assinaturas, mas certamente é um pouco mais barulhento em termos de sintaxe).

Quanto a uma possível diferença entre JPype e PyJNIus, podemos implementar interfaces java usando classes python e passá-las para java para serem usadas como callbacks, por outro lado, realmente precisaríamos gerar bytecode java, se quiséssemos estender classes java de python, como é um requisito para usar alguma API do Android, que não podemos cobrir agora, não tenho certeza se inferi corretamente do seu comentário, mas talvez você não tenha essa capacidade de ter classes java chamando seu código python como este (usando interfaces).

JPype pode implementar interfaces em Python. Basta adicionar decoradores a classes Python comuns.

from java.util.function import Consumer

@jpype.JImplements(Consumer)
class MyConsumer:
   @jpype.JOverride
   def apply(self, obj):
       pass

Use uma string em @JImplements se a classe for definida antes do JVM ser iniciado, várias interfaces podem ser implementadas de uma vez, mas todos os métodos são verificados para ver se foram implementados. A principal diferença é que o JPype usa métodos de despacho (todas as sobrecargas vão para o mesmo método) em vez dos métodos sobrecarregados. Isso ocorre porque gostamos de poder chamar os métodos de Java e Python. Posso adicionar sobrecargas individuais se esse for um recurso desejado, mas ninguém o solicitou.

(Editar: a razão de não usarmos a herança Python é que, no momento em que isso foi adicionado, ainda estávamos dando suporte ao Python 2, o que causou muitos problemas de metaclasse, vamos limpar isso ainda mais assim que as extensões de classe estiverem no lugar. Então, anotações que avaliar uma vez no momento da declaração eram mais limpos.)

Usamos o mesmo sistema para implementar personalizadores (dunder?) Para as aulas

@jpype.JImplementationFor("java.util.ArrayList")
class ArrayListImpl:
    def __getitem__(self, i):
        return self.get(i)
    @jpype.JOverride
    def addAll(self, list):
        # Decide if we need to convert or can call directly.
        ...

Também usamos anotações para definir os conversores implícitos, como "Todos os objetos Python Path convertem para java.io.File"

 @jpype.JConversion("java.io.File", instanceof=pathlib.PurePath)
 def _JFileConvert(jcls, obj):
       Paths = jpype.JClass("java.nio.file.Paths")
       return Paths.get(str(obj))

Obviamente, usar esse tipo de lógica é um pouco mais lento do que os métodos C especiais, mas mantém a legibilidade e a flexibilidade altas. Eu tenho empurrado caminhos críticos que provaram ser gargalos de volta para C, conforme necessário.

Nós também fornecemos um pouco de sintaxe para tornar as coisas mais limpas MyJavaClass@obj => cast para MyJavaClass (Java equivalente (MyJavaClass)obj ) ou cls=JInt[:] => criar um tipo de array ( cls=int[].class ) ou a=JDouble[10][5] => crie um array multidim ( double[][] a = new double[10][5] ).

Tenho trabalhado no protótipo para estender classes do JPype. Temos o mesmo problema que algumas classes Swing e outras classes de extensão exigidas por APIs. A solução que criei até agora é criar uma classe estendida com um HashMap para cada um dos métodos substituídos. Se não houver nada no hashmap para esse ponto de entrada, então o passa para super, caso contrário, ele chama o manipulador do método proxy. Mas eu decido que isso seria mais fácil de implementar depois que a ponte reversa for concluída, para que Java pudesse realmente lidar com métodos Python em vez de usar o método proxy Java. Portanto, ainda faltam cerca de 6 meses para que eu tenha o protótipo funcionando. Você pode olhar o branch epypj (meu nome para a ponte reversa) para ver como a chamada de Java de volta para Python funciona, bem como os padrões para gerar um invocador usando ASM para criar classes Java dinamicamente.

Quanto ao gerenciamento da coleta de lixo, existem algumas partes do JPype que fazem esse trabalho. O primeiro é o JPypeReferenceQueue (native / java / org / jpype / ref / JPypeReferenceQueue), que liga a vida de um objeto Python a um objeto Java. Isso é usado para criar buffers e outras coisas onde o Java precisa acessar um conceito Python por um período. A segunda é usar referências globais para que o Python possa conter um objeto Java no escopo. Isso requer o link do coletor de lixo (native / common / jp_gc.cpp) que escuta qualquer sistema para acionar um GC e enviar um ping para o outro quando certas condições (tamanho dos pools, crescimento relativo). Os últimos proxies precisam usar referências fracas porque, de outra forma, formariam loops (já que o proxy contém uma referência para a metade Java e a metade Java aponta de volta para a implementação Python). Eventualmente, pretendo usar um agente para permitir que o Python atravesse o Java, mas isso está no futuro.

Sou uma das pessoas que usa o pyjnius na área de trabalho e não no Android. Eu não sabia sobre o JPype quando comecei a construir meu projeto, mas fiz algumas investigações para ver quais são as diferenças.

Um recurso exclusivo do pyjnius é que o chamador pode decidir se deseja ou não incluir os métodos e campos protegidos e privados. Minha preferência é apenas pelo público, mas entendo o argumento de que tornar os campos e métodos não públicos disponíveis é útil.

O desempenho é fundamental para o meu projeto. Fiz alguns testes com a classe abaixo:

package org.pkg;

public class MyClass {

  public MyClass() {
  }

  public int number = 42;

  public float add1(float x, float y) {
    return x + y;
  }

  public float add2(float x, float y) {
    return x + y;
  }

  public float add2(int x, int y) {
    return x + y;
  }
}

Em JPype:

In [1]: import jpype
   ...: import jpype.imports
   ...: jpype.startJVM()
   ...: from org.pkg import MyClass
   ...: myInstance = MyClass()
   ...:

In [2]: %timeit myInstance.number
640 ns ± 2.65 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [3]: %timeit myInstance.add1(10.3, 20.5)
2.13 µs ± 24.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [4]: %timeit myInstance.add2(10.3, 20.5)
2.19 µs ± 9.41 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Em pyjnius:

In [1]: import jnius

In [2]: MyClass = jnius.autoclass('org.pkg.MyClass')

In [3]: myInstance = MyClass()

In [4]: %timeit myInstance.number
161 ns ± 0.104 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [5]: %timeit myInstance.add1(10.3, 20.5)
1.04 µs ± 8.16 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [6]: %timeit myInstance.add2(10.3, 20.5)
2.71 µs ± 11.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Pyjnius é um pouco mais rápido, exceto pelo método sobrecarregado. Devemos comparar as notas sobre como decidir qual método chamar quando o método está sobrecarregado. O Pyjnius tem esse mecanismo de pontuação que parece adicionar muita sobrecarga. O JPype torna essa decisão muito mais rápida.

Finalmente, para fins de benchmark:

In [9]: def add(x, y):
   ...:     return x + y
   ...:

In [10]: %timeit add(10.3, 20.5)
82.9 ns ± 0.187 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Claro, diferenças de alguns µs são triviais, mas isso se soma se alguém estiver fazendo milhares de pequenas chamadas muito rapidamente, o que eu preciso fazer.

A integração do JPype com o numpy é bastante agradável e fácil de usar. Eu posso ver como os pesquisadores podem usar isso para escrever scripts que passam grandes matrizes para bibliotecas Java sem sintaxe complicada. Eu também preciso passar matrizes grandes e faço isso com tobytes() e código Java especial que pode receber os bytes, mas obviamente isso não é tão limpo ou conveniente.

A velocidade do JPype, infelizmente, é uma perspectiva é-o-que-é. O JPype é muito defensivo, tentando se proteger contra coisas ruins, o que significa que há uma grande sobrecarga não trivial. Isso significa que, por exemplo, sempre que uma chamada Java é feita, ela garante que o encadeamento está conectado, de modo que não causamos falha em segfault se chamado de um encadeamento externo, como um IDE. Meu grupo de usuários locais são todos cientistas, então os pontos de entrada são todos muito protegidos contra fiação cruzada horrível. Se algo alguma vez falhar neles (não importa o quão louco seja), então eu falhei. (O que explicaria os 1.500 testes, incluindo a construção deliberada de objetos ruins.)

Em segundo lugar, a velocidade das ações individuais provavelmente varia muito em diferenças muito pequenas no trabalho que está sendo executado. O exemplo trivial que você deu foi um dos piores casos. Dependendo de que tipo de campo está sendo o acesso pode realmente alterar a velocidade de acesso em alguns casos.

Para seu exemplo de velocidade, você está solicitando um campo int.

  • No PyJnius ele cria um descritor para a pesquisa do objeto, acessa o campo, criando um novo Python longo e depois o retorna.
  • No JPype, ele cria um descritor para a pesquisa do objeto, acessa o campo, cria um novo Python long e, em seguida, cria um tipo de wrapper para um int Java, copia o Python long memory para o JInt (porque Python não tem uma maneira de criar um derivado classe inteira diretamente), em seguida, vincula o slot com um valor Java e, finalmente, retorna o JInt resultante.

Portanto, mesmo algo tão trivial como fazer benchmarking de velocidade ao acessar um campo, não é realmente tão trivial.
A diferença é que um retornou um Python longo e o outro retornou um inteiro Java real (que segue as regras de conversão Java) que poderia ser passado para outro método sobrecarregado e vinculado corretamente. O trabalho para retornar um tipo de wrapper é muito mais do que simplesmente retornar um tipo Python, daí uma grande diferença na velocidade.

Tentei demonstrar isso testando alguns tipos de campo diferentes. Infelizmente, quando testei os campos de objeto, jnius falhou no código "harness.objectField = harness". Não tenho certeza de por que aquele pedaço específico de fiação cruzada causou um problema, mas falhou para mim. Não estive muito interessado em velocidade para JPype além de remover os infratores grosseiros que deram um fator de 3-5 velocidade para chamadas e 300 vezes para certos acessos de array. Mas talvez eu deva revisar e ver quais áreas podem ser melhoradas. Duvido, porém, que possa reduzi-lo ao básico, como PyJnius, sem remover a segurança ou remover contratos de devolução (o que não posso fazer). No máximo, ainda é possível aumentar a velocidade de 10-30%,

Quanto à facilidade de acesso a campos privados e protegidos, certamente é possível. Eu prefiro que os usuários usem reflexão ou outros métodos de acesso interno em vez de expor o objeto diretamente. Se eu tivesse que fornecer algo assim, provavelmente criaria um campo chamado _private que conteria campos que não são expostos publicamente. O JPype fornece apenas um wrapper de classe para cada tipo, portanto, não tenho muitos controles de granulação fina. Portanto, você não poderia optar por criar uma classe que tenha acesso privado e, em seguida, criar um segundo objeto do mesmo tipo e não acabar com os privados expostos. Eu segui esse caminho com a conversão de strings, e foi um desastre com algumas bibliotecas escolhendo uma política e outras diferentes, resultando em incompatibilidades.

Eu executei alguns testes usando a lista de matriz.

import jpype
import timeit
jpype.startJVM()
ArrayList = jpype.JClass("java.util.ArrayList")

def pack():
    ja = ArrayList()
    for i in range(1000):
        ja.add(i)

def iter(ja):
    u = 0
    for i in ja:
        u+=i

def access(ja):
    u = 0
    for i in range(len(ja)):
        u+=ja.get(i)

def access2(ja):
    u = 0
    for i in range(len(ja)):
        u+=ja[i]


ja = ArrayList()
for i in range(1000):
   ja.add(i)

print("Pack arraylist %e"%( timeit.timeit("pack()", globals=globals(), number=1000)/1e6))
print("Iterate arraylist %e"%(timeit.timeit("iter(ja)", globals=globals(), number=1000)/1e6))
# Get is a direct call
print("Access(get) arraylist %e"%(timeit.timeit("access(ja)", globals=globals(), number=1000)/1e6))
# [] is emulated
print("Access([]) arraylist %e"%(timeit.timeit("access2(ja)", globals=globals(), number=1000)/1e6))

JPype

Pack arraylist 2.768904e-06
Iterar arraylist 5.208071e-06
Acessar (get) arraylist 4.037985e-06
Access ([]) arraylist 4.690264e-06

Jnius

Pack arraylist 3.322248e-06
Iterar arraylist 4.099314e-06
Acessar (get) arraylist 5.653444e-06
Access ([]) arraylist 7.762727e-06

Não é uma história muito consistente, a não ser dizer que eles provavelmente são trivialmente diferentes, exceto quando fornecem funcionalidades muito diferentes. Se tudo o que você está fazendo é acessar métodos, provavelmente eles são muito semelhantes. Passar arrays que são JPype pré-convertidos é 100 vezes mais rápido, converter listas e tuplas. O JPype é 2 vezes mais lento (não usamos acesso vetorial ou temos desvios especiais para tuplas atualmente). Portanto, o resultado final depende do seu estilo de codificação; pode ser muito rápido com um ou outro. Mas então meus usuários geralmente selecionam JPype para facilidade de uso e construção à prova de bala, em vez de velocidade. (Ah, quem estou brincando! Eles têm a mesma probabilidade de sair da Internet porque o JPype foi o primeiro link que encontraram no Google.)

Quanto a como o JPype faz a associação de método, esse detalhe deve estar no guia do desenvolvedor / usuário. A vinculação do método começa pré-classificando a lista de métodos para o despacho de acordo com as regras fornecidas na especificação Java de modo que se algo ocultar algo mais apareça primeiro na lista (o código pode ser encontrado em native / java / org / jpype como nós usar uma classe de utilitário Java para fazer a classificação quando o despacho é criado pela primeira vez). Além disso, cada método recebe uma lista de preferências de qual método oculta o outro. A resolução começa primeiro verificando cada argumento para ver se há um "slot Java" para o argumento. Os slots Java apontam para objetos existentes que não precisam de conversão, portanto, tirá-los do caminho antes da correspondência significa que podemos usar regras diretas em vez de regras implícitas. Em seguida, ele combina o argumento com base nos tipos em 4 níveis (exato, implícito, explícito, nenhum). Ele atalhos em explícito e nenhum para pular para o próximo método. Se chegar a uma hora exata, ele encurta todo o processo para ir para a chamada. Se houver uma correspondência, ele ocultará os métodos que possuem uma ligação menos específica. Se forem encontradas duas correspondências implícitas que não foram ocultadas, ele prosseguirá para um TypeError. Assim que todas as correspondências forem esgotadas, as rotinas de conversão são executadas. Em seguida, ele libera o bloqueio global Python e faz a chamada e readquire o bloqueio global. O tipo de retorno é pesquisado e um novo wrapper Python é criado com base no tipo retornado (ele usa retornos covariantes para que o tipo retornado seja o mais derivado em vez do tipo do método). Isso é principalmente linear com o número de sobrecargas, embora haja algumas complexidades para argumentos variados, mas as tabelas pré-construídas significam que ele tentaria foo (longo, longo) antes de tentar foo (duplo, duplo) e um acerto (longo , long) evitaria double, double de cada correspondência devido às regras de resolução do método Java. Ainda existem algumas acelerações que posso implementar, mas isso exigiria tabelas de cache adicionais.

Eu herdei o sistema de pedidos com atalhos quando comecei no projeto em 2017. Eu adicionei o método de esconder cache e slots java para eliminar a maior parte de nossa sobrecarga.

Otimizei o caminho de execução dos métodos. Os números revisados ​​para JPype são

Pack arraylist 2.226081e-06
Iterar arraylist 4.082152e-06
Acessar (get) arraylist 2.962606e-06
Access ([]) arraylist 3.644642e-06

Meu grupo de usuários locais são todos cientistas, então os pontos de entrada são todos muito protegidos contra fiação cruzada horrível. Se algo alguma vez falhar neles (não importa o quão louco seja), então eu falhei.

Sim, segfaults são horríveis e recebi centenas deles quando comecei a usar o pyjnius. Eu não recebo nenhum há muito tempo, porque talvez eu tenha resolvido os problemas de segurança e embutido no meu código. Agora tudo funciona de forma confiável. Eu entendo seu caso de uso. Se seus usuários forem cientistas que trabalham com objetos Java diretamente para fazer análise de dados com várias bibliotecas Java, um segfault faria com que perdessem todo o trabalho. O JPype parece melhor projetado para fazer trabalho científico onde os usuários finais estão trabalhando diretamente com os objetos Java por meio do Python. O principal caso de uso do pyjnius é diferente, porém, que é a comunicação com o Android. Nesse caso, as questões de segurança são problema do desenvolvedor, portanto, talvez seja apropriado fazer escolhas diferentes sobre segurança versus velocidade.

Admito que não sou um grande fã de "é seguro, contanto que você pise nesses quadrados nesta ordem". Quando comecei a trabalhar no JPype, demorei quase um ano para tornar todos os pontos de entrada à prova de bala o suficiente para que eu pudesse passar o código ao meu grupo local. E eu adicionei dois anos adicionais de armadura de API desde então. Além de algumas raras pessoas cujo JVM falha ao carregar (o que é muito difícil de resolver), há poucos problemas restantes, pois o JPype foi trazido para os padrões de código de produção.

Quanto a uma troca com velocidade e segurança, a velocidade é ótima, mas se você está ganhando velocidade economizando nas operações seguras, geralmente é uma troca ruim para a maioria dos usuários. Esteja você revirando o código de prototipagem ou escrevendo um sistema de produção, ter que parar de trabalhar e tentar contornar um segfault é uma distração que os usuários não deveriam enfrentar.

Se alguém estiver disposto a me dar alguns exemplos de como testar o JPype em um emulador Android, posso ver como fazer as modificações necessárias.

Para usar no Android, empacotamos o pyjnius como a arte de uma distribuição Python construída por python-for-android (geralmente usando buildozer como uma interface mais fácil para ele, mas é o mesmo) e, em seguida, construímos um aplicativo Python que envia esta distribuição, então, seu código python pode importar pyjnius ou qualquer outra biblioteca python que foi construída na distribuição python quando o usuário executa o aplicativo.

A primeira etapa, portanto, é ter o jpype compilado em uma distribuição, o que é feito por p4a (https://github.com/kivy/python-for-android/) quando você diz que faz parte de seus requisitos, geralmente ( mas nem sempre) uma "receita" é necessária para explicar ao p4a como construir bibliotecas que não sejam python puro, aquela para pyjnius pode ser encontrada aqui https://github.com/kivy/python-for-android/blob/ development / pythonforandroid / recipes / pyjnius / __ init__.py como um exemplo. Se você usar buildozer, você pode usar a configuração p4a.local_recipes em buildozer.spec para declarar um diretório no qual as receitas para os requisitos podem ser encontradas, então você não precisa bifurcar python-for-android para tenha sua receita usada.

Aconselho usar buildozer, pois ele automatiza mais coisas https://buildozer.readthedocs.io/en/latest/installation.html#targeting -android e você ainda pode configurar suas receitas locais para experimentar. A primeira compilação levará tempo, pois é necessário compilar o python e várias dependências para arm, e será necessário fazer o download do android ndk e sdk para ele. Você provavelmente pode usar o bootstrap padrão do kivy para o aplicativo e criar um "hello world" como o aplicativo, que importaria o jpype e apenas exibiria o resultado de algum código em uma etiqueta, ou mesmo no logcat usando imprimir, eu não lembre-se de como o kivy funciona bem no emulador de android, eu nunca usei isso, mas acho que alguns usuários usaram, e com a configuração de aceleração deve funcionar, afaik, caso contrário, você poderia usar o wrapper sdl2 ou o webview e usar um frasco ou servidor de garrafas para mostrar as coisas, eu tentaria primeiro com o kivy, pois é de longe o mais testado.

Você precisará de uma máquina linux ou osx (VM é bom e WSL no Windows 10 é bom), para construir para o Android.

Se você começar a trabalhar em uma receita jpype para python-for-android, será bem-vindo para abrir um PR em andamento sobre isso para qualquer discussão que possa surgir. Seria ótimo se funcionasse, especialmente se realmente pudesse resolver algumas limitações de pyjnius de longa data. Conforme discutido anteriormente no tópico, o pyjnius cobre essencialmente nossos requisitos básicos para o uso do kivy, mas não tem potência de desenvolvimento suficiente para ir significativamente além disso.

@inclement Eu configurei um PR para a porta do Android em jpype-project / jpype # 799. Infelizmente, não tenho certeza de para onde ir a partir daqui. Parece que ele está tentando executar o gradle, o que realmente não é o caminho de construção correto.

As ações que precisam ser realizadas são as seguintes:

  • [x] Inclui todos os arquivos jpype / *. py na construção (ou versões pré-compiladas deles).
  • [x] Execute o Apache ant em native / build.xml e coloque o arquivo jar resultante em algum lugar onde ele possa ser acessado.
  • [x] Inclui o arquivo jar (ou equivalente) na construção.
  • [x] Compile o código C ++ de nativo / comum e nativo / python em um módulo denominado _jpype para ser incluído na construção.
  • [x] Inclui um arquivo main.py que apenas inicia um shell interativo para que possamos testar isso manualmente por enquanto.
  • [] No futuro, preciso incluir "ASM" ou algo que funcione parecido para o Android para que eu possa carregar as classes criadas dinamicamente.
  • [x] Corrigir o código C ++ para que ele use um bootstrap personalizado para carregar o JVM e o arquivo jar complementar e conectar todos os métodos nativos.
  • [] Corrija o jvmfinder com algo que funciona no Android e "startJVM" é chamado automaticamente em vez de no início do principal.
  • [] Patch org.jpype para que o sistema de navegação jar (que é como as importações funcionam) possa funcionar no Android.

Eu examinei alguns dos documentos, mas nada realmente se destacou sobre como fazer isso. O layout do meu projeto é um pouco diferente do normal, pois não colocamos tudo no módulo principal (já que estamos construindo 3 módulos que compõem o sistema. Jpype, _jpype e org.jpype). Provavelmente, preciso de uma receita personalizada para realizar todas essas ações, bem como desativar padrões indesejáveis, como executar o Gradle (a menos que esteja fazendo algo útil que eu não saiba dizer).

Parece que ele está tentando executar o gradle, o que realmente não é o caminho de construção correto.

Gradle é a ferramenta de compilação usada como a etapa final do empacotamento de um APK, provavelmente não está relacionado à inclusão do jpype.

Inclua todos os arquivos jpype / *. Py na construção (ou nas versões pré-compiladas deles).

Em geral, se jpype funciona essencialmente como um módulo Python normal, então sua primeira tentativa de receita provavelmente fará a maior parte do trabalho pesado - o CppCompiledComponentsPythonRecipe faz algo como executar python setup.py build_ext e python setup.py install usando o ambiente NDK. Isso deve instalar o pacote jpype python dentro do ambiente python que está sendo construído para inclusão dentro do aplicativo.

Inclua o arquivo jar (ou equivalente) na construção.

Esta é provavelmente uma etapa extra que a receita precisará fazer, ela se resumirá a copiar seu arquivo jar (ou o que você precisar) em algum lugar apropriado dentro do projeto Android que o python-for-android está construindo.

Compile o código C ++ de nativo / comum e nativo / python em um módulo denominado _jpype para ser incluído na construção.

Se isso for tratado pelo setup.py, já deve estar funcionando, mas pode precisar de alguns ajustes. Se não for, você pode incluir seus comandos de compilação na receita (e fazer com que sejam construídos para o ambiente Android configurando os env vars apropriados, como você verá usando self.get_env em outras receitas).

Corrija o código C ++ para que ele use um bootstrap customizado para carregar o JVM e o arquivo jar complementar e conectar todos os métodos nativos.

Espero que esta parte seja bastante direta, dependendo apenas do uso da função de interface JNI do Android certa. Fazemos isso de uma forma ligeiramente hackeada corrigindo o pyjnius em vez de fazer alguma compilação condicional apropriada, pois diferentes bibliotecas auxiliares fornecem diferentes wrappres, como demonstrado, por exemplo, por este patch . Essa complexidade não precisa afetá-lo, você pode simplesmente chamar a função correta da API do Android.

Corrija o jvmfinder com algo que funciona no Android e "startJVM" é chamado automaticamente, em vez de no início do principal.

Não estou familiarizado o suficiente com o JVM para saber realmente, mas acho que o Android quer que você sempre acesse um jvm existente e você não pode iniciar uma nova instância. Isso vai importar (ou é apenas errado?)?

Execute o Apache ant em native / build.xml e coloque o arquivo jar resultante em algum lugar onde ele possa ser acessado.

Eu também não tenho certeza sobre este devido a falta de familiaridade. Esta é apenas uma etapa de compilação interna do jpype? Não tenho certeza de como as versões java interagem, mas acho que usar a formiga nativa do sistema deve funcionar aqui.

Ele deu um aviso de que o buildozer não respeita o setup.py, então ele pulou diretamente do topo para a etapa do gradle. Daí a necessidade de adicionar essas etapas intermediárias manualmente. Nosso setup.py executa uma série de etapas de compilação personalizadas, como criar o arquivo de ganchos para mesclar o arquivo jar com dll, o que provavelmente não se aplica aqui, então não houve problema em ser ignorado, mas também falhou em localizar os arquivos cpp e java que são definidos em setup.py. Parte do problema é que meu setup.py era tão grande que tive que dividir no módulo setupext.

Acho que primeiro preciso descobrir como posso executar um comando clean para que o processo seja executado uma vez e mostre o log. (Eu executei uma refeição parcial na primeira vez enquanto estava jogando. Portanto, não tenho um registro limpo.) Também gostaríamos de mover esta conversa para o PR para que possamos ficar mais no tópico do tópico.

FWIW consegui portar o núcleo do meu projeto não android para JPype. Houve duas pequenas dificuldades ou diferenças:

  1. O classpath kwarg em jpype.startJVM() parece ser ignorado, não importa o que eu faça. A função addClassPath() funcionou. Os desenvolvedores que se preocupam com a ordem do caminho de classe podem precisar fazer alguns ajustes em comparação com a forma como usaram jnius_config.add_classpath() .

  2. A implementação de interfaces Java funciona muito bem, mas é um pouco diferente. Em meu código, eu tinha uma função que estava retornando uma lista de strings Python. Em retrospecto, provavelmente deveria ter convertido cada string em uma string Java, mas não pensei nisso e fiz a definição da função de interface retornar uma lista de objetos Java para fazer isso funcionar. Isso funciona bem no pyjnius, o que efetivamente torna as strings de python uma subclasse de objetos Java. Isso não funciona no JPype, mas resolvi facilmente convertendo as strings com a classe JString do JPype.

Escrever @JOverride para os métodos implementados é muito mais simples do que código como @java_method('(Ljava/lang/String;[Ljava/lang/Object;)V') .

Depois de lidar com esses dois problemas, basicamente tudo funcionou bem. Eu tenho algumas matrizes de byte de passagem de código que terão que mudar, mas o JPype tem um bom suporte para isso. Além disso, as conversões de tipo implícitas do JPype são muito convenientes e podem substituir muitos decoradores que tive que espalhar por toda parte.

@Thrameos eu respeito sua determinação aqui. Boa sorte para que o JPype funcione no Android.

Obrigado pelos comentários.

Não tenho certeza do que pode estar errado no caminho de classe, embora se eu tivesse que adivinhar, seria de onde você estava iniciando o Python. Às vezes, as pessoas iniciam a JVM a partir de um módulo e, como as regras para caminhos Java e caminhos Python em relação ao diretório inicial são diferentes, isso pode fazer com que jars sejam perdidos. (Há uma seção no guia do usuário relacionada a esse assunto).

Eu uso o recurso de classpath diariamente e faz parte de nossos padrões de teste. Portanto, se os caminhos de classe não fossem respeitados, isso causaria algumas falhas importantes. Dito isso, você não seria o primeiro a ter dificuldades em encontrar o conjunto mágico de locais e caminhos absolutos necessários para fazer um padrão complexo funcionar.

Aqui está um exemplo em que estou iniciando meu sistema de teste a partir de um diretório Py relativo a por área de desenvolvimento.

import jpype
import os

devel = os.path.dirname(__file__)
devel = os.path.join(devel, '..', '..')
devel = os.path.abspath(devel)  # Notice that I converted the path to absolute so that it doesn't matter where the
# PWD of Java will be when this script is called.   Otherwise, if I import this from a different location it will use the
# original PWD and Java will find nothing in the classpath.

classpath = [
    '%s/gov.llnl.math/dist/*' % devel,
    '%s/gov.llnl.rdak/dist/*' % devel,
    '%s/gov.llnl.rnak/dist/*' % devel,
    '%s/gov.llnl.rtk/dist/*' % devel,
    '%s/gov.llnl.rtk.gadras/dist/*' % devel,
    '%s/gov.llnl.rtk.response/dist/*' % devel,
    '%s/gov.llnl.utility/dist/*' % devel,
    ]

jpype.startJVM(classpath=classpath, convertStrings=False)

Caminhos relativos e absolutos funcionam, mas isso vai depender muito de onde você começa. O addClassPath tem magia especial para garantir que todos os caminhos sejam em relação ao local do chamador. Acho que a mesma lógica é necessária nos argumentos de palavra-chave do caminho de classe.

Posso ver o problema de retornar uma lista de strings. Dependerá do tipo de retorno quanto do comportamento que será acionado. Se o método foi declarado como retornando String[] eu esperaria forçar cada string Python em um Java on ao retornar. Se o método foi declarado como retornando List<String> , haveria um problema, pois os genéricos Java o retirariam, então seria List<Object> . Dependendo de como o JPype vê a lista, ele pode ou não tentar converter, mas esse comportamento deve ser definido, então irei verificá-lo. A solução segura é uma compreensão de lista que deve ser capaz de converter todos os itens para a devolução.

Não tenho certeza do que pode estar errado no caminho de classe, embora se eu tivesse que adivinhar, seria de onde você estava iniciando o Python. Às vezes, as pessoas iniciam a JVM a partir de um módulo

Isso é o que eu estava fazendo!

Eu uso o recurso de classpath diariamente e faz parte de nossos padrões de teste. Portanto, se os caminhos de classe não fossem respeitados, isso causaria algumas falhas importantes. Dito isso, você não seria o primeiro a ter dificuldades em encontrar o conjunto mágico de locais e caminhos absolutos necessários para fazer um padrão complexo funcionar.

Sim, imaginei que você notaria imediatamente e que não fui a primeira pessoa a ter esse problema. Obrigado pelo exemplo de código, que é útil. Como resultado, fiz algumas melhorias em meu código.

A solução segura é uma compreensão de lista que deve ser capaz de converter todos os itens para a devolução.

Isso é exatamente o que eu fiz e está funcionando corretamente agora. Obrigado!

Alguns anos atrás, fiz uma comparação entre py4j e jnius e descobri que jnius era muito mais rápido.

Sempre que vi jpype mencionado em alguns lugares, sempre presumi que se referia a - http://jpype.sourceforge.net/ e fiquei longe dele porque parecia não ter manutenção (não vi o novo projeto de maneira estranha)

Ao ler este tópico, tentei meus benchmarks novamente (meu principal caso de teste para velocidade é ler e pontuar arquivos PMML) - e descobri que o jpype tinha um ótimo desempenho.
Alguns resultados:
https://gist.github.com/AbdealiJK/1dd5b7677435ba22f9ab3e26016bb3e7

# jpype
# createjvm: 0.550s
# loadmodel: tot=1.466451 max=1.064521s avg=0.014665s
# fields   : tot=0.019881 max=0.009795s avg=0.000199s
# score    : tot=0.033356 max=0.023338s avg=0.000334s

# jnius
# createjvm: 0.249s
# loadmodel: tot=1.773011 max=1.385274s avg=0.017730s
# fields   : tot=0.039058 max=0.012234s avg=0.000391s
# score    : tot=0.067590 max=0.031904s avg=0.000676s

# py4j
# createjvm: 0.222s
# loadmodel: tot=0.616913 max=0.027464s avg=0.006169s
# fields   : tot=0.699152 max=0.026426s avg=0.006992s
# score    : tot=0.389583 max=0.017620s avg=0.003896s

Para ser justo, o JPype tinha sua API de front end escrita em Python (ao contrário de CPython) até março de 2020. Portanto, há muitos benchmarks mais antigos para mostrar que era bastante lento para o que fornecia. Havia simplesmente tantos problemas de back-end que precisavam ser resolvidos com gerenciamento de memória, removendo wrappers multicamadas inteiros para suportar Ruby, multithreading e coisas do tipo, que a velocidade era a última coisa a ser trabalhada.

Neste ponto, deve ser muito rápido. Mas se você encontrar algo que precise de trabalho adicional em relação a outros pacotes, apenas anote os problemas. Por causa dos contratos de retorno, existem algumas limitações, mas os caminhos principais para chamadas, transferências de array e armazenamento em buffer de memória são bastante trabalhados neste ponto.

Também estou obtendo bons resultados com o JPype. Em particular, estou obtendo um bom valor da integração com matrizes entorpecidas. Consegui adicionar novos recursos que não podia fazer antes, que me permitiram melhorar o desempenho de maneiras significativas.

Se alguém puder ser gentil a ponto de tentar atualizar a ferramenta kivy-remote-shell com para fazê-la compilar com o Python3 e buildozer atuais e melhorar as instruções para ser mais passo a passo, isso ajudaria com o esforço de portabilidade do JPype muito. Concluí todas as etapas necessárias até a fase de inicialização e teste. Mas, sem um ambiente para concluir o processo de inicialização, será difícil fazer progresso. Posso tentar atualizar novamente a ferramenta de shell remoto neste fim de semana (e talvez ter sucesso) ou uma parte interessada com melhor conhecimento de kivy pode concluir esta tarefa de pré-requisito e então posso passar o fim de semana completando o trabalho técnico que sou o mais qualificado para concluir . Embora eu ofereça gratuitamente meu tempo para ajudar os outros, são recursos finitos e qualquer trabalho que eu faça nos esforços de portabilidade do Android é um atraso na ponte Python do Java, no qual várias outras pessoas também estão interessadas.

Espero que o esforço de portabilidade do Android possa evitar seguir o caminho do esforço de portabilidade do PyPy, onde passei várias semanas retrabalhando o código principal para ser capaz de lidar com as diferenças, mas depois tive um problema técnico em que uma diferença trivial no sistema de objetos produziu um erro, e não consegui encontrar ninguém que pudesse me ajudar a rastrear como depurar um relatório de erro gerado no código gerado. Embora eu não chore sobre leite derramado e todo esse esforço foi feito para melhorar o código do JPype para outras formas significativas, no final do dia, os usuários que queriam usar o JPype foram deixados de lado. Se esse esforço falhar, nem tudo será perdido, pois vou voltar para ele, mas uma vez que algo está no fim da fila, tenho dificuldade para voltar a fazer isso por 6 meses, a menos que alguém seja capaz de me ajudar .

Atualização do progresso para as partes interessadas.

Consegui inicializar o JPype de dentro do pythonforandroid e testei a funcionalidade básica. Embora alguns dos recursos avançados possam não ser possíveis devido a diferenças na JVM, acredito que a grande maioria do JPype estará disponível para uso na plataforma Android. O porte demorou um pouco, pois exigiu algumas atualizações nos projetos buildozer e pythonforandroid (portanto, muitas leituras do código-fonte e pedidos de ajuda). Muito obrigado aos desenvolvedores aqui por serem responsivos para que eu pudesse concluir a parte mais difícil do processo em um fim de semana. Não teria sido possível sem sua contribuição. Eu coloquei as mudanças relacionadas em como PR, mas olhando para o backlog de PR, pode demorar um pouco até que seja considerado. Agora que tenho as principais especificações técnicas de que preciso, devo ser capaz de integrá-las e ter um código de lançamento funcional em algum lugar em torno do JPype, 1,2 nominalmente no calendário para o final do outono. Posso levá-lo adiante se houver grande interesse do usuário, mas ele está competindo com o Python do Java, que é um recurso importante para outros projetos.

Se alguém quiser ajudar a acelerar o esforço, a próxima etapa difícil será descobrir como construir uma imagem docker com tudo no lugar com um sistema parcialmente construído para que possamos executar uma compilação, carregar um emulador e executar a bancada de teste do android em pipelines azure (ou algum outro sistema de CI). Assim que tivermos um CI funcionando que possa detectar o que funciona e o que não funciona, estaremos muito mais perto de sermos capazes de implantar um software estável. Não tenho certeza se isso deve ser alojado no projeto JPype ou se devemos ter um projeto de teste android separado.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

ghost picture ghost  ·  3Comentários

stania picture stania  ·  6Comentários

tshirtman picture tshirtman  ·  23Comentários

cmacdonald picture cmacdonald  ·  20Comentários

tom19952000 picture tom19952000  ·  15Comentários