Anaconda虚拟环境离线迁移

项目中遇到需要在离线环境下为anaconda安装python 3.8虚拟环境的问题,即服务器不能连接互联网的环境,这里记录一下解决方法。

一、环境信息

> uname -a
Linux taos-1 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

> conda --version
conda 4.5.4

二、在线安装虚拟环境

如果已经有一个需要迁移的虚拟环境,则可以跳过这一步。

先找一台在线的服务器,安装anaconda所需的虚拟环境,我这里虚拟环境是python 3.8加上pandasnumpymatplotlib等几个常用软件包。

> conda create -n python38 python=3.8
> conda install -n python38 pandas numpy matplotlib

三、备份虚拟环境

使用conda pack命令完整备份虚拟环境:

> conda install -c conda-pack
> conda pack -n python38 -o env_python38_pack.tar.gz

备份得到的是一个.tar.gz压缩包,如果查看压缩包里的内容,目录结构如下图所示:

file

四、离线恢复虚拟环境

离线恢复不需要conda-pack命令,只需要将打好的虚拟环境包复制到目标环境的临时目录,然后解压缩到anaconda的envs目录下即可:

> mkdir /root/anaconda3/envs/python38
> tar zxvf /tmp/env_python38_pack.tar.gz -C /root/anaconda3/envs/python38

输入conda info -e命令可以看到虚拟环境已经添加上了:

file

五、补充说明

1. 是否可以直接备份env下的目录?

一般不可以,按 conda-pack 官网给出的理由如下:

A tool like conda-pack is necessary because conda environments are not relocatable. Simply moving an environment to a different directory can render it partially or completely inoperable. conda-pack addresses this challenge by building archives from original conda package sources and reproducing conda’s own relocation logic.

网上有些文章提到可以直接备份envs/python38目录然后用下面的命令恢复:

> conda create -n [name] --clone [path] --offline

但经过测试当虚拟环境有依赖包时,离线恢复环境会失败,提示下面的错误,原因可能是虚拟环境的依赖包与目标环境pkgs目录下的包版本不同:

RuntimeError('EnforceUnusedAdapter called with url https://repo.anaconda.com/pkgs/main/linux-64/ca-certificates-2022.10.11-h06a4308_0.tar.bz2\nThis command is using a remote connection in offline mode.\n',)

file

观察源anaconda下的pkgs目录可以发现,conda install安装新的虚拟环境时有一些依赖包是安装在pkgs目录下的。那么如果备份时将pkgs目录也备份下来是否可以呢?我认为有以下缺点:

  1. pkgs目录比较大不方便网络传输(一般2GB~3GB);
  2. 恢复时需要处理envspkgs两个目录比较繁琐;
  3. 对目标环境的侵入性比较高,因为会覆盖pkgs下的一些文件。

如果只备份pkgs下的部分新安装的目录,比如按修改时间过滤,则可能遇到类似下面的错误:

FileNotFoundError: [Errno 2] No such file or directory: '/root/anaconda3/pkgs/libstdcxx-ng-11.2.0-h1234567_1/info/index.json'

2. 是否可以用conda export命令备份?

如果目标环境是在线的可以用conda export命令备份,然后用conda create -f恢复。但对离线的情况不适用。

> conda env export -n python38 > /tmp/python38.yml
> cat /tmp/python38.yml
name: python38
channels:
  - defaults
dependencies:
  - ca-certificates=2022.10.11=h06a4308_0
  - certifi=2022.9.24=py38h06a4308_0
  - ld_impl_linux-64=2.38=h1181459_1
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.3=h5eee18b_3
  - openssl=1.1.1q=h7f8727e_0
  - pip=22.2.2=py38h06a4308_0
  - python=3.8.13=haa1d7c7_1
  - readline=8.2=h5eee18b_0
  - setuptools=65.5.0=py38h06a4308_0
  - sqlite=3.39.3=h5082296_0
  - tk=8.6.12=h1ccaba5_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.2.6=h5eee18b_0
  - zlib=1.2.13=h5eee18b_0
prefix: /disk1/anaconda3/envs/python38

# 恢复环境
> conda env create -f python38.yaml

综上,建议用conda pack命令备份anaconda的虚拟环境。

六、参考链接

https://www.anaconda.com/blog/moving-conda-environments
https://conda.github.io/conda-pack/
https://www.jianshu.com/p/adb927fa0091
https://blog.csdn.net/snowing118102/article/details/84319736
https://blog.csdn.net/CREATOR_Jay_xu/article/details/91971446

在Python里访问Java对象 – Py4J

Py4J是一套java+python软件包,利用它开发者可以在python里动态的访问jvm里的java对象,其基本原理是在java端创建一个Socket服务,在python端使用Socket客户端调用,Py4J在python语法上的进行了精心的包装,使得在python里的这些远程java对象使用起来与本地的python对象十分接近。

环境信息

Java Python Py4J
11.0.10 3.8.5 0.10.9.7

Java服务端

创建一个新的java工程,并在pom.xml里引入py4j依赖:

<dependency>
    <groupId>net.sf.py4j</groupId>
    <artifactId>py4j</artifactId>
    <version>0.10.9.7</version>
</dependency>

以下借用py4j官方提供的例子。首先定义一个Java类提供一些功能,这里是用一个堆栈类作为例子,提供了push, pop等功能:

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);
        }
    }
}

然后要提供一个对应的EntryPoint类作为服务提供者,这个类实现main函数,执行后会启动一个Socket服务,用于接收python客户端的请求。

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) {
        // 默认端口号25335,可以在构造方法里配置其他端口号
        GatewayServer gatewayServer = new GatewayServer(new StackEntryPoint());
        gatewayServer.start();
        System.out.println("Gateway Server Started");
    }

}

直接执行StackEntryPoint就启动了Java服务端。

Python客户端

首先安装py4j的最新版本:

> pip install py4j

在python命令行里执行下面的代码就可以获得一个Stack对象,其中.entry_point得到的是GatewayServer里的StackEntryPoint实例。

>>> from py4j.java_gateway import JavaGateway
>>> gateway = JavaGateway()
>>> stack = gateway.entry_point.getStack()

测试一下这个Stack对象的功能:

>>> stack
JavaObject id=o0

>>> stack.getInternalList()
['Initial Item']

>>> stack.push('item2')
>>> stack.getInternalList()
['item2', 'Initial Item']

Stack里可以直接push字符串,也可以push一个java的list:

>>> list = gateway.jvm.java.util.ArrayList()
>>> list.add('item3')
>>> list.add('item4')
>>> stack.pushAll(list)
>>> stack.getInternalList()
['item4', 'item3', 'item2', 'Initial Item']

但不可以直接push一个python的list,将会抛出异常。需要借助py4j提供的ListConverter先转换为java list:

>>> stack.pushAll(['item3','item4'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\zhanghao\anaconda3\lib\site-packages\py4j\java_gateway.py", line 1314, in __call__
    args_command, temp_args = self._build_args(*args)
  File "C:\Users\zhanghao\anaconda3\lib\site-packages\py4j\java_gateway.py", line 1283, in _build_args
    [get_command_part(arg, self.pool) for arg in new_args])
  File "C:\Users\zhanghao\anaconda3\lib\site-packages\py4j\java_gateway.py", line 1283, in <listcomp>
    [get_command_part(arg, self.pool) for arg in new_args])
  File "C:\Users\zhanghao\anaconda3\lib\site-packages\py4j\protocol.py", line 298, in get_command_part
    command_part = REFERENCE_TYPE + parameter._get_object_id()
AttributeError: 'list' object has no attribute '_get_object_id'

如果要在python里使用任意的Java对象,可以用gateway.jvm引用完整的java类名来创建对象实例:

>>> random = gateway.jvm.java.util.Random()
>>> random.nextInt()
433815240

所以在本文的例子里,gateway.entry_point.getStack()gateway.jvm.py4j.examples.Stack()基本是等价的,除了前者是单例的,初始化的内容有所不同。

参考资料

https://www.py4j.org/getting_started.html

Python处理csv速度慢问题一例和解决

问题现象

一段python代码处理一个60万行csv文件耗时过长,从内存增长图和作业日志上看,处理此csv文件用了十几分钟,但在本地python命令行里测试读取200万行的csv(列数相同)文件只需要不到30秒,因此猜测是在pandas处理此文件数据时存在未优化的代码。

file

分析和解决

经过排查,发现是在讲csv文件里的时间戳转换为datetime类型时,消耗了大量时间,更换timestamp转datetime的函数:

# input[key]['k_ts'] = input[key]['k_ts'].apply(lambda x:pd.to_datetime(x, utc=True, unit='ms').tz_convert('Asia/Shanghai'))

input[key]['k_ts'] = input[key]['k_ts'].apply(lambda x:datetime.datetime.fromtimestamp(x/1000))

同时反方向的datetime转换到timestamp函数也做相应更改:

# output[key][ts_col] = output[key][ts_col].apply(lambda x:x.timestamp() * 1000).astype('int64')

output[key][ts_col] = output[key][ts_col].apply(lambda x:int(time.mktime(x.timetuple())*1e3 + x.microsecond/1e3))

修改后问题解决。

Pentaho Mondrian和数据分析(OLAP)

cubo

下载和安装Mondrian

Mondrian官方网站上的文档Installation部分似乎很久没更新了,例如文档里提到下载.zip格式的打包文件,实际上网站和sourceforge上都没有提供,只提供了.jar格式(网站提供版本为3.12.0.1),看起来已经转移到github了。经过实践,安装的方法如下:

Continue reading "Pentaho Mondrian和数据分析(OLAP)"

基于RapidMiner开发问题和解决

file

RapidMiner(前身是YALE)是一个十分流行的开源数据挖掘软件,它不仅提供了一个GUI的数据处理和分析环境,还提供了Java API以便将它的能力嵌入其他应用程序。

BTW,选择RapidMiner而非WEKA的主要原因有两个:

1、RapidMiner对Java开发更方便

2、RapidMiner同时提供free license和commertial license,而WEKA只提供GNU license(无法用来开发商用软件)

本文记录了基于RapidMiner开发数据分析应用程序时遇到的一些问题和解决方法。

1、安装了RapidMiner 4.3,但执行RapidMiner.init()时抛出异常java.lang.UnsupportedClassVersionError: Bad version number in .class file

RapidMiner 4.3是用JDK1.6编译的(虽然用1.5也可以编译),因此在JDK1.5或以下版本环境里调用会抛出上述异常。解决办法有两个,一是安装JDK1.6,二是从CVS里下载RapidMiner的源代码自己在1.5里编译并导出jar文件。参考链接

2、在Eclipse应用里执行RapidMiner.init()时提示java.lang.IllegalArgumentException: URI scheme is not "file"

需要设置环境变量"rapidminer.home"到rapidminer安装目录,以便初始化时能找到"rapidminerrc"这个文件。即使未安装rapidminer,也要保证在这个目录下有个"etc"目录,里面有"rapidminerrc"文件。(另,还有个方法是设置"rapidminer.rcfile"环境变量指向rapidminerrc文件,未试验)

3、在Eclipse应用里执行RapidMiner.init()时提示[Error] Cannot find 'operators.xml'.

经过跟踪rapidminer代码,发现需要把operators.xml文件放在classpath下的com.rapidminer.resources包里。所以结合上面一条问题,可以执行的代码如下:

System.setProperty(RapidMiner.PROPERTY_RAPIDMINER_HOME, "D:/eclipse3.4m7/workspace/yale");
RapidMiner.init(); 

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2009/02/16/1392063.html