Hur man skriver egna pythonpaket

Översikt

Python är ett underbart programmeringsspråk och mycket mer. En av dess svagaste punkter är förpackning. Detta är ett välkänt faktum i samhället. Installera, importera, använda och skapa paket har förbättrats under årens lopp, men det är fortfarande inte i nivå med nyare språk som Go and Rust som kan lära sig mycket av Pythons kamp och andra mer mogna språk. 

I den här handledningen lär du dig allt du behöver veta för att bygga och dela egna paket. För allmän bakgrund på Python-paket, läs du hur du använder Python-paket.

Förpackning av ett projekt

Förpackning av ett projekt är det sätt på vilket du tar en förhoppningsvis sammanhängande uppsättning Python-moduler och eventuellt andra filer och lägger dem i en struktur som enkelt kan användas. Det finns olika saker du behöver tänka på, till exempel beroende på andra paket, intern struktur (delpaket), versionering, målgrupp och paketform (käll och / eller binär).

Exempel

Låt oss börja med ett snabbt exempel. Conman-paketet är ett paket för hantering av konfiguration. Den stöder flera filformat samt distribuerad konfiguration med hjälp av etcd.

Ett paketets innehåll lagras vanligtvis i en enda katalog (även om det är vanligt att dela delpaket i flera kataloger) och ibland, som i det här fallet, i sitt eget git-repository. 

Rots katalogen innehåller olika konfigurationsfiler (setup.py är obligatorisk och viktigaste) och själva paketkoden finns i en underkatalog vars namn är paketets namn och helst en testkatalog. Här är hur det ser ut som "conman":

> träd. ├── LISENS ├── MANIFEST.in ├── README.md ├── conman │ ├── __init__.py │ ├── __pycache__ │ ├── conman_base.py │ ├── conman_etcd.py │ └── conman_file.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── test-requirements.txt ├── tester │ ├── __pycache__ │ ├── conman_etcd_test.py │ ├── conman_file_test .py │ └─ - etcd_test_util.py └── tox.ini

Låt oss ta en titt på setup.py fil. Det importerar två funktioner från setuptools-paketet: inrätta() och find_packages (). Då kallas det inrätta() funktion och användningsområden find_packages () för en av parametrarna.

från setuptools import setup, hitta_packages setup (namn = 'conman', version = "0.3", url = "https://github.com/the-gigi/conman", licens = "MIT", författare = "Gigi Sayfan" , author_email = "[email protected]", description = "Hantera konfigurationsfiler", paket = find_packages (exclude = ['tests']), long_description = open ('README.md') .läs (), zip_safe = Falsk, setup_requires = ['nose> = 1,0'], test_suite = "nose.collector") 

Detta är ganska normalt. Medan setup.py filen är en vanlig Python-fil och du kan göra vad du vill ha i det, det primära jobbet att det ska ringas inrätta() fungera med lämpliga parametrar eftersom det kommer att åberopas av olika verktyg på ett vanligt sätt när du installerar ditt paket. Jag går över detaljerna i nästa avsnitt.

Konfigurationsfilerna

Dessutom setup.py, Det finns några andra valfria konfigurationsfiler som kan dyka upp här och tjäna olika syften.

Setup.py

De inrätta() funktionen tar ett stort antal namngivna argument för att styra många aspekter av paketinstallationen samt att köra olika kommandon. Många argument anger metadata som används för sökning och filtrering när du laddar upp ditt paket till ett förråd.

  • namn: ditt paketets namn (och hur det kommer att listas på PYPI)
  • version: detta är avgörande för att upprätthålla korrekt beroendehantering
  • URL: URL för ditt paket, vanligtvis GitHub eller kanske readthedocs URL
  • paket: lista över delpaket som måste ingå find_packages () hjälper här
  • setup_requires: här anger du beroenden
  • test_suite: vilket verktyg som ska köras vid testtiden

De lång beskrivning ställs här till innehållet i README.md fil, vilket är en bra praxis att ha en enda sanningskälla.

Setup.cfg

Setup.py-filen serverar också ett kommandoradsgränssnitt för att köra olika kommandon. Till exempel, för att köra enhetstesterna kan du skriva: python setup.py test

running test running egg_info skrivning conman.egg-info / PKG-INFO skrivning av toppnivå namn till conman.egg-info / top_level.txt skriver dependency_links till conman.egg-info / dependency_links.txt läs manifestfilen 'conman.egg-info /SOURCES.txt "läs manifest mall" MANIFEST.in "skriv manifest fil" conman.egg-info / SOURCES.txt "kör build_ext test_add_bad_key (conman_etcd_test.ConManEtcdTest) ... ok test_add_good_key (conman_etcd_test.ConManEtcdTest) ... ok test_dictionary_access (conman_etcd_test.ConManEtcdTest ) ... ok test_initialization (conman_etcd_test.ConManEtcdTest) ... ok test_refresh (conman_etcd_test.ConManEtcdTest) ... ok test_add_config_file_from_env_var (conman_file_test.ConmanFileTest) ... ok test_add_config_file_simple_guess_file_type (conman_file_test.ConmanFileTest) ... ok test_add_config_file_simple_unknown_wrong_file_type (conman_file_test.ConmanFileTest) ... ok test_add_config_file_simple_with_file_type (conman_file_test.ConmanFileTest) ... ok test_add_config_file_simple_w rong_file_type (conman_file_test.ConmanFileTest) ... ok test_add_config_file_with_base_dir (conman_file_test.ConmanFileTest) ... ok test_dictionary_access (conman_file_test.ConmanFileTest) ... ok test_guess_file_type (conman_file_test.ConmanFileTest) ... ok test_init_no_files (conman_file_test.ConmanFileTest) ... ok test_init_some_bad_files (conman_file_test.ConmanFileTest) ... ok test_init_some_good_files ( conman_file_test.ConmanFileTest) ... ok -------------------------------------------- -------------------------- Ran 16 test i 0.160s OK 

Setup.cfg är en ini-formatfil som kan innehålla alternativinställningar för kommandon du skickar till setup.py. Här innehåller setup.cfg några alternativ för nosetests (vår testrunner):

[nosetests] verbose = 1 nocapture = 1 

MANIFEST.in

Den här filen innehåller filer som inte ingår i den interna paketkatalogen, men du vill fortfarande inkludera. Det är typiskt de readme fil, licensfilen och liknande. En viktig fil är requirements.txt. Denna fil används av pip för att installera andra nödvändiga paket.

Här är conman s MANIFEST.in fil:

Inkludera LICENSE inkluderar README.md include requirements.txt

beroenden

Du kan ange beroenden både i install_requires avsnitt av setup.py och i en requirements.txt fil. Pip installerar automatiskt beroenden från install_requires, men inte från requirements.txt fil. För att installera dessa krav måste du explicit ange det när du kör pip: pip installera -r requirements.txt.

De install_requires alternativet är utformat för att ange minimala och mer abstrakta krav på huvudversionsnivån. Requirements.txt-filen är för mer konkreta krav, ofta med nedlagda mindre versioner.

Här är kravet på conman. Du kan se att alla versioner är fastsatta, vilket betyder att det kan påverkas negativt om en av dessa paket uppgraderar och introducerar en förändring som bryter conman.

PyYAML == 3,11 python-etcd == 0.4.3 urllib3 == 1,7 pyOpenSSL == 0,15,1 psutil == 4,0,0 sex == 1,7,3

Pinning ger dig förutsägbarhet och sinnesfrid. Detta är särskilt viktigt om många installerar ditt paket vid olika tidpunkter. Utan stiftning kommer varje person att få en annan blandning av beroendeversioner baserat på när de installerade den. Nackdelen med pinning är att om du inte håller med din utveckling av beroenden kan du fastna på en gammal, dåligt fungerande och till och med sårbar version av något beroende.

Jag skrev ursprungligen conman 2014 och betalade inte mycket uppmärksamhet åt den. Nu, för denna handledning uppgraderade jag allt och det fanns några större förbättringar över hela linjen för nästan alla beroenden.

distributioner

Du kan skapa en källfördelning eller en binär distribution. Jag täcker båda.

Källfördelning

Du skapar en källdistribution med kommandot: python setup.py sdist. Här är utgången för conman:

> python setup.py sdist körning sdist körning ägg_info skrivning conman.egg-info / PKG-INFO skriver överordnade namn till conman.egg-info / top_level.txt skrivning dependency_links till conman.egg-info / dependency_links.txt läs manifestfil 'conman.egg-info / SOURCES.txt' läs manifest mall 'MANIFEST.in' skriv manifestfilen 'conman.egg-info / SOURCES.txt' varning: sdist: standardfilen hittades inte: borde ha en av README, README. rst, README.txt körkontroll skapa conman-0.3 skapa conman-0.3 / conman skapa conman-0.3 / conman.egg-info gör svåra länkar i conman-0.3 ... hård länk LICENSE -> conman-0.3 hård länkning MANIFEST.in -> conman-0.3 hård länk README.md -> conman-0.3 hård länk krav.txt -> conman-0.3 hård länk setup.cfg -> conman-0.3 hård länk setup.py -> conman-0.3 hård länk conman / __ init__.py -> conman-0.3 / conman hard linking conman / conman_base.py -> conman-0.3 / conman hårdkoppling conman / conman_etcd.py -> conman-0.3 / conman hard linking conman / conman_fil e.py -> conman-0.3 / conman hard linking conman.egg-info / PKG-INFO -> conman-0.3 / conman.egg-info hårt länkande conman.egg-info / SOURCES.txt -> conman-0.3 / conman .egg-info hårt länkande conman.egg-info / dependency_links.txt -> conman-0.3 / conman.egg-info hårt länkande conman.egg-info / inte-zip-safe -> conman-0.3 / conman.egg-info hårdkoppling conman.egg-info / top_level.txt -> conman-0.3 / conman.egg-info kopiering setup.cfg -> conman-0.3 Skriva conman-0.3 / setup.cfg skapa dist Skapa tarar arkiv ta bort 'conman-0.3' (och allt under det) 

Som du kan se fick jag en varning om att sakna en README-fil med ett av standardprefixen eftersom jag gillar Markdown så att jag har en "README.md" istället. Annat än det var alla paketkällfiler inkluderade och de ytterligare filerna. Sedan skapades en massa metadata i conman.egg-info katalogen. Slutligen kallas ett komprimerat tjärarkiv conman-0.3.tar.gz skapas och läggs i en dist underkatalog.

Installera det här paketet kräver ett byggsteg (även om det är ren Python). Du kan installera den med hjälp av pip normalt, bara genom att passera vägen till paketet. Till exempel:

pip install dist / conman-0.3.tar.gz Bearbetning ./dist/conman-0.3.tar.gz Installera samlade paket: conman Running setup.py installera för conman ... done Installerat framgångsrikt conman-0.3

Conman har installerats i platspaket och kan importeras som alla andra paket:

importera conman conman .__ file__ '/Users/gigi/.virtualenvs/conman/lib/python2.7/site-packages/conman/__init__.pyc'

Wheels

Hjul är ett relativt nytt sätt att paketera Python-kod och eventuellt C-förlängningar. De ersätter äggformatet. Det finns flera typer av hjul: rena Python-hjul, plattformshjul och universella hjul. De rena Pythonhjulen är paket som conman som inte har någon C-förlängningskod. 

Platformhjulen har C-förlängningskod. De universella hjulen är rena Python-hjul som är kompatibla med både Python 2 och Python 3 med samma kodbas (de behöver inte ens 2to3). Om du har ett rent Python-paket och vill att ditt paket ska stödja både Python 2 och Python 3 (blir allt viktigare) kan du bygga en enda universell byggning istället för ett hjul för Python 2 och ett hjul för Python 3. 

Om ditt paket har C-förlängningskod måste du bygga ett plattformshjul för varje plattform. Den stora fördelen med hjul speciellt för paket med C-förlängningar är att det inte finns något behov av att ha kompilator och stödjande bibliotek tillgängliga på målmaskinen. Hjulet innehåller redan ett inbyggt paket. Så du vet att det inte kommer att misslyckas med att bygga och det är mycket snabbare att installera eftersom det är bokstavligen bara en kopia. Människor som använder vetenskapliga bibliotek som Numpy och Pandas kan verkligen uppskatta detta, eftersom installationen av sådana paket brukade ta lång tid och kan ha misslyckats om något bibliotek saknades eller kompilatorn inte konfigurerades korrekt.

Kommandot att bygga rena eller plattformshjul är: python setup.py bdist_wheel.

Setuptools-motorn som tillhandahåller inrätta() funktion - detekterar automatiskt om ett rent eller plattformshjul behövs.

kör bdist_wheel kör bygga springa build_py skapa bygga skapa bygga / lib skapa bygga / lib / conman kopiera conman / __ init__.py -> bygga / lib / conman kopiera conman / conman_base.py -> bygga / lib / conman kopiera conman / conman_etcd.py -> bygga / lib / conman kopiera conman / conman_file.py -> bygga / lib / conman installera för att bygga / bdist.macosx-10.9-x86_64 / hjulkörning installera kör install_lib skapa build / bdist.macosx-10.9-x86_64 skapa build / bdist.macosx-10.9-x86_64 / hjulbyggande bygg / bdist.macosx-10.9-x86_64 / hjul / conman kopiering bygg / lib / conman / __ init__.py -> bygg / bdist.macosx-10.9-x86_64 / hjul / conman kopiering bygga /lib/conman/conman_base.py -> build / bdist.macosx-10.9-x86_64 / hjul / conman kopiering build / lib / conman / conman_etcd.py -> bygg / bdist.macosx-10.9-x86_64 / hjul / conman kopiering bygga /lib/conman/conman_file.py -> build / bdist.macosx-10.9-x86_64 / hjul / conman kör install_egg_info kör egg_info skapa conman.egg-info skrivning conman.egg-info / PKG-INFO skrivning på toppnivå nam es till conman.egg-info / top_level.txt skriver dependency_links till conman.egg-info / dependency_links.txt skriv manifestfilen 'conman.egg-info / SOURCES.txt' läs manifestfilen 'conman.egg-info / SOURCES.txt 'läs manifest mall' MANIFEST.in 'skriv manifestfilen' conman.egg-info / SOURCES.txt 'Kopiera conman.egg-info för att bygga / bdist.macosx-10.9-x86_64 / wheel / conman-0.3-py2.7. ägg info kör install_scripts skapa build / bdist.macosx-10.9-x86_64 / wheel / conman-0.3.dist-info / WHEEL

Kontrollerar dist katalog, kan du se att ett rent pythonhjul skapades:

ls -la dist dist / total 32 -rw-r-r- 1 gigi-personal 5.5K feb 29 07:57 conman-0.3-py2-none-any.whl -rw-r - r-- 1 gigi-personal 4.4K feb 28 23:33 conman-0.3.tar.gz

Namnet "conman-0.3-py2-none-any.whl" har flera komponenter: paketnamn, paketversion, Python-version, plattformsversion och slutligen "whl" -tillägget.

För att bygga universella paket lägger du till --universell, som i python setup.py bdist_wheel --universal.

Det resulterande hjulet kallas "conman-0.3-py2.py3-none-any.whl".

Observera att det är ditt ansvar att säkerställa att din kod verkligen fungerar under både Python 2 och Python 3 om du skapar ett universalpaket.

Slutsats

Att skriva egna Python-paket kräver att du hanterar många verktyg, specificerar mycket metadata och tänker noga på dina beroende och målgrupp. Men belöningen är stor. 

Om du skriver användbar kod och paketerar den ordentligt, kommer de att kunna installera det enkelt och dra nytta av det.