在同一环境中使用 CLI Versus 可执行文件从 parquet 读取 DataFrame 时的不同行为

Different behavior while reading DataFrame from parquet using CLI Versus executable on same environment

请将以下程序视为Minimal Reproducible Example -MRE:

import pandas as pd
import pyarrow
from pyarrow import parquet

def foo():
    print(pyarrow.__file__)
    print('version:',pyarrow.cpp_version)
    print('-----------------------------------------------------')
    df = pd.DataFrame({'A': [1,2,3], 'B':['dummy']*3})
    print('Orignal DataFrame:\n', df)
    print('-----------------------------------------------------')
    _table = pyarrow.Table.from_pandas(df)
    parquet.write_table(_table, 'foo')
    _table = parquet.read_table('foo', columns=[])    #passing empty list to columns arg
    df = _table.to_pandas()
    print('After reading from file with columns=[]:\n', df)
    print('-----------------------------------------------------')
    print('Not passing [] to columns parameter')
    _table = parquet.read_table('foo')                #Not passing any list
    df = _table.to_pandas()
    print(df)
    print('-----------------------------------------------------')
    x = input('press any key to exit: ')

if __name__=='__main__':
    foo()

当我从 console/IDE 运行 时,它会读取 columns=[] 的全部数据:

(env) D:\foo>python foo.py
D:\foo\env\lib\site-packages\pyarrow\__init__.py
version: 3.0.0
-----------------------------------------------------
Orignal DataFrame:
    A      B
0  1  dummy
1  2  dummy
2  3  dummy
-----------------------------------------------------
After reading from file with columns=[]:
    A      B
0  1  dummy
1  2  dummy
2  3  dummy
-----------------------------------------------------
Not passing [] to columns parameter
   A      B
0  1  dummy
1  2  dummy
2  3  dummy
-----------------------------------------------------
press any key to exit:

但是当我从使用 Pyinstaller 创建的可执行文件 运行 时,它没有读取 columns=[]:

的数据
E:\foo\dist\foo\pyarrow\__init__.pyc
version: 3.0.0
-----------------------------------------------------
Orignal DataFrame:
    A      B
0  1  dummy
1  2  dummy
2  3  dummy
-----------------------------------------------------
After reading from file with columns=[]:
 Empty DataFrame
Columns: []
Index: [0, 1, 2]
-----------------------------------------------------
Not passing [] to columns parameter
   A      B
0  1  dummy
1  2  dummy
2  3  dummy
-----------------------------------------------------
press any key to exit:

如您所见,传递 columns=[] 会在可执行文件中给出空数据框,但在 运行 直接 python 文件时不存在此行为,我不确定为什么对于同一环境中的同一代码,存在这两种不同的行为。

正在查看 source code at GitHubparquet.read_table 的文档字符串:

columns: list
If not None, only these columns will be read from the file. A column name may be a prefix of a nested field, e.g. 'a' will select 'a.b', 'a.c', and 'a.d.e'.

read_table 进一步调用 Scanner dataset.read that calls _dataset.to_table which returns call to self.scanner which then returns call to static method from_dataset class。

到处都是None作为columns参数的默认值,如果None[]在python中直接转换为布尔值,它们确实都是 False,但是如果 []None 进行检查,那么它将是 False,但是如果它获取所有列columns=[] 因为布尔值的计算结果为 False,或者因为列表为空所以它根本不读取任何列。

但是,为什么从命令 line/IDE 中 运行 宁它的行为与从使用 Pyinstaller 为相同版本的 Pyarrow 创建的可执行文件 运行 宁它时的行为不同?

我所在的环境:

如果你想试一试,这里是供你参考的规格文件(你需要更改pathex参数):

foo.spec

# -*- mode: python ; coding: utf-8 -*-
import sys ; sys.setrecursionlimit(sys.getrecursionlimit() * 5)
block_cipher = None


a = Analysis(['foo.py'],
             pathex=['D:\foo'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='foo',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='foo')

pyarrow.parquet.read_table 的 pyarrow 文档可能不清楚。我提出 ARROW-13436 来澄清这一点。

从某些测试来看,行为似乎在某些时候从无列更改为所有列,然后又变回(在 4.0 中)无列。我认为没有专栏是正确的行为。

所以我猜测您的可执行文件使用的 pyarrow 版本与您的 IDE 不同。您通常可以通过 运行...

来确认这一点
import pyarrow
print(pyarrow.__file__)
print(pyarrow.cpp_version)

...在两种环境下比较结果。

在@ThePyGuy(OP)评论后,找到了原因。

原因是,当他在 IDE 上 运行 时,他 运行 它在环境中,而当他 运行 在命令行上时,它在环境之外。和不同的 DLL 文件。所以修复是将 pyarrow 包复制到环境之外,它给出了相同的结果。

感谢@U12-Forward 协助我调试问题。

经过一些研究和调试,并探索了库程序文件,我发现 pyarrow 使用 _ParquetDatasetV2ParquetDataset 函数,这两个函数本质上是从 parquet 文件读取数据的不同函数,_ParquetDatasetV2 被用作 legacy_mode,即使这些函数是在 pyarrow.parquet 模块中定义的,它们来自 pyarrow 的 Dataset 模块,它在使用创建的可执行文件中丢失Pyinstaller.

当我将 pyarrow.Dataset 添加为隐藏导入并创建构建时,由于 Dataset 模块使用的几个缺少依赖项,exe 在执行时会引发 ModuleNotFoundError。为了解决它,我将环境中的所有 .py 文件添加到隐藏的导入并再次创建构建,最后它成功了,我的意思是我能够在两者中观察到相同的行为环境。

修改后的spec文件修改后如下所示:

# -*- mode: python ; coding: utf-8 -*-
import sys ; sys.setrecursionlimit(sys.getrecursionlimit() * 5)
block_cipher = None


a = Analysis(['foo.py'],
             pathex=['D:\foo'],
             binaries=[],
             datas=[],
             hiddenimports=['pyarrow.benchmark', 'pyarrow.cffi', 'pyarrow.compat', 'pyarrow.compute', 'pyarrow.csv', 'pyarrow.cuda', 'pyarrow.dataset', 'pyarrow.feather', 'pyarrow.filesystem', 'pyarrow.flight', 'pyarrow.fs', 'pyarrow.hdfs', 'pyarrow.ipc', 'pyarrow.json', 'pyarrow.jvm', 'pyarrow.orc', 'pyarrow.pandas_compat', 'pyarrow.parquet', 'pyarrow.plasma', 'pyarrow.serialization', 'pyarrow.types', 'pyarrow.util', 'pyarrow._generated_version', 'pyarrow.__init__'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='foo',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='foo')

此外,为了创建构建,我使用 --paths 参数包含了虚拟环境的路径:

pyinstaller --path D:\foo\env\Lib\site-packages foo.spec

执行上述步骤后执行:

E:\foo\dist\foo\pyarrow\__init__.pyc
version: 3.0.0
-----------------------------------------------------
Orignal DataFrame:
    A      B
0  1  dummy
1  2  dummy
2  3  dummy
-----------------------------------------------------
After reading from file with columns=[]:
    A      B
0  1  dummy
1  2  dummy
2  3  dummy
-----------------------------------------------------
Not passing [] to columns parameter
   A      B
0  1  dummy
1  2  dummy
2  3  dummy
-----------------------------------------------------
press any key to exit:

确实没有任何地方提到 columns=[] 的期望行为,但是通过@Pace 查看 ARROW-13436 opened in pyarrow,似乎 columns=[] 的期望行为是阅读根本没有数据列,但它不是官方构造,因此它可能是 pyarrow 3.0.0 本身的错误。