En praktisk guide til brug af Setup.py

Data & AI-uddannelsesguide 2021

Download GoDataDriven-brochuren for at få en komplet oversigt over tilgængelige træningssessioner og læringsrejser for datateknikere, datavidenskab, dataanalytikere og analytik-oversættere.

Når du bruger python professionelt, kan det betale sig at opsætte dine projekter
på en konsekvent måde. Dette hjælper dine samarbejdspartnere til hurtigt at forstå
strukturen i et projekt og gør det lettere for dem at opsætte projektet
på deres maskine. Nøglen til opsætning af dit projekt er setup.py-filen.
I denne blog vil jeg gå i detaljer med denne fil.

Hvor vi starter

Her går jeg ud fra, at du allerede har en pakke, som du vil opsætte.
Dette behøver ikke at være en færdig pakke – ideelt set bør du oprette
setup.py længe før dit projekt er færdigt. Det kan endda være en tom pakke;
du skal blot sørge for, at pakkemappen eksisterer
og indeholder en fil med navnet init.py (som kan være tom).

Hvis du følger min kollega Henks struktur
for dit projekt, bør din udgangssituation se nogenlunde sådan ud:

example_project/├── exampleproject/ Python package with source code.│ ├── __init__.py Make the folder a package.│ └── example.py Example module.└── README.md README with info of the project.

Du kan have andre filer eller mapper i din struktur, f.eks.
mapper med navnet notebooks/, tests/ eller data/, men disse er ikke nødvendige.

Sagen om en setup.py

Når du har oprettet en pakke som denne, så er det sandsynligt, at du
vil bruge noget af koden andre steder. Du kan f.eks. ønske
at gøre dette i en notesbog:

from exampleproject.example import example_function

Dette ville fungere, hvis din nuværende arbejdskatalog er example_project/, men i
alle andre tilfælde vil python give dig output som f.eks:

ModuleNotFoundError: No module named 'exampleproject'

Du kunne fortælle python, hvor python skal lede efter pakken ved at indstille PYTHONPATH
omgivelsesvariablen eller tilføje stien til sys.path,
men det er langt fra ideelt: det ville kræve forskellige handlinger på forskellige
platforme, og den sti, du skal indstille, afhænger af placeringen af din kode.
En langt bedre måde er at installere din pakke ved hjælp af en setup.py og pip,
da pip er standardmåden at installere alle andre pakker på, og den er bundet
til at fungere ens på alle platforme.

Et minimalt eksempel

Så hvordan ser en setup.py-fil ud? Her er et minimalt eksempel0:

from setuptools import setup, find_packagessetup( name='example', version='0.1.0', packages=find_packages(include=))

Her angiver vi tre ting:

  • Navnet på pakken, som er det navn, som pip vil bruge til din pakke.
    Dette behøver ikke at være det samme som navnet på den mappe, pakken bor
    i, selvom det kan være forvirrende, hvis det ikke er det. Et eksempel på, hvor pakke
    navnet og mappen ikke passer sammen, er Scikit-Learn: du installerer den
    ved hjælp af pip install scikit-learn, mens du bruger den ved at importere fra sklearn.
  • Versionen af din pakke. Dette er den version pip vil rapportere, og bruges
    for eksempel når du udgiver din pakke på PyPI1.
  • Hvilke pakker du skal inkludere; i vores tilfælde er dette bare exampleproject/.
    Her lader vi setuptools regne dette ud
    automatisk2. Mens du i princippet kunne bruge find_packages()
    uden argumenter, kan dette potentielt resultere i, at uønskede pakker bliver
    inkluderet. Dette kan f.eks. ske,
    hvis du inkluderede en __init__.py i din tests/
    mappe. Alternativt kan du også bruge exclude-argumentet for at eksplicit
    forhindre medtagelsen af tests i pakken, men dette er lidt
    mindre robust.

Nu er alt, hvad du skal gøre for at installere din pakke, at køre følgende
fra inde i example_project/mappen3:

pip install -e .

Den . her henviser til den aktuelle arbejdskatalog, som jeg antager er den mappe
, hvor setup.py kan findes. Flaget -e angiver, at vi ønsker at installere
i redigerbar tilstand, hvilket betyder
at når vi redigerer filerne i vores pakke, behøver vi ikke at geninstallere
pakken, før ændringerne træder i kraft. Du skal dog enten genstarte
python eller genindlæse pakken!

Når du redigerer oplysninger i selve setup.py, skal du i de fleste tilfælde geninstallere
pakken, og også hvis du tilføjer nye (under)pakker.
Når du er i tvivl, kan det aldrig skade at geninstallere. Du skal bare køre pip install -e . igen.

Krav

De fleste projekter har nogle afhængigheder. Du har højst sandsynligt brugt
en requirements.txt
fil før, eller en environment.yml
hvis du bruger conda. Nu, hvor du opretter en setup.py, kan du angive dine
afhængigheder i install_requires-argumentet.
For eksempel kan du for et typisk datalogiprojekt have:

setup( name='example', version='0.1.0', packages=find_packages(include=), install_requires=)

Du kan angive krav uden en version (PyYAML), fastgøre en version (pandas==0.23.3), angive en minimum
version ('numpy>=1.14.5) eller angive et versionsinterval (matplotlib>=2.2.0,<3.0.0). Disse
krav vil automatisk blive installeret af pip, når du installerer din pakke.

Extras-require

I nogle tilfælde kan du have afhængigheder, som kun er påkrævet i visse situationer. Som datavidenskabsmand
laver jeg ofte pakker, som jeg bruger til at træne en model. Når jeg arbejder interaktivt
på en sådan model, kan jeg have brug for at have matplotlib og jupyter installeret for interaktivt at arbejde med
dataene og for at oprette visualiseringer
af modellens ydeevne. På den anden side, hvis modellen kører i produktion, ønsker jeg ikke
at installere matplotlib eller jupyter på den maskine (eller container), hvor jeg træner
eller foretager inferens. Heldigvis giver setuptools mulighed for at angive valgfrie afhængigheder i extras_require:

setup( name='example', version='0.1.0', packages=find_packages(include=), install_requires=, extras_require={ 'interactive': , })

Nu, hvis vi installerer pakken normalt (pip install example fra PyPI eller pip install -e . lokalt)
, vil den kun installere afhængighederne PyYAML, pandas og numpy. Men når vi angiver
at vi ønsker de valgfrie interactive afhængigheder (pip install example
eller pip install -e .),
så vil matplotlib og jupyter også blive installeret.

Skripter og indgangspunkter

Den vigtigste brugssituation for de fleste pythonpakker, som du installerer fra PyPI, er at levere funktionalitet
, som kan bruges i anden pythonkode. Med andre ord kan du import fra disse pakker.
Som datalog laver jeg ofte pakker, der ikke er beregnet til at blive brugt af anden pythonkode, men
som skal gøre noget, f.eks. træne en model. Som sådan har jeg ofte et pythonscript, som
jeg ønsker at udføre fra kommandolinjen.

Den bedste måde4 at eksponere funktionaliteten i din pakke til kommandolinjen er at definere
en entry_point som følger:

setup( # ..., entry_points={ 'console_scripts': })

Nu kan du bruge kommandoen my-command fra kommandolinjen, som igen vil udføre main
funktionen inde i exampleproject/example.py. Glem ikke at geninstallere – ellers vil kommandoen
ikke blive registreret.

Tests

Når du skriver kode, vil jeg kraftigt opfordre dig til også at skrive tests for denne kode. Til test
med python foreslår jeg, at du bruger pytest. Selvfølgelig skal du ikke tilføje pytest til dine afhængigheder
i install_requires: det er ikke påkrævet af brugerne af din pakke. For at få det installeret
automatisk, når du kører tests, kan du tilføje følgende til din setup.py:

setup( # ..., setup_requires=, tests_require=,)

Dertil skal du oprette en fil ved navn setup.cfg med følgende indhold:

test=pytest

Nu kan du blot køre python setup.py test, og setuptools vil sikre, at de nødvendige afhængigheder
er installeret, og køre pytest for dig! Tag et kig her, hvis
du vil angive argumenter eller indstille konfigurationsindstillinger for pytest.

Hvis du har yderligere krav til testning (f.eks. pytest-flask), kan du tilføje dem til tests_require.

Flake8

Personligt synes jeg, at det er en god idé at køre Flake8 for at
kontrollere formateringen af din kode. Ligesom med pytest skal du ikke tilføje flake8 til
install_requires-afhængighederne: Det behøver ikke at være installeret for at kunne bruge din
pakke. I stedet kan du tilføje den til setup_requires:

setup( # ..., setup_requires=)

Nu kan du blot køre python setup.py flake8. Du kan selvfølgelig også fastgøre versionen
af flake8 (eller en hvilken som helst anden pakke) i setup_requires.

Hvis du ønsker at ændre nogle af konfigurationsparametrene for Flake8 kan du tilføje et afsnit til
din setup.cfg. For eksempel:

max-line-length=120

Pakkedata

I nogle tilfælde ønsker du måske at inkludere nogle ikke-pythonfiler i din pakke. Disse
kan f.eks. være skemafiler eller en lille opslagstabel. Vær opmærksom på, at sådanne filer
vil blive pakket sammen med din kode, så det er generelt en dårlig idé at inkludere
alle store filer.

Sæt, at vi har en schema.json i vores projekt, som vi placerer i exampleproject/data/schema.json.
Hvis vi ønsker at inkludere denne i vores pakke, skal vi bruge package_data-argumentet i setup:

setup( # ..., package_data={'exampleproject': })

Dette vil sikre, at filen inkluderes i pakken. Vi kan også vælge at inkludere
alle filer baseret på et mønster, for eksempel:

setup( # ..., package_data={'': })

Dette vil tilføje alle *.json filer i enhver pakke, som den støder på.

Vend nu ikke selv at forsøge at finde ud af de installerede filers placering, da
pkg_resources har nogle meget praktiske bekvemmelighedsfunktioner:

  • pkg_resources.resource_stream giver dig en stream af filen, ligesom det
    objekt, du får, når du kalder open(),
  • pkg_resources.resource_string giver dig indholdet af filen som en streng,
  • pkg_resources.resource_filename vil give dig filnavnet på filen (og udpakke
    den til en midlertidig hvis den er inkluderet i en zippet pakke) for hvis de to muligheder
    ovenfor ikke passer til dine behov.

For eksempel kunne vi læse vores skema ind ved hjælp af:

from json import loadfrom pkg_resources import resource_streamschema = load(resource_stream('exampleproject', 'data/schema.json'))

Metadata

Hvis du vil udgive din pakke, så vil du sandsynligvis give dine
potentielle brugere nogle flere oplysninger om din pakke, herunder en beskrivelse,
navnet på forfatteren eller vedligeholderen og url’en til pakkens hjemmeside.
Du kan finde en komplet liste over alle tilladte metadata i setuptools
docs.

Dertil kommer, at hvis du vil udgive til PyPI, så vil du måske
automatisk indlæse indholdet af din README.md
i long_description,
og levere klassifikatorer for at fortælle pip endnu
mere om din pakke.

Vejledning

Denne blog skulle være et godt udgangspunkt for at opsætte de fleste af dine python-projekter.
Hvis du vil læse mere om python-pakning, så kig
på dokumentationen. Her er et eksempel setup.py
som kombinerer alle dele vist i denne blog:

from setuptools import setup, find_packagessetup( name='example', version='0.1.0', description='Setting up a python package', author='Rogier van der Geer', author_email='[email protected]', url='https://blog.godatadriven.com/setup-py', packages=find_packages(include=), install_requires=, extras_require={'plotting': }, setup_requires=, tests_require=, entry_points={ 'console_scripts': }, package_data={'exampleproject': })

og den tilhørende setup.cfg:

test=pytestmax-line-length=120

Forbedre dine Python-færdigheder, lær af eksperterne!

På GoDataDriven tilbyder vi et væld af Python-kurser fra nybegynder til ekspert, undervist af de allerbedste fagfolk på området. Deltag hos os, og få et højere niveau i dit Python-spil:

  • Python Essentials – Perfekt, hvis du lige er begyndt med Python.
  • Certified Data Science with Python Foundation – Vil du tage skridtet fra dataanalyse og visualisering til ægte datavidenskab? Dette er det rigtige kursus.
  • Advanced Data Science with Python – Lær at producere dine modeller som en professionel og bruge Python til maskinlæring.
Fodnoter

0: I denne blog har jeg brugt setuptools
til at opsætte mit eksempelprojekt. Alternativt
kan du også bruge distutils,
som er standardværktøjet til pakning i python, men det mangler funktioner
som f.eks. funktionen find_packages() og entry_points.
Da brugen af setuptools er meget almindelig i dag, og mange af dets funktioner
kan være særligt nyttige, foreslår jeg, at du bruger setuptools.

1: Hvis du ønsker, at versionen af din pakke også skal være tilgængelig inde i python,
så tag et kig her.

2: Du kunne også liste dine pakker manuelt, men dette er særligt fejlbehæftet.

3: Alternativt kunne du køre python setup.py install, men at bruge pip har
mange fordele, blandt andet automatisk installation af afhængigheder og den
mulighed for at afinstallere eller opdatere din pakke.

4: Du kunne også bruge scripts-argumentet (se for
eksempel her)
men da dette kræver, at du opretter et python-shell-script, fungerer det måske ikke
så godt (eller slet ikke) på Windows.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.