Cython kodunu içeren bir Python paketini nasıl yapılandırmalıyım


122

Biraz Cython kodu içeren bir Python paketi yapmak istiyorum . Cython kodum güzel çalışıyor. Ancak, şimdi onu en iyi nasıl paketleyeceğimi bilmek istiyorum.

Sadece paketi kurmak isteyen çoğu insan için .c, Cython'un oluşturduğu dosyayı dahil etmek setup.pyve modülü üretmek için bunu derlemek istiyorum. O zaman kullanıcının paketi kurmak için Cython kurulu olmasına gerek kalmaz.

Ancak paketi değiştirmek isteyebilecek kişiler için, Cython .pyxdosyalarını da sağlamak istiyorum ve bir şekilde setup.pyonları Cython kullanarak oluşturmalarına da izin veriyorum (böylece bu kullanıcıların Cython yüklemesine ihtiyacı olacaktı ).

Paketteki dosyaları bu iki senaryoyu da karşılayacak şekilde nasıl yapılandırmalıyım?

Cython dokümantasyon Biraz yol verir . Ancak setup.pyCython vakalarını içeren / içermeyen bir single'ın nasıl yapılacağını söylemiyor.


1
Görüyorum ki soru, cevapların hepsinden daha fazla oy alıyor. İnsanların yanıtları neden yetersiz bulabileceğini merak ediyorum.
Craig McQueen

4
Tam olarak cevabı veren dokümantasyonun bu bölümünü buldum .
Will

Yanıtlar:


72

Bunu şimdi kendim bir Python paketinde yaptımsimplerandom ( BitBucket deposu - DÜZENLE: şimdi github ) (Bunun popüler bir paket olmasını beklemiyorum, ancak Cython öğrenmek için iyi bir şanstı).

Bu yöntem, bir .pyxdosya oluşturmanın Cython.Distutils.build_ext(en azından Cython sürüm 0.14 ile) her zaman .ckaynak .pyxdosyayla aynı dizinde bir dosya oluşturduğu gerçeğine dayanır .

İşte setup.pytemellerini gösterdiğini umduğum kısaltılmış bir versiyonu :

from distutils.core import setup
from distutils.extension import Extension

try:
    from Cython.Distutils import build_ext
except ImportError:
    use_cython = False
else:
    use_cython = True

cmdclass = {}
ext_modules = []

if use_cython:
    ext_modules += [
        Extension("mypackage.mycythonmodule", ["cython/mycythonmodule.pyx"]),
    ]
    cmdclass.update({'build_ext': build_ext})
else:
    ext_modules += [
        Extension("mypackage.mycythonmodule", ["cython/mycythonmodule.c"]),
    ]

setup(
    name='mypackage',
    ...
    cmdclass=cmdclass,
    ext_modules=ext_modules,
    ...
)

Ayrıca , bir kaynak dağıtımına (ile oluşturulan bir kaynak dağıtımı) dahil MANIFEST.inedilmesini sağlamak için düzenledim :mycythonmodule.cpython setup.py sdist

...
recursive-include cython *
...

mycythonmodule.cSürüm kontrolü 'trunk'a' (veya Mercurial için 'varsayılan') taahhütte bulunmuyorum. Bir sürüm yaptığımda , kaynak kodu dağıtımı için mevcut ve güncel python setup.py build_extolduğundan emin olmak için bir ilk yapmayı hatırlamam gerekiyor mycythonmodule.c. Ayrıca bir sürüm dalı yapıyorum ve C dosyasını şubeye teslim ediyorum. Bu şekilde, o sürümle birlikte dağıtılan C dosyasının tarihsel bir kaydına sahibim.


Teşekkürler, açtığım bir Pyrex projesi için tam da ihtiyacım olan şey bu! MANIFEST.in beni bir saniyeliğine tetikledi, ama sadece o satıra ihtiyacım vardı. C dosyasını ilgi dışı bir şekilde kaynak kontrolüne ekliyorum, ancak bunun gereksiz olduğunu görüyorum.
chmullig

Cevabımı, C dosyasının nasıl gövde / varsayılan değil, ancak bir sürüm dalına eklendiğini açıklamak için düzenledim.
Craig McQueen

1
@CraigMcQueen harika cevap için teşekkürler, bana çok yardımcı oldu! Yine de merak ediyorum, Cython mümkün olduğunda kullanmak istenen bir davranış mı? Bana öyle geliyor ki, kullanıcı açıkça Cython'u kullanmak istemediği sürece, önceden oluşturulmuş c dosyalarını varsayılan olarak kullanmak daha iyi olur, bu durumda ortam değişkenini veya başka bir şeyi ayarlayabilir. Bu, kurulumu daha kararlı / sağlam hale getirir, çünkü kullanıcı, Cython'un hangi sürümünü yüklediğine bağlı olarak farklı sonuçlar alabilir - yüklediğinin ve paketin yapımını etkilediğinin farkında bile olmayabilir.
Martinsos

20

Craig McQueen'in cevabına ek olarak: sdistCython'un bir kaynak dağıtımı oluşturmadan önce kaynak dosyalarınızı otomatik olarak derlemesi için komutun nasıl geçersiz kılınacağını öğrenmek için aşağıya bakın .

Bu şekilde, eski Ckaynakları yanlışlıkla dağıtma riskini almazsınız . Dağıtım süreci üzerinde sınırlı kontrole sahip olduğunuz durumlarda da yardımcı olur, örneğin sürekli entegrasyondan otomatik olarak dağıtımlar oluştururken vb.

from distutils.command.sdist import sdist as _sdist

...

class sdist(_sdist):
    def run(self):
        # Make sure the compiled Cython files in the distribution are up-to-date
        from Cython.Build import cythonize
        cythonize(['cython/mycythonmodule.pyx'])
        _sdist.run(self)
cmdclass['sdist'] = sdist

19

http://docs.cython.org/en/latest/src/userguide/source_files_and_compilation.html#distributing-cython-modules

Oluşturulan .c dosyalarını ve Cython kaynaklarınızı dağıtmanız şiddetle tavsiye edilir, böylece kullanıcılar Cython'a ihtiyaç duymadan modülünüzü kurabilirler.

Ayrıca, dağıttığınız sürümde Cython derlemesinin varsayılan olarak etkinleştirilmemesi önerilir. Kullanıcı Cython'u kurmuş olsa bile, muhtemelen sadece modülünüzü kurmak için kullanmak istemez. Ayrıca, sahip olduğu sürüm kullandığınız sürümle aynı olmayabilir ve kaynaklarınızı doğru bir şekilde derleyemeyebilir.

Bu basitçe, birlikte gönderdiğiniz setup.py dosyasının, oluşturulan .c dosyalarında normal bir distutils dosyası olacağı anlamına gelir, bunun yerine sahip olacağımız temel örnek için:

from distutils.core import setup
from distutils.extension import Extension
 
setup(
    ext_modules = [Extension("example", ["example.c"])]
)

7

En kolayı her ikisini de dahil etmek ama sadece c-dosyasını kullanmaktır. .Pyx dosyasını eklemek güzeldir, ancak yine de .c dosyasına sahip olduğunuzda buna gerek yoktur. .Pyx'i yeniden derlemek isteyen kişiler Pyrex'i kurabilir ve bunu manuel olarak yapabilir.

Aksi takdirde, önce C dosyasını oluşturan dağıtımlar için özel bir build_ext komutuna sahip olmanız gerekir. Cython zaten bir tane içeriyor. http://docs.cython.org/src/userguide/source_files_and_compilation.html

Bu belgelerin yapmadığı şey, bunun nasıl koşullu yapılacağını söylemektir, ancak

try:
     from Cython.distutils import build_ext
except ImportError:
     from distutils.command import build_ext

Halletmeli.


1
Cevabınız için teşekkürler. Bu mantıklı, ancak Cython kurulduğunda setup.pydoğrudan .pyxdosyadan oluşturulup oluşturulamayacağını tercih etsem de. Cevabım bunu da uyguladı.
Craig McQueen

Pekala, cevabımın tüm noktası bu. Tam bir setup.py değildi.
Lennart Regebro

4

(Cython) ile oluşturulan .c dosyalarının dahil edilmesi oldukça tuhaftır. Özellikle bunu git'e dahil ettiğimizde. Setuptools_cython kullanmayı tercih ederim . Cython mevcut olmadığında, yerleşik Cython ortamına sahip bir yumurta oluşturacak ve ardından yumurtayı kullanarak kodunuzu oluşturacaktır.

Olası bir örnek: https://github.com/douban/greenify/blob/master/setup.py


Güncelleme (2017/01/05):

O zamandan beri setuptools 18.0kullanmaya gerek yok setuptools_cython. İşte Cython projesini sıfırdan inşa etmek için bir örnek setuptools_cython.


bu, kurulum_başvurularında belirtmiş olmanıza rağmen Cython'un yüklenmemesi sorununu çözüyor mu?
Kamil Sindi

'setuptools>=18.0'yöntemi oluşturmak yerine setup_requires'e koymak da mümkün değil is_installedmi?
Kamil Sindi

1
@capitalistpug Önce emin olmak gerekir setuptools>=18.0o zaman sadece koymak gerekir, kurulduktan 'Cython >= 0.18'içinde setup_requiresve Cython ilerleme yükleme sırasında yüklenecektir. Ancak kurulum araçlarını <18.0 kullanıyorsanız, setup_requires içinde belirli bir cython bile kullansanız, yüklenmeyecektir, bu durumda kullanmayı düşünmelisiniz setuptools_cython.
McKelvin

Teşekkürler @McKelvin, bu harika bir çözüm gibi görünüyor! Bunun yanında kaynak dosyaları önceden şifrelemek gibi diğer yaklaşımı kullanmamız için herhangi bir neden var mı? Yaklaşımınızı denedim ve kurulum sırasında biraz yavaş görünüyor (yüklenmesi bir dakika sürüyor ancak bir saniyede kuruluyor).
Martinsos

1
@Martinsos pip install wheel. O halde neden 1 olmalıdır. Lütfen önce tekerleği takın ve tekrar deneyin.
McKelvin

2

Bu, yazdığım bir kurulum betiğidir ve yapı içine iç içe dizinler eklemeyi kolaylaştırır. Bir paketin içindeki klasörden çalıştırılması gerekiyor.

Bunun gibi bir yapı verin:

__init__.py
setup.py
test.py
subdir/
      __init__.py
      anothertest.py

setup.py

from setuptools import setup, Extension
from Cython.Distutils import build_ext
# from os import path
ext_names = (
    'test',
    'subdir.anothertest',       
) 

cmdclass = {'build_ext': build_ext}
# for modules in main dir      
ext_modules = [
    Extension(
        ext,
        [ext + ".py"],            
    ) 
    for ext in ext_names if ext.find('.') < 0] 
# for modules in subdir ONLY ONE LEVEL DOWN!! 
# modify it if you need more !!!
ext_modules += [
    Extension(
        ext,
        ["/".join(ext.split('.')) + ".py"],     
    )
    for ext in ext_names if ext.find('.') > 0]

setup(
    name='name',
    ext_modules=ext_modules,
    cmdclass=cmdclass,
    packages=["base", "base.subdir"],
)
#  Build --------------------------
#  python setup.py build_ext --inplace

Mutlu derleme;)


2

Bulduğum basit hack:

from distutils.core import setup

try:
    from Cython.Build import cythonize
except ImportError:
    from pip import pip

    pip.main(['install', 'cython'])

    from Cython.Build import cythonize


setup(…)

İçe aktarılamıyorsa Cython'u kurun. Muhtemelen bu kodu paylaşmamalıyım, ancak kendi bağımlılıklarım için yeterince iyi.


2

Diğer tüm cevaplar ya güvenir

  • distutils
  • 'den içe aktarma, Cython.Buildcython yoluyla istemekle setup_requiresonu içe aktarmak arasında bir tavuk ve yumurta sorunu yaratır .

Bunun yerine modern bir çözüm, kurulum araçlarını kullanmaktır, bu yanıta bakın (Cython uzantılarının otomatik olarak işlenmesi için kurulum araçları 18.0 gerekir, yani, zaten uzun yıllardır mevcuttur). setup.pyGereksinimleri ele alan modern bir standart , bir giriş noktası ve bir cython modülü şöyle görünebilir:

from setuptools import setup, Extension

with open('requirements.txt') as f:
    requirements = f.read().splitlines()

setup(
    name='MyPackage',
    install_requires=requirements,
    setup_requires=[
        'setuptools>=18.0',  # automatically handles Cython extensions
        'cython>=0.28.4',
    ],
    entry_points={
        'console_scripts': [
            'mymain = mypackage.main:main',
        ],
    },
    ext_modules=[
        Extension(
            'mypackage.my_cython_module',
            sources=['mypackage/my_cython_module.pyx'],
        ),
    ],
)

Cython.BuildKurulum zamanında öğesinden içe aktarmak benim için ImportError'a neden oluyor. Pyx'i derlemek için kurulum araçlarına sahip olmak, bunu yapmanın en iyi yoludur.
Carson Ip

1

Sınırlı özellikli dağıtımlar yerine yalnızca kurulum araçlarını kullanarak bulduğum en kolay yol

from setuptools import setup
from setuptools.extension import Extension
try:
    from Cython.Build import cythonize
except ImportError:
    use_cython = False
else:
    use_cython = True

ext_modules = []
if use_cython:
    ext_modules += cythonize('package/cython_module.pyx')
else:
    ext_modules += [Extension('package.cython_module',
                              ['package/cython_modules.c'])]

setup(name='package_name', ext_modules=ext_modules)

Aslında, setuptools ile açıkça dene / yakalanmış içe aktarmaya gerek yoktur, cevabıma Cython.Buildbakın.
bluenote10

0

Özel bir build_extkomut sağlayarak bunu yapmanın oldukça iyi bir yolunu bulduğumu düşünüyorum . Fikir şudur:

  1. İşlevin gövdesini geçersiz kılarak finalize_options()ve yaparak numpy başlıklarını ekliyorum import numpy, bu da numpy'yi yüklemeden önce mevcut olmama sorununu güzel bir şekilde ortadan kaldırıyor setup().

  2. check_extensions_list()Cython sistemde mevcutsa , komutun yöntemine bağlanır ve tüm güncel olmayan cython modüllerini cythonize ederek daha sonra build_extension() yöntem tarafından işlenebilecek C uzantılarıyla değiştirir . İşlevselliğin sadece ikinci kısmını modülümüzde de sağlıyoruz: bu, eğer cython mevcut değilse, ancak mevcut bir C uzantımız varsa, yine de çalışıyor ve bu da kaynak dağıtımları yapmanıza izin veriyor demektir.

İşte kod:

import re, sys, os.path
from distutils import dep_util, log
from setuptools.command.build_ext import build_ext

try:
    import Cython.Build
    HAVE_CYTHON = True
except ImportError:
    HAVE_CYTHON = False

class BuildExtWithNumpy(build_ext):
    def check_cython(self, ext):
        c_sources = []
        for fname in ext.sources:
            cname, matches = re.subn(r"(?i)\.pyx$", ".c", fname, 1)
            c_sources.append(cname)
            if matches and dep_util.newer(fname, cname):
                if HAVE_CYTHON:
                    return ext
                raise RuntimeError("Cython and C module unavailable")
        ext.sources = c_sources
        return ext

    def check_extensions_list(self, extensions):
        extensions = [self.check_cython(ext) for ext in extensions]
        return build_ext.check_extensions_list(self, extensions)

    def finalize_options(self):
        import numpy as np
        build_ext.finalize_options(self)
        self.include_dirs.append(np.get_include())

Bu, birinin, setup()ithalatlar ve cython'un mevcut olup olmadığı konusunda endişelenmeden argümanları yazmasına izin verir :

setup(
    # ...
    ext_modules=[Extension("_my_fast_thing", ["src/_my_fast_thing.pyx"])],
    setup_requires=['numpy'],
    cmdclass={'build_ext': BuildExtWithNumpy}
    )
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.