Kategória: Python.
Table of Contents
|
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:
- https://docs.python.org/3/library/debug.html: általános hibakeresés Pythonban.
- https://docs.python.org/3/library/profile.html: lehetőségek a futási sebesség problémák felderítésére.
- https://realpython.com/python-memory-management/: a Python memóriakezelésének a részleteiről olvashatunk.
- https://pypi.org/project/memory-profiler/: memóriaproblémák lehetséges felderítése.
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/)