Fejlett Python témák

Kategória: Python.

Automatikus formázás

Áttekintés

A programozók sokan sokféle stílusban programoznak. Mások kódját gyakran nehezen olvashatónak vagy pusztán igénytelennek látjuk; nyilván lesznek olyanok, akik az általunk megírt kódról vélekednek így. De vajon van-e olyan stílus, ami valamilyen objektív kritérium alapján a lehető legjobb? Erre a kérdésre nehéz választ adni. Vannak viszont ajánlások, és projekten belül mindenképpen célszerű valamilyen követendő stílust lerögzíteni.

A következő kérdés az, hogy ezt a stílust mennyire szeretnénk kierőszakolni? Szinte minden programozási nyelvhez léteznek automatikus formázó eszközök. Ezek kötelező bevezetése kétélű fegyver: kétségtelen, hogy annyira egységesíti a kódot, amilyen mértékben csak lehet, ám sok esetben a gépi formázás kevésbé logikus, mint amit a fejlesztő eredetileg megalkotott. Ráadásul itt bejön még a kódgenerálás kérdése is: ha a generált kódot formázzunk automatikusan, abból gyakran káosz lesz.

Személy szerint én szeretek óvatosan bánni a kötelezően használandó automatikus eszközökkel. Kicsit nyelvfüggő is az, hogy mennyire tartom ezt jó ötletnek. A Python a szigorú szerkezetével és tömörségével alkalmas arra, hogy ott tényleg bevezessünk kötelező érvényű automatikus formázó eszközöket.

Black

A Black egy Python automatikus formázó eszköz. Telepítése:

pip install black

Használata:

black [forrás].py

Beállítása PyCharm-ban

Beállítása PyCharm-ban:

  • File → Settings… → Tools → External Tools → + a bal felső sarokban
  • Name: Black
  • Group: External Tools
  • Description: Black in PyCharm configuration
  • Program: C:\programs\Python\Python39\Scripts\black.exe (ide a saját elérési útvonalat kell beírni)
  • Arguments: $FilePath$
  • Working directory: $ProjectFileDir$
  • Advanced options: az első kettő legyen bekapcsolva (alapértelmezett)

Érdemes még gyorsbillentyűt is hozzárendelni:

  • File → Settings… → Keymap → External Tools → External Tools
  • jobb kattintás a Black-en
  • Add Keyboard Shortcut
  • Itt adjunk meg egy billentyűkombinációt (én az Alt+B-t adtam meg)
  • OK

Ezt követően a megadott billentyűkombinációval az éppen megnyitott forrást lehet automatikusan formázni.

Formázás

Néhány látványosabb formázás:

  • A 88 karakternél szélesebb sorokat töri. (Néhány kivétellel: pl. string, megjegyzés.)
  • Ha nem fér ki a felsorolás egy sorba, akkor külön sorba teszi az összes elemet, és az utolsó után is vesszőt tesz.
  • Törli a felesleges üres sorokat (egyet meghagy), de van, ahol két üres sort tart elfogadhatónak (pl. az importok után); oda behelyezi.

Kódrészlet kihagyása a formázásból

Ha egy kódrészletet nem szeretnénk, hogy formázzon, akkor az alábbi megjegyzések közé kell tenni:

# fmt: off
...
# fmt: on

Egységtesztelés

Egy példa

Tegyük fel, hogy van egy mymath.py forrásunk a következő tartalommal:

def add(a, b):
    return a + b
 
def multiply(a, b):
    return a * b

Egységtesztelés

Ezt szeretnénk egységtesztelni (testmymath.py):

import unittest
from mymath import *
 
class TestMyMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(5, add(3, 2))
 
    def test_multiply(self):
        self.assertEqual(6, multiply(3, 2))

Futtatás parancssorból:

python -m unittest testmymath

Lefedettség mérés

Lefedettséget a coverage csomaggal tudunk mérni.

pip install coverage

Futtatás:

coverage run -m unittest testmymath

Eredmény:

coverage report -m

Részletesebb riport generálása:

coverage html

A PyCharm közösségi, ingyenes változata nem támogatja a lefedettséget.

Nosetest

Az egyszerű esetekben teljesen használható a unittest. Másik lehetőség: a nosetests kiegészítőt tudjuk használni. Ezt először fel kell telepíteni:

pip install nose

Majd a futtatás:

nosetests mymathtest

Teljesítmény problémák kezelése

Áttekintés

Az egyik legnehezebben felderíthető probléma típus - nemcsak a Pythonban - a teljesítmény probléma: amikor a rendszer funkcionálisan jól működik, de lassú, sok memóriát fogyaszt, ún. memória szivárgás lép fel stb. Általános recept e problémák felderítésére nem adható; itt csak néhány tippet olvashatunk arról, hogy milyen eszközök állnak rendelkezésre Pythonban, amelyek segítségével legalább el tudunk indulni.

Az alábbi oldalakat érdemes a témában elolvasni:

Egy példa

Tekintsük az alábbi, kissé erőltetett, de jól érthető példát (profilertest.py):

def big_memory():
    my_array = [0] * 100_000_000
    return 1
 
def slow():
    for i in range(100_000_000):
        pass
    return 2
 
def quick():
    return 3
 
def run_all():
    print(big_memory())
    print(slow())
    print(quick())
 
run_all()

A példában van 3 függvény:

  • big_memory(): sok memóriát fogyaszt.
  • slow(): hosszú ideig tart.
  • quick(): gyors.

Teljesítmény problémák felderítése

A teljesítmény problémák kezeléséhez a cProfile csomagot használhatjuk. Ehhez tudnunk kell, hogy a Python valójában csak egy specifikáció, melynek több megvalósítása is van. Az Alapértelmezett a CPython, ami - ahogy a nevéből is következik - C-ben lett megvalósítva. Ehhez tartozik a cProfiler. Az egyik lehetőség ennek futtatására az alábbi, Python kódból történő:

import cProfile
cProfile.run('run_all()')

Ennek az eredménye:

         10 function calls in 2.092 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.092    2.092 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 profilertest.py:14(quick)
        1    0.309    0.309    2.092    2.092 profilertest.py:17(run_all)
        1    0.324    0.324    0.324    0.324 profilertest.py:3(big_memory)
        1    1.459    1.459    1.459    1.459 profilertest.py:8(slow)
        1    0.000    0.000    2.092    2.092 {built-in method builtins.exec}
        3    0.000    0.000    0.000    0.000 {built-in method builtins.print}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

A követkőt olvashatjuk ki:

  • A teljes futás kicsit több mint 2 másodpercig tartott.
  • A quick() mérési hibahatáron belül lefutott.
  • A run_all() futása kb. 3 tized másodpercig tartott. (Ez kicsit meglepő, és minden bizonnyal a profiling miatti lassulás ebbe számolódik.)
  • A big_memory() szintúgy.
  • A slow() pedig közel másfél másodpercig.
  • Ezen kívül kiderül az is, hogy a print() függvényt háromszor hívtuk, a többit egyszer.

Mindenesetre kiolvasható, hogy elsősorban a slow() függvényt érdemes jobban megvizsgálnunk.

Memória problémák felderítése

Kifejezetten memóriaproblémák felderítéséhez érdemes a memory-profiler csomagot használni. Ezt szokásos Python csomagként kell feltelepíteni:

pip install memory-profiler

A @profile annotációval kell annotálni azokat a függvényeket, amelyekre végre szeretnénk hajtani. A slow() függvény futása ezzel kivárhatatlanná vált, így csak a big_memory() és quick() függvényeket ellenőrizzük:

@profile
def big_memory():
    my_array = [0] * 100_000_000
    return 1
 
def slow():
    for i in range(100_000_000):
        pass
    return 2
 
@profile
def quick():
    return 3
 
def run_all():
    print(big_memory())
    print(slow())
    print(quick())
 
run_all()

Az elemzést most kívülről indítjuk:

python -m memory_profiler profilertest.py

Az eredmény:

Filename: profilertest.py

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     2   19.922 MiB   19.922 MiB           1   @profile
     3                                         def big_memory():
     4  782.863 MiB  762.941 MiB           1       my_array = [0] * 100_000_000
     5  782.863 MiB    0.000 MiB           1       return 1

Filename: profilertest.py

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
    13   19.457 MiB   19.457 MiB           1   @profile
    14                                         def quick():
    15   19.457 MiB    0.000 MiB           1       return 3

Értelmezés:

  • A mértékegység a kissé szokatlan MiB. Ennek jelentése megabájt olyan értelemben, hogy ez pontosan 1.048.576 bájt (azaz 1024*1024). A problémát az okozza, hogy a megabájt használata nem egységes; jelenthet 1024*1024 bájtot, 1000*1024 bájtot, de 1000*1000 bájtot is.
  • A @profile önmagában behoz kb. 20 megabájtot.
  • A my_array = [0] * 100_000_000 behoz kb. 762 megabájtot. Utána számolva: a százmilliós tömb elemeit 8 bájton tárolja, és 100.000.000*8/1.048.576 eredménye kb. 762.
  • Máshol csak a @profile által behúzott közel 20 megabájt jelenik meg.

Eredményül tehát azt kaptuk, hogy a 100.000.000 méretű tömb létrehozása okozta a nagy memória fogyasztást.

Keretrendszerek

https://www.djangoproject.com/start/

TODO: Django (https://www.djangoproject.com/)

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License