7

cython で明示的な相対インポートを使用しようとしています。リリース ノートから、相対インポートは cython 0.23 以降で機能するように思われます。私は Python 3.5 で 0.23.4 を使用しています。しかし、多くの参照が見つからないこの奇妙なエラーが発生します。エラーはcimportからのみです:

driver.pyx:4:0: relative cimport beyond main package is not allowed

ディレクトリ構造は次のとおりです。

    myProject/
        setup.py
        __init__.py
        test/
            driver.pyx
            other.pyx
            other.pxd

おそらくsetup.pyをめちゃくちゃにしているように見えるので、以下のすべてのファイルを含めました。

setup.py

from setuptools import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [
    Extension('other', ['test/other.pyx'],),
    Extension('driver', ['test/driver.pyx'],),
]

setup(
    name='Test',
    ext_modules=ext_modules,
    include_dirs=["test/"],
    cmdclass={'build_ext': build_ext},
)

driver.pyx

#!/usr/bin/env python
from . import other
from . cimport other

other.pyx

#!/usr/bin/env python

HI = "Hello"

cdef class Other:
    def __init__(self):
        self.name = "Test"

    cdef get_name(self):
        return self.name

other.pxd

cdef class Other:
    cdef get_name(self)

に引っ越してみまし__init__.pytest/。ディレクトリで実行setup.pyしてみましたtest(適切に調整していinclude_dirsます)。どちらも同じエラーを出します。

cimport other削除すると.機能しますが、これはおもちゃの例であり、他のフォルダーを適切にインポートできるように相対インポートが必要です。これは、このエラーで見つけられる唯一のであり、私の問題は異なると確信しています。

4

2 に答える 2

5

このエラーの唯一の他のは、machinekitプロジェクトの hal.pyx にありました。これは別のエラーであると確信していましたが、今日、そのエラーが解決された後、マシンキットが機能していることに気付きました。つまり、明示的な相対インポートが機能する必要があります。彼らのsetup.pyファイルはlinuxcncディレクトリツリーにないものを参照していますが、コンパイル時のある時点で作成されたと思います。重要なことはinclude_dirs、子ディレクトリではなく親ディレクトリが含まれていることです。

myProject私のプロジェクト構造に翻訳すると、のinclude_dirs代わりに入れたことを意味しtest/ます。このガイドを 2 度目に読んだ後、ようやく python がパッケージをどのように考えているかを少し理解し始めました。問題はinclude_dirs、これが子ディレクトリだったことです。これにより、cython はそれを単一のフラット ディレクトリとして効果的に表示したように見えます。この場合、相対インポートは許可されませんか? このようなエラーにより、より明確になった可能性があります。

ValueError: Attempted relative import in non-package

何が起こっているのかを正確に知るにはまだ十分な理解がありませんが、幸運にも解決策は比較的簡単でした. include_dirsネストされたファイル構造をcythonに認識させるように変更しました:

from setuptools import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [
    Extension('other', ['test/other.pyx'],),
    Extension('driver', ['test/driver.pyx'],),
]

setup(
    name='Test',
    ext_modules=ext_modules,
    include_dirs=["."],
    cmdclass={'build_ext': build_ext},
)

それは今すべて動作します!

于 2015-11-06T17:36:52.767 に答える
2

Cythonization エラーには、少なくとも 4 つの解決策があります (これらの結果は にありますcython == 0.29.24)。

  1. ファイルを追加し、ビルド中の s の名前を、ビルド中のモジュールのサブモジュール、つまりexample_package/__init__.pxd and に変更します(質問では、これらはandになります)。この変更は、以下で説明するように、インストールされたサブモジュールと をインポートするために必要です。以下で説明するように、キーワード parameter と argument がないため、この場合、インストールされたパッケージは実際には名前空間パッケージであることに注意してください。Extensionexample_package.otherexample_package.driverTest.otherTest.driver

    driverotherpackages=['example_package']

  2. example_package/__init__.py ファイルを追加し、ビルド中の の名前を、Extensionビルド中のモジュールのサブモジュール、つまりexample_package.otherとに変更しexample_package.driverます。この場合でも、__init__.pyが存在する場合、インストールされるパッケージexample_packageは名前空間パッケージになります。通常のパッケージにするpackages=['example_package']には、関数に渡す必要がありますsetuptools.setup

    の追加と同様に__init__.pxd、この変更は、インストールされたサブモジュールをインポートするために必要です。

  3. example_package/__init__.pxd ファイルを追加し、ステートメントをファイル内のcimport絶対に変更します(パッケージはこの代替方法でビルドおよびインストールされますが、s の名前も変更する必要があるため、インポートしません):cimportexample_package/driver.pyxExtension

     from . import other
     from example_package cimport other
    
  4. example_package/__init__.py 前の項目で行ったように、ファイルを追加し、ステートメントをfile 内のcimport絶対に変更します。パッケージはこれでビルドおよびインストールされますが、インポートされません。cimportexample_package/driver.pyx

質問は明示的に相対インポートを求めているため、その意味で、最初の2つの選択肢は相対インポートで機能するため、質問に対する答えです。

上記の 4 つの変更のいずれかにより、次のエラーが回避されます。

Error compiling Cython file:
------------------------------------------------------------
...
from . import other
from . cimport other
^
------------------------------------------------------------

example_package/driver.pyx:2:0: relative cimport beyond main package is not allowed

しかし、すでに上で述べたように、また以下で説明するように、Extensionインストールされたサブモジュールをインポートするには、最初または 2 番目の選択肢の名前を変更する必要があります (さらに、4 番目の選択肢でパラメーターとキーワード引数を渡すとpackages=[PACKAGE_NAME]、Python パッケージexample_packageをインポートできますが、そのサブモジュールdriverother)。

修正済みsetup.py

私が推奨するファイルsetup.pyは、すべての追加変更 (上記のビルドとインストールに必要な変更だけではありません) は次のとおりです。

"""Installation script."""
import os
import setuptools


try:
    from Cython.Build import cythonize
    cy_ext = f'{os.extsep}pyx'
except ImportError:
    # this case is intended for use when installing from
    # a source distribution (produced with `sdist`),
    # which, as recommended by Cython documentation,
    # should include the generated `*.c` files,
    # in order to enable installation in absence of `cython`
    print('`import cython` failed')
    cy_ext = f'{os.extsep}c'


PACKAGE_NAME = 'example_package'


def run_setup():
    """Build and install package."""
    ext_modules = extensions()
    setuptools.setup(
        name=PACKAGE_NAME,
        ext_modules=ext_modules,
        packages=[PACKAGE_NAME],
        package_dir={PACKAGE_NAME: PACKAGE_NAME})


def extensions():
    """Return C extensions, cythonize as needed."""
    extensions = dict(
        other=setuptools.extension.Extension(
            f'{PACKAGE_NAME}.other',
            sources=[f'{PACKAGE_NAME}/other{cy_ext}'],),
        driver=setuptools.extension.Extension(
            f'{PACKAGE_NAME}.driver',
            sources=[f'{PACKAGE_NAME}/driver{cy_ext}'],))
    if cy_ext == f'{os.extsep}pyx':
        ext_modules = list()
        for k, v in extensions.items():
            c = cythonize(
                [v],
                # show_all_warnings=True  # this line requires `cython >= 3.0`
                )
            ext_modules.append(c[0])
    else:
        ext_modules = list(extensions.values())
    return ext_modules


if __name__ == '__main__':
    run_setup()

その他の変更

この回答の他の変更は、パッケージを正常にビルドおよびインストールするために必要ではありませんが、他の理由で推奨されます。他のいくつかの変更については、以下に動機を説明します。

ご了承ください:

  • example_package/__init__.pxdまたは追加するだけexample_package/__init__.pyでは不十分であり、
  • Extension名前を変えるだけでは不十分で、
  • cimportステートメントをに変更するだけでfrom example_package cimport otherは不十分です。

ビルドとインストールには、これらの変更のうち 2 つが一緒に必要です。つまり、前述の 4 つの選択肢の 1 つです。

Cython のソースとから構築された拡張モジュールをインポートできるようにするには、拡張の名前を次のように変更する必要もあります。driver.pyxother.pyx

  • Extension('example_package.other', ...)
  • Extension('example_package.driver', ...)

が名前空間パッケージになったimportため、これが機能することに注意してください(CPython用語集のエントリ)。example_package

>>> 
<module 'example_package' (namespace)>
>>> import example_package.driver
>>> import example_package.other

(また、使用したファイルのパラメーターinclude_dirsを省略しました。これを以下に含めます。)setuptools.setupsetup.py

これらの変更は、パッケージのビルドとインストール、および拡張モジュールのインポートに必要です。拡張モジュールが含まれていない (したがって、名前空間パッケージになっていない) 場合に、インストールされたパッケージを Python からインポートするには:

  • ディレクトリにファイル__init__.pyを追加する必要がありますexample_package/(問題のディレクトリはディレクトリですTest/)。
  • キーワード引数packages=[example_package],を関数に渡す必要がありますsetuptools.setup

それ以外の場合、ステートメントimport example_packageModuleNotFoundError. ファイルの追加は、パッケージを通常のパッケージ(CPython用語集のエントリ__init__.py) にするためにも必要です。これは通常、名前空間パッケージの代わりに意図されているものです。

を使用するかどうか__init__.pxd

通常の Python パッケージには__init__.pyファイルが含まれています。__init__.pxdファイルは、他のパッケージがヘッダーを必要とする場合にのみ関連します*.pxd。そうでない場合、上記の 4 つの解決策は基本的に 2 つの解決策であり、それぞれにまたはの代替手段example_package/__init__.pyがあるため、ファイルで十分であるように思われます。__init__.py__init__.pxd

したがって、ファイルとその配置に関する私の推奨事項は次のとおりです。

.
├── example_package
│   ├── __init__.py
│   ├── driver.pyx
│   ├── other.pxd
│   └── other.pyx
└── setup.py

両方の変更が必要です

ファイルを追加するだけ__init__.pxdで、cythonization エラーが発生します。

Error compiling Cython file:
------------------------------------------------------------
...
from . import other
from . cimport other
^
------------------------------------------------------------

example_package/driver.pyx:3:0: relative cimport beyond main package is not allowed

cimportステートメントのみを変更すると ( なしで__init__.pxd)、cythonization エラーが発生します。

Error compiling Cython file:
------------------------------------------------------------
...
#!/usr/bin/env python
from . import other
from example_package cimport other
^
------------------------------------------------------------

example_package/driver.pyx:3:0: 'example_package.pxd' not found

Error compiling Cython file:
------------------------------------------------------------
...
#!/usr/bin/env python
from . import other
from example_package cimport other
^
------------------------------------------------------------

example_package/driver.pyx:3:0: 'example_package/other.pxd' not found

パッケージの命名

example_package上記ではパッケージの名前として書いていますTest/が、これが実際に機能することを確認し、必要な最小限の変更が__init__.pxdファイルとfrom example_package cimport other.

均一性のために、このパッケージ名でTest/実行するときにディレクトリの名前も実際に変更しましたが、現時点では大文字と小文字を区別するファイルシステムを使用していないため、キーワード引数と一緒に名前が付けられsetup.pyたディレクトリであるかどうかはわかりません。問題では、大文字と小文字を区別するファイルシステムで問題が発生した可能性があります。test/name='Test',setup.py

そう:

  • Testパッケージ名とディレクトリ名として使用Testすると、ビルドとインストールに役立ちました。
  • testパッケージ名とtestディレクトリ名として使用すると、ビルドとインストールに役立ちました。

別のパッケージ名を使用することをお勧めします。また、以下の理由により:

  • パッケージに名前が付けられている場合のインポートはTest、ステートメントで行われimport Testます。書き込みは別のimport testパッケージをインポートします (以下を参照)。
  • パッケージ名として使用すると、ファイルが追加された場合でも、以下で説明する理由によりtest、インストールされたパッケージがインポートされません。test__init__.py

いずれにせよ、以下に説明する理由により、メイン パッケージのテスト ハーネスとしてのみ使用することを意図した補助パッケージであっても、パッケージ名を変更することをお勧めします。

また、小文字のパッケージ命名はPEP 8によって義務付けられているためtest、これはテストのディレクトリであると理解される可能性がありますが、これが実際にメイン パッケージの例であることが意図されている場合はそうではありません。

パッケージとディレクトリに名前を付けたときに、ビルドおよびインストール後に発生するエラーtestは次のとおりです (ドット ... は、実際の出力を編集した結果です)。

>>> import test
>>> test
<module 'test' from '.../lib/python3.9/test/__init__.py'>

つまり、CPython には次のようなパッケージが含まれていますtest

このtestパッケージには、Python のすべてのリグレッション テストと、モジュールtest.supportおよびtest.regrtest.

したがって、この名前testは、インストール後にインポートすることを意図したサンプル パッケージには使用できません (ただし、パッケージはビルドされてインストールされ、さらには によってアンインストールされpip uninstall -y testます)。

別の詳細は、from test cimport otherコンパイルされたとしても実際には間違っているということです。ビルドされたtestパッケージが実際に魔法のようにインポートされた場合 (CPython のtestパッケージが存在する場合)、実行時にこのcimportステートメントは CPython のtestパッケージにデフォルト設定されていたからです。それにもかかわらず、Cython の翻訳は、これをビルドされたパッケージcimportから実際にインポートされた他の形式に変換する可能性があります。CPython のパッケージが存在する場合test.other、インストールされたパッケージのインポートは不可能であるように見えるため、これが実行時エラーを引き起こしたかどうかは不明です。testtestcimport

また、次の点に注意してください。

注:このtestパッケージは、Python による内部使用のみを目的としています。これは、Python の中心的な開発者のために文書化されています。ここに記載されているコードは、Python のリリース間で予告なく変更または削除される可能性があるため、Python の標準ライブラリ以外でこのパッケージを使用することはお勧めできません。

すべての実験の合間に、 を実行しrm -rf build dist *.egg-info test/*.cます。そのため、ファイル配置を以前に示したものに変更する前に、私が使用したものは質問と同じです。

パッケージの名前をexample_package

質問のファイル内のパラメーターに指定された引数に基づいて、実際にインストールするパッケージが含まれているexample_packageと仮定して、パッケージの名前を に変更しました。test/name=setup.py

この名前変更の動機は、「test」または「tests」が通常、Python パッケージに付随するテストのディレクトリに名前を付けるために使用されることです。このようなディレクトリや、テストの使用方法については、さまざまな取り決めがあります。次のセクションでは、テストを配置するための私の提案について説明します。

可能性に関しては、次のセクションで説明する以外の配置が一般的に使用されており、パッケージ自体内のディレクトリにテストを配置することも含まれます。myProject/質問が書いてあり、ファイルがあることを考えると、質問がmyProject/__init__.py実際にそのような取り決めを使用しているかどうかはわかりません。

ただし、その場合は、実際driverotherはテスト モジュールになります。Testモジュールが行うことである別のパッケージ(質問で呼ばれる)としてテストをインストールしますが、メインパッケージのモジュールでmyProject/setup.pyあることを示唆してdriverいるotherため、メインパッケージは「テスト」と呼ばれます。

そうでない場合、つまりdriverotherが実際にテスト モジュールであり、メイン パッケージのセットアップ スクリプトでsetup.pyなく、メイン パッケージのテストのみを目的とした「補助」パッケージをビルドおよびインストールするセットアップ スクリプトである場合 (この場合は「myProject」という名前で、質問setup.pyのディレクトリを含むディレクトリに存在する場合)、への名前を変更すると、これがメインパッケージであることに対応しません。(Cython コードを含むテスト ハーネス パッケージを用意することも興味深いため、コンパイルが必要であり、場合によってはインストールも必要です。)myProject/Testexample_package/

その場合、おそらく のTest代わりに名前を変更できますtests_of_example_package。つまり、その場合、パッケージの名前に「test」という単語を含めることは適切ですが、パッケージを補助として修飾することexample_packageは明示的であるように見えます。明示的は暗黙的よりも優れています ( PEP 20 )。

(テストは__init__.py、これを補助 Python パッケージとしてインストールしない場合でも (を使用して) パッケージとして配置されることがあります (付属するメイン Python パッケージのテスト ハーネスとしてのみ意図されています)。動機は、テスト スイートの共通モジュールをインポートできるようにすることです。複数のテスト モジュールで使用されますが、それ自体はテスト ランナーによって直接実行されるモジュールではありません。)

これがメインのパッケージなら、サンプルでは例を書く目的で「Test」を使用したと思います。もしそうなら、名前を変更する唯一の理由 (小文字は別として) は、メイン パッケージ自体とそのテストを区別することです。

PEP 8では、Python パッケージの小文字の名前が義務付けられています。

アンダースコアの使用はお勧めできませんが、Python パッケージの名前もすべて小文字で短くする必要があります。

のアンダースコアexample_packageは単なる例です。

テストの手配

テストtest/は、Python パッケージを含むディレクトリと同じディレクトリにあり、パッケージにちなんで名付けられたディレクトリに配置される場合があります。たとえば、このアプローチを強くお勧めします (このツリーはプログラムで作成されましたtree)。

.
├── example_package
│   └── __init__.py
├── setup.py
└── tests
    └── module_name_test.py

パッケージをソース ディレクトリから誤ってインポートせずにテストするには、パッケージインストールexample_packageされている場所 (通常site-packages/は. これは、テストに対する最も信頼性の高いアプローチであり、各テスト フレームワークの動作、テスト フレームワークのさまざまな構成オプションの動作、オプション同士の相互作用、テスト フレームワーク自体のバグがテストに与える影響に依存しません。cdtests/

このようにして、example_package他のディレクトリ配置を使用する理由がなくても、パッケージ ソースをディレクトリ内に配置できます。

Python モジュールのシバン

ファイル内のシバン*.pyxは効果がないため、削除できます。シバン行は Cython によって Python コメント行として扱われ、Cython*.cがファイルから生成するファイル内のどこかに後で C コメント内に移動され*.pyxます。したがって、効果はありません。gccCython が行うように(Cython が を呼び出すかgcc、または別のコンパイラがシステム、環境パス、環境変数に依存するかどうかにかかわらず)、直接呼び出し (または別の C コンパイラ) によってコンパイルされることを意図した C ソースでのシバン行の使用を認識していません。 、およびその他の情報)。

また、シバンは、実行可能ファイルとして実行される可能性のある Python モジュールにのみ関連します。これは、Python パッケージ内のモジュールに当てはまることを意図したものではないため、そこでシバン行が使用されることはほとんどありません。

例外は、実験やデバッグの目的など、開発中に直接実行されることがまれなパッケージ モジュールである可能性があります。__main__それにもかかわらず、そのようなモジュールにはスタンザがあると予想されます。

そのため、シバンが関連する Python モジュールには、通常、__main__スタンザもあります。

完全を期すために、setup.pyは として実行されることを意図し__main__ており、__main__スタンザがありますが、セットアップ スクリプトを実行する方法 (使用しない場合はpip--usingpipを強くお勧めします) は によるものpython setup.pyであるため、 にシバンは必要ありませsetup.pyん (シバンは表示されません)。質問の中にあります-完全を期すためにこれについて言及しています)。

の代わりにインポートsetuptoolsするsetup.pydistutils

distutilsモジュール]() は、PEP 632で指定されているように Python 3.10 で非推奨になり、Python 3.12 で削除されます。

Cython が拡張機能に存在しない場合の切り替え.c、代わりに.pyx

これは、 Cython の推奨事項と一致しています。

生成されたファイルと Cython ソースを配布することを強くお勧めします.c。これにより、ユーザーは Cython を使用可能にする必要なくモジュールをインストールできます。

setup.py変更されないままのモジュールスコープ変数の大文字の名前(「定数」)

モジュールスコープの Python 変数は、定数として使用することを意図しています。つまり、最初の代入後も変更されません。PEP 8では、アンダースコア付きの大文字の識別子を持つことが義務付けられています。

通常、定数はモジュール レベルで定義され、すべて大文字で書かれ、アンダースコアで単語が区切られます。例には、 および が含まMAX_OVERFLOWTOTALます。

したがって、識別子PACKAGE_NAME.

フォーマットされた文字列

Python >= 3.6 を必要とするフォーマット済み文字列リテラルを使用しました。

モジュール内の関数としてコードを配置するsetup.py

これは、関数名を使用してコードのさまざまなセクションに名前を付けることができ、スタンザ__main__を含めることにより、として実行された場合にのみコードを実行できるため、外部コードに関連する可能性のある特定の機能をインポートして使用できるため、一般的には良い方法ですフレームワーク) 必ずしもすべてのコードを実行する必要はありません。たとえば、関数を実行する必要はありません。__main__setup.pysetuptools.setup

質問は最小限の実例を示しているため、質問には小さなsetup.pyものが関連しています。このセクションは、質問ではなく、実際のパッケージで何をすべきかの推奨事項として書いています。

内のモジュールと関数のドキュメント文字列にも同じことが当てはまりますsetup.py

また、関数をトップダウンで配置することをお勧めします。このレイアウトは読みやすいため、呼び出し元が呼び出し先の上に配置されます。

一般性のために使用os.extsepしましたが、ドットを使用しても機能すると思いますが、読みやすくなっています。

パッケージの配置

前述のように、ビルド エラー「メイン パッケージを超える相対 cimport は許可されていません」を回避するために必要な質問の例への唯一の変更は、または のいずれかの追加__init__.py__init__.pxdおよび の絶対cimport内部driver.pyxまたは名前の変更のいずれかでしたExtensions

ファイルの削除__init__.py

__init__.py最終版では、 と同じディレクトリにあるファイルを削除しましたsetup.py。私の理解では、このファイルはこの例では効果がありません。test/この例がメイン パッケージのディレクトリとして使用することを意図している場合は、すべて__init__.pyが 内に表示されtest/ます。

test/が実際にはメイン パッケージのテストの補助パッケージである場合、 はメイン パッケージ__init__.pyの一部であり、パッケージとは無関係test/です。ただし、その場合、メイン パッケージとテスト ハーネス パッケージの両方のビルドを担当するsetup.py上記のファイルが存在するようです。myProject/

絶対インポートの使用

language_levelcython < 3.0.0Python 3 でも、デフォルトの inは 2です。

language_level (2/3/3str) モジュールのコンパイルに使用する Python 言語レベルをグローバルに設定します。デフォルトは Python 2 との互換性です。Python 3 ソース コード セマンティクスを有効にするには、モジュールの開始時にこれを 3 (または 3str) に設定するか、"-3" または "--3str" コマンド ライン オプションをコンパイラに渡します。

質問は Python 3.5 と を使用してcython == 0.23.4いるため、これが当てはまります。

では、デフォルトの Cython セマンティクスが変更されていcython >= 3.0.0ます。

デフォルトの言語レベルは3str、Python 3 セマンティクスなどに変更されました。

Python 2 と Python 3 の両方のセマンティクス (プレリリースを渡すcompiler_directives=dict(language_level=3)、またはプレリリースをインストールするcython == 3.0.0a8) を使用すると、最初の 2 つのソリューション (相対インポートを使用する) が機能します。

それにもかかわらず、PEP 8 では絶対インポートが推奨されています。

インポート システムが正しく構成されていない場合、通常はより読みやすく、より適切に動作する (または少なくともより適切なエラー メッセージを表示する) 傾向があるため、絶対インポートをお勧めします ...

絶対インポートは、パッケージの構造のリファクタリングに対しても堅牢です。それらは明示的であり、明示的は暗黙的よりも優れています ( PEP 20 )。

driver.pyxこの変更後のモジュールは次のようになります。

from example_package import other
from example_package cimport other

この回答のコードは、Python パッケージsetup.pyのファイルに記述した内容に基づいています。download.pydd

于 2021-07-15T05:11:58.510 に答える