Szövegfeldolgozás Pythonban

Kategória: Python.

XML

Már az írás pillanatában is bőven túl volt az XML a csúcson, és az elterjedtsége várhatóan tovább fog csökkenni. Ám még mindig van annyira fontos, hogy legalább említés szintjén érdemes vele foglalkozni. Ebben a fejezetben a Python XML támogatását vesszük szemügyre.

Az XML felépítésével itt nem foglalkozunk, az le van írva a szabványok oldalon. Tekintsük a következő példát!

test.xml

<fruits>
    <fruit name="apple">3</fruit>
    <fruit name="banana">2</fruit>
    <fruit name="orange">5</fruit>
</fruits>

A Python beépített könyvtárakkal támogatja az XML feldolgozást. Az xml.dom komponenst minidom-két szokás betölteni. Az alábbi módon tudjuk ennek segítségével feldolgozni a fenti XML-t:

from xml.dom import minidom
 
my_xml = minidom.parse('test.xml')
fruits = my_xml.documentElement
for fruit in fruits.childNodes:
    if isinstance(fruit, minidom.Element):
        fruit_name = fruit.attributes['name'].value
        fruit_value = fruit.firstChild.data
        print(f'{fruit_name}: {fruit_value}')
# apple: 3
# banana: 2
# orange: 5

Látható, hogy kissé nehézkes a dolog. Helyenként logikátlan a dolog, pl. az érték külön gyerek elemként jelenik meg, ahogy a kizárólag formázási céllal benne levő szóközök. Meglehetősen sokáig is tartott, mire elkészült a fenti példa. Ezt minden bizonnyal a könyvtár fejlesztői is tapasztalták, így megjelent az ElementTree kiegészítő. Ennek segítségével már jóval átláthatóbb és logikusabb kódot írhatunk:

import xml.etree.ElementTree as ET
 
my_xml = ET.parse('test.xml')
fruits = my_xml.getroot()
for fruit in fruits:
    fruit_name = fruit.attrib['name']
    fruit_value = fruit.text
    print(f'{fruit_name}: {fruit_value}')
# apple: 3
# banana: 2
# orange: 5

Ha az XML fájl mérete túl nagy, adott esetben még a memóriába sem fér el egyszerre, így nem tudjuk felépíteni a DOM-ot (Document Object Model), akkor - más programozási nyelvekhez hasonlóan - a sax technológiát ill. az ezt megvalósító komponenst használhatjuk:

import xml.sax
 
class MyXmlHandler(xml.sax.ContentHandler):
    def __init__(self):
        self.result = {}
        self.name = ''
 
    def startElement(self, tag, attributes):
        if tag == 'fruit':
            self.name = attributes['name']
 
    def endElement(self, tag):
        if tag == 'fruit':
            self.result[self.name] = self.value
 
    def characters(self, content):
        self.value = content
 
    def print_result(self):
        for fruit_name in self.result:
            fruit_value = self.result[fruit_name]
            print(f'{fruit_name}: {fruit_value}')
 
my_parser = xml.sax.make_parser()
my_handler = MyXmlHandler()
my_parser.setContentHandler(my_handler)
my_parser.parse("test.xml")
my_handler.print_result()
# apple: 3
# banana: 2
# orange: 5

A módszer itt a visszahívás, angol callback: az egyes elemek elején, magán az elemen és azok végén hív a rendszer egy-egy függvényt, és a függvények felelőssége a feldolgozás. A fenti példa végeredménye ugyanaz, mint a korábbiaké, SAX módszerrel.

Ezzel épp hogy karoltuk az XML kezelésnek a felszínét. Számos nagyobb terület maradt nyitva:

  • bonyolultabb lekérdezések,
  • az XML módosítása,
  • elemek törlése az XML-ből,
  • XML struktúra felépítése az alapoktól,
  • XML fájl generálása,
  • stb. stb. stb.

Túl nagy energiákat erre a témára már nem érdemes szánni, de szükség esetén a neten még fellelhetőek azok az anyagok, amelyek hasznosak ebben a témában:

JSON

A belső json modult kell importálni. A json.loads(json_str) átalakítja a JSON formátumot (tehát a stringet) Python szótárrá (dict), míg a json.dumps(python_dict) a szótárat alakítja JSON formázott stringgé:

import json
 
x_json =  '{"name": "Pista", "age": 30, "city": "Budapest"}'
x_dict = json.loads(x_json)
print(x_dict) # {'name': 'Pista', 'age': 30, 'city': 'Budapest'}
print(x_dict["age"]) # 30
 
y_dict = {
    "name": "Sanyi",
    "age": 40,
    "city": "Szeged",
}
y_json = json.dumps(y_dict)
print(y_json) # {"name": "Sanyi", "age": 40, "city": "Szeged"}

Mindenféle formázások lehetségesek, melyekre itt nem térek ki.

Sablonkezelés

A sablonkezelés valójában string formázás: az adatokat adott sablon szerint formázzuk meg. Pythonban a beépített string formázás is alkalmas valamilyen szinten sablonkezelésre, ami egyébként egy egész hosszú fejlődési utat járt be. Tekintsük azt a példát, hogy két számot szeretnénk összeadni, és ki szeretnénk írni a két összeadandót közöttük egy plusz jellel, egy egyenlőségjelet, majd az eredményt, tehát pl.:

3 + 2 = 5

Itt a 3 és a 2 legyen két változó értéke, pl.:

a = 3
b = 2

Az első ötletünk általában az, hogy a számokat stringgé alakítjuk, majd a + operátorral összefűzzük:

print(str(a) + ' + ' + str(b) + ' = ' + str(a+b))
# 3 + 2 = 5

Ezt csak nagyon egyszerű esetekben érdemes használnunk, mivel a kódunk gyorsan áttekinthetetlenné válik. Ennél egy fokkal jobb az alábbi megoldás:

print('%d + %d = %d' % (a, b, a+b))
# 3 + 2 = 5

Itt tehát elkülönül a sablon és a behelyettesített értékek. Ráadásul itt a más programozási nyelvben megszokott (valójában a C-ből eredő) string formázási lehetőségeket is megadhatunk, például:

print('%03d + %03d = %03d' % (a, b, a+b))
# 003 + 002 = 005

Ezzel ugyanaz a probléma, mint az elsővel: összetettebb esetekben kezd áttekinthetetlenné válni. Következő lehetőség a kapcsos zárójeles megoldás, ahol már egyértelműen elkülönül a sablon és a behelyettesítendő értékek; ez utóbbiakat egy függvényhívás paramétereiként kell átadni:

my_template = '{x} + {y} = {z}'
my_result = my_template.format(x=a, y=b, z=a+b)
print(my_result)
# 3 + 2 = 5

Nem feltétlenül kell nevesíteni a sablon értékeket, ez esetben sorrendben kell megadnunk:

print('{} + {} = {}'.format(a, b, a+b))
# 3 + 2 = 5

A sorrenden változtathatunk is:

print('{1} + {0} = {2}'.format(a, b, a+b))
# 2 + 3 = 5

Ugyanazt az értéket többször is kiírhatjuk:

print('{0} + {0} = {1}'.format(a, a+a))
# 3 + 3 = 6

Ennek egyszerűsített változata:

print(f'{a} + {b} = {a+b}')
# 3 + 2 = 5

Ez igen tömör, jól olvasható. A kapcsos zárójelek közé akármilyen kifejezést írhatunk, és igen gazdag a formázási lehetőség. Pl.:

print(f'{a:.2f} + {b:.2f} = {a+b:.2f}')
# 3.00 + 2.00 = 5.00

Ebben a példában a számot két tizedes pontossággal írtuk ki. Érdemes megemlíteni itt pár további lehetőséget:

print(f'{12.3456789:.3f}') # 12.346
print(f'{12.3456789:.3g}') # 12.3
print(f'{123456789:,}') # 123,456,789

Az f adott tizedespontos pontossággal formáz, a g adott értékes számjegyig (túl nagy és túl kicsi számok esetén sajnos a tudományos jelölést használja), az alsó pedig vesszővel tagol (arra sajnos nem sikerült rájönnöm, hogy hogyan lehet ponttal tagolni, és vesszőt használni tizedesvesszőként).

Bonyolultabb esetekben használhatunk külső sablonkezelő könyvtárat. Az elterjedtebb általános sablonkönyvtárakhoz (Mustache, Velocity stb.) mind van Pythonra írt könyvtár, de vannak kifejezetten Python sablonkezelő könyvtárak is. Ilyen pl. a Jinja. Telepítése a szokásos:

pip install jinja2

Használata:

from jinja2 import Template
 
my_template = Template("Hello {{something}}!")
print(my_template.render(something="World"))
# Hello World!

Egy többsoros példa:

from jinja2 import Template
 
my_template = Template("""{% for fruit in fruits %}{{fruit}}: {{fruits[fruit]}}
{% endfor %}""")
print(my_template.render(fruits = {'apple': 3, 'banana': 2, 'orange': 5}))
# apple: 3
# banana: 2
# orange: 5

Naplózás

A naplózás nagyon fontos az üzemeltetés során: egy esetleges hiba esetén ugyanis ez az első támpont. Pythonban a naplózás is viszonylag egyszerű: a belső logging könyvtárat kell használni. 5 naplózási szintet különböztet meg: critical, error, warning, info, debug.

Használata:

import logging
 
logging.debug('A debug message')
logging.info('An info message')
logging.warning('A warning message')
logging.error('An error message')
logging.critical('A critical message')

Az alapértelmezett naplózási szint a warning, így ennek eredménye:

WARNING:root:A warning message
ERROR:root:An error message
CRITICAL:root:A critical message

A logging.basicConfig() hívással lehet finomhangolni az eredményt: a naplózási szintet, a kimeneti formátumot vagy a fájlnevet beállítani. Tekintsük a következpt:

import logging
 
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s - %(name)s - %(message)s')
 
logging.debug('A debug message')
logging.info('An info message')
logging.warning('A warning message')
logging.error('An error message')
logging.critical('A critical message')

Eredménye:

2021-07-30 10:55:30,740 INFO - root - An info message
2021-07-30 10:55:30,740 WARNING - root - A warning message
2021-07-30 10:55:30,740 ERROR - root - An error message
2021-07-30 10:55:30,740 CRITICAL - root - A critical message

Ha megadjuk a filename paramétert, akkor az eredményt fájlba írja.

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