Kardeş paket ithalatı


200

Kardeş ithalatı ve hatta paket belgeleri ile ilgili soruları okumayı denedim , ancak henüz bir cevap bulamadım.

Aşağıdaki yapı ile:

├── LICENSE.md
├── README.md
├── api
   ├── __init__.py
   ├── api.py
   └── api_key.py
├── examples
   ├── __init__.py
   ├── example_one.py
   └── example_two.py
└── tests
   ├── __init__.py
   └── test_one.py

examplesVe testsdizinlerindeki komut dosyaları apimodülden nasıl içe aktarılabilir ve komut satırından çalıştırılabilir?

Ayrıca, sys.path.inserther dosya için çirkin kesmek önlemek istiyorum . Elbette bu Python'da yapılabilir, değil mi?


7
Tüm sys.pathkesmek geçmiş atlama ve şimdiye kadar (7 yıl sonra!) Gönderilen tek gerçek çözümü okumak öneririz .
Aran-Fey

1
Bu arada, yine de iyi bir çözüm için yer var: Yürütülebilir kodu kütüphane kodundan ayırmak; çoğu zaman bir paket içindeki bir komut dosyasının başlaması için çalıştırılabilir olmaması gerekir .
Aran-Fey

Hem soru hem de cevaplar çok faydalı. Sadece merak ediyorum, nasıl olur da "Kabul Edildi Cevap" bu durumda ödül verilenle aynı değil?
Indominus

@ Aran-Fey Bu göreceli içe aktarma hatası Soru ve Yanıtlarında önemsiz bir hatırlatma. Bunca zamandır bir hack arıyordum, ama derinden sorundan kurtulmak için basit bir yol olduğunu biliyordum. Buradaki herkes için bir çözüm olduğunu söylememekle birlikte, birçok kişi için olabileceği için iyi bir hatırlatmadır.
colorlace

Yanıtlar:


70

Yedi yıl sonra

Aşağıdaki yanıtı yazdığım için, değişiklik sys.pathyapmak hala özel komut dosyaları için iyi çalışan hızlı ve kirli bir hile, ancak birkaç iyileştirme yapıldı

  • Paketi kurmak (bir virtualenv veya değil) size ne istediğinizi verecektir, ancak ben doğrudan setuptools (ve kullanaraksetup.cfg meta verileri depolamak için
  • -mBayrağı kullanma ve bir paket olarak çalışmak da işe yarar (ancak çalışma dizininizi yüklenebilir bir pakete dönüştürmek istiyorsanız biraz garipleşecektir).
  • Testler için, özellikle, pytest bu durumda api paketini bulabilir ve sys.pathsizin için kesmekle ilgilenir

Bu gerçekten ne yapmak istediğinize bağlı. Ancak sizin durumunuzda, amacınız bir noktada uygun bir paket yapmak,pip -e , henüz mükemmel olmasa bile muhtemelen en iyi bahistir.

Eski cevap

Başka bir yerde daha önce de belirtildiği gibi, korkunç gerçek, kardeş modüllerinden veya bir __main__modülden ebeveyn paketinden ithalatlara izin vermek için çirkin hackler yapmanız gerektiğidir . Konu PEP 366'da detaylandırılmıştır . PEP 3122 , ithalatı daha rasyonel bir şekilde ele almaya çalıştı, ancak Guido,

Tek kullanımlık durum, bir modülün dizininde yaşayan ve her zaman antipattern olarak gördüğüm komut dosyaları çalıştırıyor gibi görünüyor.

( burada )

Yine de, bu kalıbı düzenli olarak

# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __name__ == "__main__" and __package__ is None:
    from sys import path
    from os.path import dirname as dir

    path.append(dir(path[0]))
    __package__ = "examples"

import api

İşte path[0]çalışan komut dosyanızın üst klasörü vedir(path[0]) üst düzey klasörünüz.

Bununla birlikte, göreceli ithalatları hala kullanamadım, ancak üst seviyeden (örneğin apiüst klasöründe) mutlak ithalatlara izin veriyor .


3
Eğer yok zorunda eğer kullanarak bir proje dizinden çalıştırmak -mpaketi yüklerseniz form veya (pip ve kolay yapmak Virtualenv)
jfs

2
Pytest sizin için api paketini nasıl bulur? Eğlenceli, bu iş parçacığı buldum çünkü bu sorunu özellikle pytest ve kardeş paketi içe aktarma ile çalışıyorum.
JuniorIncanter

1
İki sorum var, lütfen. 1. Deseniniz bensiz çalışıyor gibi görünüyor __package__ = "examples". Neden onu kullanıyorsun? 2. Ne durumda __name__ == "__main__"ama __package__değil None?
actual_panda

@actual_panda Setting, iirc çalışmak __packages__gibi mutlak bir yol istiyorsanız examples.api(ancak son yaptığımdan bu yana uzun zaman geçti) ve paketin None olup olmadığını kontrol etmek, garip durumlar ve futureproofing için çoğunlukla başarısız oldu.
Evpok

Tanrım, sadece diğer diller Python'da olduğu kadar aynı işlemi yaparsa. Herkesin bu dili neden sevdiğini anlıyorum. Btw, belgeler de mükemmel. Yapılandırılmamış metinden dönüş türlerini çıkarmayı seviyorum, Javadoc ve phpdoc'dan hoş bir değişiklik. ffs ....
matt

168

Sys.path hack'lerinden bıktınız mı?

Bir sürü var sys.path.append hack var, ama problemi çözmenin alternatif bir yolunu buldum.

özet

  • Kodu bir klasöre sarın (ör. packaged_stuff)
  • Setuptools.setup () yönteminisetup.py kullandığınız create komut dosyasını kullanın .
  • Pip ile paketi düzenlenebilir durumda yükleyin pip install -e <myproject_folder>
  • Kullanarak içe aktar from packaged_stuff.modulename import function_name

Kurmak

Başlangıç ​​noktası, sağladığınız ve adlı bir klasöre sarılmış dosya yapısıdır myproject.

.
└── myproject
    ├── api
       ├── api_key.py
       ├── api.py
       └── __init__.py
    ├── examples
       ├── example_one.py
       ├── example_two.py
       └── __init__.py
    ├── LICENCE.md
    ├── README.md
    └── tests
        ├── __init__.py
        └── test_one.py

.Kök klasörü çağıracağım ve benim örneğimde bu dosyada bulunur C:\tmp\test_imports\.

api.py

Test örneği olarak aşağıdakileri kullanalım ./api/api.py

def function_from_api():
    return 'I am the return value from api.api!'

test_one.py

from api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

Test_one komutunu çalıştırmayı deneyin:

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\myproject\tests\test_one.py", line 1, in <module>
    from api.api import function_from_api
ModuleNotFoundError: No module named 'api'

Ayrıca göreli ithalat denemek işe yaramaz:

Kullanılması from ..api.api import function_from_apihalinde neden olacaktır

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\tests\test_one.py", line 1, in <module>
    from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package

adımlar

  1. Kök seviyesi dizinine bir setup.py dosyası oluşturun

İçindekiler setup.py*

from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())
  1. Sanal ortam kullanma

Sanal ortamlara aşina iseniz, birini etkinleştirin ve sonraki adıma geçin. Sanal ortamların kullanılması kesinlikle gerekli değildir , ancak uzun vadede size gerçekten yardımcı olacaktır (1'den fazla projeniz devam ederken ..). En temel adımlar (kök klasörde çalıştır)

  • Sanal env oluştur
    • python -m venv venv
  • Sanal env'yi etkinleştir
    • source ./venv/bin/activate(Linux, macOS) veya ./venv/Scripts/activate(Win)

Bu konuda daha fazla bilgi edinmek için, sadece "python sanal env öğretici" veya benzeri Google. Muhtemelen oluşturma, etkinleştirme ve devre dışı bırakma dışında başka komutlara ihtiyacınız yoktur.

Bir sanal ortam oluşturup etkinleştirdikten sonra, konsolunuz parantez içinde sanal ortamın adını vermelidir

PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>

ve klasör ağacınız şöyle görünmelidir **

.
├── myproject
   ├── api
      ├── api_key.py
      ├── api.py
      └── __init__.py
   ├── examples
      ├── example_one.py
      ├── example_two.py
      └── __init__.py
   ├── LICENCE.md
   ├── README.md
   └── tests
       ├── __init__.py
       └── test_one.py
├── setup.py
└── venv
    ├── Include
    ├── Lib
    ├── pyvenv.cfg
    └── Scripts [87 entries exceeds filelimit, not opening dir]
  1. pip projenizi düzenlenebilir durumda yükleyin

myprojectKullanarak üst düzey paketinizi kurun pip. Hile, -eyükleme yaparken bayrağı kullanmaktır . Bu şekilde düzenlenebilir bir duruma yüklenir ve .py dosyalarında yapılan tüm düzenlemeler otomatik olarak yüklü pakete dahil edilir.

Kök dizinde çalıştırın

pip install -e . (noktaya dikkat edin, "geçerli dizin" anlamına gelir)

Ayrıca şunu kullanarak yüklendiğini görebilirsiniz: pip freeze

(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
  Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0
  1. myproject.İthalatınıza ekleyin

myproject.Yalnızca aksi takdirde çalışmayan ithalatlara eklemeniz gerekeceğini unutmayın. Olmadan çalıştı İthalat setup.py&pip install çalışacak hala iyi çalışıyor. Aşağıdaki örneğe bakın.


Çözümü test edin

Şimdi, çözümü api.pyyukarıda tanımlananları kullanarak test edelim vetest_one.py aşağıda tanımlananları .

test_one.py

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

testi çalıştırmak

(venv) PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
I am the return value from api.api!

* Daha ayrıntılı setup.py örnekleri için setuptools belgelerine bakın .

** Gerçekte, sanal ortamınızı sabit diskinizde herhangi bir yere koyabilirsiniz.


13
Detaylı yazı için teşekkürler. İşte benim sorunum. Söylediğin her şeyi yaparsam ve bir pip dondurma yaparsam -e git+https://username@bitbucket.org/folder/myproject.git@f65466656XXXXX#egg=myproject, nasıl çözüleceğine dair bir fikrim var mı?
Si Pzt

2
Göreli içe aktarma çözümü neden çalışmıyor? Sana inanıyorum, ama Python'un kıvrık sistemini anlamaya çalışıyorum.
Jared Nielsen

8
A ile ilgili sorunları olan var ModuleNotFoundErrormı? Bu adımları izleyen bir virtualenv içine 'MyProject' yükledim, ve bir yorumlanır oturumu girip çalıştırdığınızda import myprojectalıyorum ModuleNotFoundError: No module named 'myproject'? pip list installed | grep myprojecto olduğunu, dizin doğrudur, ve verison hem gösterileri pipve pythondoğru olduğu doğrulandı.
Temmuz

2
Hey @ np8, işe yarıyor, yanlışlıkla venv ve os'a yükledim :) pip listpaketleri pip freezegösterir, bayrak -e
Grzegorz Krug

3
Göreli ithalatın nasıl işleneceğini anlamaya çalışırken yaklaşık 2 saat geçirdim ve bu cevap nihayet mantıklı bir şey yapan cevaptı. 👍👍
Graham Lea

43

testsKlasördeki Python dosyalarının üstüne eklediğim başka bir alternatif :

# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))

1
+1 gerçekten basit ve mükemmel çalıştı. İthalat için üst sınıfı eklemeniz gerekir (ex api.api, örnekler.example_two) ama bu şekilde tercih ederim.
Evan Plaice

10
Yeni başlayanlar (kendim gibi) ..burada yürüttüğünüz dizine göreceli --- bu test / örnek dosyasını içeren dizine değil, bahsetmeye değer olduğunu düşünüyorum . Proje dizininden yürütüyorum ve ./bunun yerine gerekli . Umarım bu başka birine yardımcı olur.
Joshua Detwiler

@JoshDetwiler, evet kesinlikle. Farkında değildim. Teşekkürler.
doak

1
Bu kötü bir cevap. Yolu kesmek iyi bir uygulama değildir; Python dünyasında ne kadar kullanıldığını skandal ediyor. Bu sorunun ana noktalarından biri, bu tür bir saldırıdan kaçınırken ithalatın nasıl yapılabileceğini görmekti.
jtcotton63

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))@JoshuaDetwiler
vldbnc

31

sys.pathGerekli olmadıkça ve bu durumda gerekmediği sürece hacklemenize gerek yoktur. kullanın:

import api.api_key # in tests, examples

Proje dizinden çalıştırın: python -m tests.test_one.

Muhtemelen testsiçeriye (api'nin unittests ise) içeri girmeli apive python -m api.testtüm testleri (varsa varsayarak __main__.py) ya da bunun yerine python -m api.test.test_oneçalıştırmalısınız test_one.

Ayrıca kaldırmak olabilir __init__.pydan examples(bir Python paketi değildir) ve bir virtualenv örneklerini çalıştırmak api, örneğin yüklü pip install -e .INPLACE yükleyecek bir virtualenv apiUygun varsa paketi setup.py.


@Alex yanıtı, testlerin açıkça " api'nin unittests iseler " diyor paragraf dışında API testleri olduğunu varsaymaz .
jfs

ne yazık ki o zaman kök dir çalışan ile sıkışmış ve PyCharm hala güzel fonksiyonları için dosyayı bulamıyor
mhstnsc

@mhstnsc: doğru değil. python -m api.test.test_oneVirtualenv etkinleştirildiğinde her yerden çalışabilmelisiniz . PyCharm'ı testlerinizi çalıştıracak şekilde yapılandıramazsanız, yeni bir Yığın Taşması sorusu sormayı deneyin (bu konuda mevcut bir soru bulamazsanız).
jfs

@jfs Sanal env yolunu kaçırdım ama herhangi bir dizinden bu şeyleri çalıştırmak için shebang satırından daha fazla bir şey kullanmak istemiyorum. Bu PyCharm ile çalışmakla ilgili değil. PyCharm ile Devs de tamamlama ve herhangi bir çözüm ile çalışmasını sağlayamadı fonksiyonları üzerinden atlamak bilecekti.
mhstnsc

mhstnsc birçok durumda uygun bir shebang yeterlidir (virtualenv python ikili dosyaya işaret edin. İyi bir Python IDE sanal bir desteği desteklemelidir.
jfs

9

Kardeş / göreli bir ithalat kesmek olmadan ilgisiz projeler arasında kod paylaşmanın amaçlanan yolunu görmek için gerekli olan pitoloji anlayışım henüz yok. O güne kadar, bu benim çözümüm. Birinden examplesveya testsiçeri aktarmak ..\apiiçin şöyle görünür:

import sys.path
import os.path
# Import from sibling directory ..\api
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
import api.api
import api.api_key

Bu hala api üst dizinini verir ve sys.path.append (os.path.dirname (os.path.dirname (os.path.abspath ( dosya )) birleşimine ihtiyacınız olmaz. )
Camilo Sanchez

4

Kardeşler paket içe aktarmaları için [sys.path] [2] modülünün insert veya append yöntemini kullanabilirsiniz :

if __name__ == '__main__' and if __package__ is None:
    import sys
    from os import path
    sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
    import api

Komut dosyalarınızı aşağıdaki gibi başlatıyorsanız bu işe yarar:

python examples/example_one.py
python tests/test_one.py

Öte yandan, göreli içe aktarmayı da kullanabilirsiniz:

if __name__ == '__main__' and if __package__ is not None:
    import ..api.api

Bu durumda, komut dosyanızı '-m' argümanıyla başlatmanız gerekir (bu durumda '.py' uzantısını vermemeniz gerektiğini unutmayın ):

python -m packageName.examples.example_one
python -m packageName.tests.test_one

Tabii ki, iki yaklaşımı karıştırabilirsiniz, böylece komut dosyanız nasıl adlandırılırsa çalışsın:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        import api
    else:
        import ..api.api

__file__Küresel olmayan Click çerçevesini kullanıyordum, bu yüzden aşağıdakileri kullanmak zorunda kaldım: sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))Ama şimdi herhangi bir dizinde çalışıyor
GammaGames

3

TLDR

Bu yöntem, kurulum araçlarını, yol korsanlıklarını, ek komut satırı bağımsız değişkenlerini veya projenizin her bir dosyasında paketin en üst düzeyini belirtmeyi gerektirmez.

Sadece sizin olmak istediğiniz __main__her şeyin üst dizininde bir komut dosyası yapın ve oradan her şeyi çalıştırın. Daha fazla açıklama için okumaya devam edin.

açıklama

Bu, yeni bir yolu birlikte kesmek, ekstra komut satırı argümanları veya kardeşlerini tanımak için programlarınızın her birine kod eklemeden yapılabilir.

Daha önce de belirttiğim gibi bunun başarısız olmasının nedeni, çağrılan programların kendi __name__setlerine sahip olmasıdır __main__. Bu durumda, çağrılan komut dosyasının kendisini paketin en üst düzeyinde kabul eder ve kardeş dizinlerindeki komut dosyalarını tanımayı reddeder.

Ancak, dizinin üst düzey altındaki her şey yine de üst düzey altında HERHANGİ BİR ŞEY kabul edecektir . Bu , kardeş dizinlerindeki dosyaları birbirini tanımak / kullanmak için yapmanız gereken tek şey, bunları ana dizinlerindeki bir komut dosyasından çağırmaktır.

Kavram Kanıtı Aşağıdaki yapıya sahip bir rehberde:

.
|__Main.py
|
|__Siblings
   |
   |___sib1
   |   |
   |   |__call.py
   |
   |___sib2
       |
       |__callsib.py

Main.py aşağıdaki kodu içerir:

import sib1.call as call


def main():
    call.Call()


if __name__ == '__main__':
    main()

sib1 / call.py şunları içerir:

import sib2.callsib as callsib


def Call():
    callsib.CallSib()


if __name__ == '__main__':
    Call()

ve sib2 / callsib.py şunları içerir:

def CallSib():
    print("Got Called")

if __name__ == '__main__':
    CallSib()

Eğer bu örneği çoğaltmak Eğer bu çağrı göreceksiniz Main.py"denilen Got" sonuçlanacaktır tanımlandığı gibi basılıyor sib2/callsib.py olsa sib2/callsib.pyaracılığıyla çağırıldım sib1/call.py. Bununla birlikte, eğer doğrudan çağrı yapacaksa sib1/call.py(ithalatta uygun değişiklikler yaptıktan sonra) bir istisna atar. Üst dizinde komut dosyası tarafından çağrıldığında çalışsa da, paketin en üst düzeyinde olduğuna inanıyorsa çalışmaz.


2

Bunu nasıl ele aldığımı göstermek için örnek bir proje yaptım, bu da yukarıda belirtildiği gibi başka bir sys.path hack'idir. Aşağıdakilere dayanan Python Kardeş İthalat Örneği :

if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())

Çalışma dizininiz Python projesinin kökünde kaldığı sürece bu oldukça etkili görünüyor. Biri bunu gerçek bir üretim ortamında dağıtırsa, orada da çalışıp çalışmadığını duymak harika olurdu.


Bu yalnızca komut dosyasının üst dizininden çalışıyorsanız çalışır
Evpok

1

İçe aktarma ifadelerinin ilgili kodda nasıl yazıldığını görmeniz gerekir. Eğer examples/example_one.pykullanımlar aşağıdaki ithalat beyanı:

import api.api

... sonra projenin kök dizininin sistem yolunda olmasını bekler.

Bunu herhangi bir hack olmadan desteklemenin en kolay yolu (koyduğunuz gibi), üst düzey dizinden örnekleri aşağıdaki gibi çalıştırmaktır:

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py 

Python 2.7.1 ile ben izleyici kitlesi elde: $ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api. Ben de aynısını alıyorum import api.api.
zachwill

Cevabımı Güncelleme ... sen do ithalat yolu, etrafta hiçbir şekilde geçerli dizinin eklemek zorunda.
AJ.

1

Eclipse'de Pydev kullanan birinin buraya gelmesi durumunda: kardeşin üst yolunu (ve böylece çağıran modülün üst öğesini) Project-> Properties'i kullanarak ve sol kütüphaneler Pydev-PYTHONPATH altındaki Harici Kitaplıkları ayarlayarak harici bir kütüphane klasörü olarak ekleyebilirsiniz . Sonra kardeşinizden içe aktarabilirsiniz, örn from sibling import some_class.


-3

İlk olarak, modülün kendisi ile aynı ada sahip dosyalara sahip olmamalısınız. Diğer ithalatı bozabilir.

Bir dosyayı içe aktardığınızda, önce yorumlayıcı geçerli dizini kontrol eder ve ardından genel dizinleri arar.

İçeride examplesveya testsarayabilirsiniz:

from ..api import api

Python 2.7.1 ile aşağıdakileri Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
alıyorum

2
Oh, o zaman __init__.pyüst düzey dizine bir dosya eklemelisiniz . Aksi takdirde Python bunu bir modül olarak ele

8
Çalışmaz. Meselenin modül beri yani ana klasörün bir paket olmadığını değil __name__ise __main__yerine package.module, Python, böylece üst paketi göremiyorum .noktaları hiçbir şey.
Evpok
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.