Kihagyás

7. gyakorlat

Warning

Figyelem! Jövő héten CooSpace ZH!

A gyakorlat anyaga

Python virtuális környezet

A Python egyik legnagyobb erőssége, hogy rengeteg minden el van hozzá készítve, akár a standard függvénykönyvtár részeként, akár letölthető külső függvénykönyvtárként. Erről már biztosan beszéltünk korábban is. Ez eddig világos, de vajon hogy működni a valóságban? Írunk a programot, amihez egy csomó ilyen külső dolgot használunk. És utána?! Felírjuk egy lapra, hogy miket szedtünk le, és reméljük, hogy a felhasználó/megrendelő is telepítette az adott csomagokat, azonos, vagy kompatibilis verziókat, és nem lesz ebből gond? Nem teljesen így lesz, bár a megoldás alapjaiban hasonlítani fog, de jó volna erre valamilyen garancia is.

Erre megoldás lesz a virtualenv használata, amely segítségével virtuális, szeparált környezeteket hozhatunk létre, és amiket azon belül telepítünk, azt később le is tudjuk majd kérni, így publikálás előtt pontos listát készíthetünk a telepítendő csomagokról. Ebből a listából aztán bárki egy paranccsal telepíthet mindent. Illetve a szeparált, virtuális környezetben telepített csomagok nem keverednek egyéb virtuális környezetekkel, sem pedig a globális csomagokkal (alapesetben), így különböző verziókat is használhatunk a különböző környezetekben.

Oké, ez jól hangzik. Mi kell ehhez? Ehhez és még sok egyéb csomag telepítéséhez a pip csomagkezelő fog kelleni.

pip

A pip (package installer for Python) egy csomagkezelő rendszer, amely operációs rendszertől függetlenül használható mindegyiken, és sok hasznos dolgot tud. Alapvetően, a Python 3.4 verziótól kezdődően tartalmazza a pip csomagkezelőt. Ha esetleg nincs telepítve mégse, akkor le lehet tölteni a hivatalos oldalról.

A pip alapvetően a PyPI (Python Package Index, https://pypi.org/) oldalról telepít csomagokat.

Akkor telepítsünk vele fel egy csomagot, például a virtualenv-et. Ehhez parancssorba be kell pötyögnünk a pip install virtualenv parancsot (adminisztrátorként). Ez a legutóbbi stabil kiadást fogja telepíteni, de ha nem elégszünk meg ezzel, és mindenképpen a legfrissebb fejlesztői változatra van szükségünk, a pip segítségével bármit telepíthetünk egy bármilyen git verziókövetőből, ebben az esetben a pip install git+https://github.com/pypa/virtualenv.git@main parancsot kell kiadnunk.

Természetesen felhasználói módban is telepíthetjük a virtualenv csomagot, a parancs után a --user kapcsolót kiadva. Amennyiben nincsenek a környezeti változók megfelelően beállítva, a python -m paranccsal lehet modulokat "megszólítani".

Hozzunk létre egy virtuális környezetet:

1
virtualenv gyak7

Ezzel elkészült a virtuális környezet a projektünkhöz, mostmár csak aktiválnunk kellene

  • Linux: source gyak7/bin/activate

  • Windows: gyak7\Scripts\activate

Az aktív környezetből kilépni a deactivate paranccsal tudunk. Amíg nem lépünk ki, minden telepítés a virtuális környezetben történik majd.

Például telepítünk valamit: pip install cowsay

A telepített cowsay modult parancssorból tudjuk használni. Próbáljuk ki!

Amennyiben minden szükséges külső függvénykönyvtárat telepítettünk, valahogy rögzíteni kéne, hogy a felhasználó is pontosan azokat a verziókat telepíthesse: Ehhez a pip freeze parancsot használjuk első körben, ez a standard kimenetre kiírja az összes, környezetben telepített függvénykönyvtárat. Ezt irányítsuk egy fájlba, jellemzően requirements.txt szokott lenni.

1
pip freeze > requirements.txt

A requirements.txt fájlt általában a projekt gyökerébe tesszük, ezt felismeri a PyCharm is, és sok más IDE is. Ha kapunk valahonnan egy projektet, ahol ilyet látunk, nem kell megijedni, nem kézzel telepítünk mindent, a pip ebben is segít:

pip install -r requirements.txt

Ez mindent úgy telepít fel, ahogy az a requirements.txt fájlban rögzítve van. A telepítést általában célszerű egy virtuális környezetbe végezni.

Ha pedig egy virtuális környezetre nincs szükségünk, csak kitöröljük a mappáját: rm -rf gyak7/

Letöltés az internetről

Ez pont megoldható a Python beépített függvénykönyvtárával is, az alábbi módon:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import json
import os
import urllib.request
import shutil

HP_API = "http://hp-api.herokuapp.com/api/characters"

if os.path.exists("tmp"):
    shutil.rmtree("tmp")
os.mkdir("tmp")

with urllib.request.urlopen(HP_API) as f:
    html = f.read().decode('utf-8')
    data = json.loads(html)

    for character in data:
        print(f"{character['name']} - {character['image']}")
        print(character)
        # Első megoldás
        # with urllib.request.urlopen(character['image']) as img, \
        #         open(os.path.join("tmp", os.path.basename(character['image'])), "wb") as out:
        #     out.write(img.read())

        # Második megoldás
        # urllib.request.urlretrieve(character['image'], os.path.join("tmp", os.path.basename(character['image'])))

requests

Azonban a külső requests modul segítségével jóval egyszerűbb dolgunk van, és jóval több dolgot csinálhatunk jóval gyorsabban.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import requests
from requests.exceptions import HTTPError

HP_API = "http://hp-api.herokuapp.com/api/characters"

response = requests.get(HP_API)
print("Válasz", response.status_code)
if response:
    # A válasz, szövegként
    print(response.text)
    # byte folyamként
    print(response.content)
    # A válasz JSON formátumra alakítva
    print(response.json())
    # A válasz header-ök
    print(response.headers)
    print(response.headers['Content-Type'])
    print(response.headers['content-type'])

try:
    response = requests.get('https://api.github.com/invalid')
    # Kivétel dobása nem megfelelő válasz esetén
    response.raise_for_status()
except HTTPError as http_err:
    print(f'HTTP error occurred: {http_err}')
except Exception as err:
    print(f'Other error occurred: {err}')
else:
    print('Success!')

response = requests.get(
    'https://api.github.com/search/repositories',
    params={'q': 'requests+language:python'},
    headers={'Accept': 'application/vnd.github.v3.text-match+json'},
)
print(response.json())

response = requests.post('https://httpbin.org/post', data=[('key', 'value')])
print(response.json())
response = requests.post('https://httpbin.org/post', data={'key': 'value'})
print(response.json())
response = requests.post('https://httpbin.org/post', json={'key':'value'})
print(response.json())

requests.put('https://httpbin.org/put', data={'key': 'value'})
requests.delete('https://httpbin.org/delete')
requests.head('https://httpbin.org/get')
requests.patch('https://httpbin.org/patch', data={'key': 'value'})
requests.options('https://httpbin.org/get')

response = requests.delete('https://httpbin.org/delete')
print(response.json())

# Ha ellenőrizni szeretnénk, hogy konkrétan mit küldtünk, arra a response objektumban található request szolgál.
# Ez tárolja az eredeti kérést
print("eredeti kérés DELETE", response.request)
response = requests.post('https://httpbin.org/post', data=[('key', 'value')])
print("eredeti kérés POST", response.request)
print("eredeti kérés HEADERS", response.request.headers)
print("eredeti kérés URL", response.request.url)

arrow

Az Arrow segítségével még magasabb szintű dátum és időkezelés valósítható meg. Az Arrow csomagról bővebben: https://arrow.readthedocs.io/en/latest/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import arrow
import datetime

# Jelenlegi idő
utc = arrow.utcnow()
print("utc", utc)
print("utc local", utc.to('local'))
print("utc local pacific", utc.to('local').to("US/Pacific"))
print("---")

utc = arrow.now("local")
print("arrow.now", utc)
print(utc.humanize(locale="hu_HU"))
print(arrow.now('America/New_York'))
print("---")

# Idő lekérése
ido = arrow.get("2021-03-24T17:55:27.777527+01:00")
print(ido)
print(ido.humanize(locale="hu_HU"))

ido = ido.shift(hours=-1)
print("-1 ora:", ido)
print(ido.humanize(locale="hu_HU"))
ido = ido.shift(days=-2, hours=-1, minutes=10)
print("-2 nap -1 ora -10 perc:", ido)
print(ido.humanize(locale="hu_HU"))
print(ido.humanize(locale="ko_kr"))
print(ido.humanize(granularity="minute"))
print(ido.humanize(granularity=["hour", "minute"]))
print(ido.humanize(only_distance=True, granularity=["day", "minute"]))
print(ido.format())
print(ido.timestamp())
print(arrow.get(1616425527.777527))
print(arrow.get('2013-05-05 12:30:45', 'YYYY-MM-DD HH:mm:ss'))
print(arrow.get("Tomorrow (2019-10-31) is Halloween!", "YYYY-MM-DD"))
print(arrow.get('2013-05-05  T \n   12:30:45\t123456', 'YYYY-MM-DD T HH:mm:ss S', normalize_whitespace=True))
print("type", type(ido))
print("type", type(ido.naive))
print("datetime tipus", ido.naive)
print("---")
# Datetime támogatás

print(arrow.get(datetime.datetime.utcnow()))
print(arrow.get(datetime.date(2013, 5, 5)))

print("---")
print(arrow.utcnow().span('hour'))
print(arrow.utcnow().floor('hour'))
print(arrow.utcnow().ceil('hour'))

start = datetime.datetime(2020, 4, 5, 10, 30)
end = datetime.datetime(2020, 4, 5, 16, 15)
for r in arrow.Arrow.range('hour', start, end):
    print("range", r)

karacsony = arrow.utcnow().replace(month=12, day=25)
napok_karacsonyig = (karacsony - arrow.utcnow()).days
print(f"Karácsonyig {napok_karacsonyig} nap van hátra!")
print((karacsony - arrow.utcnow()))
print(type(karacsony - arrow.utcnow()))

Fire

A Fire segítségével még egyszerűbben valósítható meg a parancssori argumentumkezelés. Bővebben például itt: https://github.com/google/python-fire/blob/master/docs/guide.md

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import datetime
import fire


def pentek13(elmult_evek):
    """
    Az utóbbi hány évben számoljuk ki a péntek 13-ak számát.
    :param elmult_evek: az elmúlt évek száma
    :return: hányszor volt péntek 13
    """
    most = datetime.date.today()
    mikortol = most.year - elmult_evek

    ev_szamlalo = dict()

    for ev in range(mikortol, most.year):
        for honap in range(1, 13):
            datum_13 = datetime.date(ev, honap, 13)
            if datum_13.weekday() == 4:
                if datum_13.year not in ev_szamlalo:
                    ev_szamlalo[datum_13.year] = 0
                ev_szamlalo[datum_13.year] += 1

    return ev_szamlalo


if __name__ == '__main__':
    fire.Fire(pentek13)

Több metódus modulokba szervezése

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import fire


def add_simple(a, b, c):
    return a + b + c


def add_args_default(a, *args):
    return a + sum(args)


if __name__ == '__main__':
    fire.Fire({'add3': add_simple, 'add': add_args_default})

Osztályok használatával még jobban OO parancssori argumentum beolvasó hierarchia jöhet létre.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import fire


class Cake(object):
    def __init__(self, slices=10, flavour='chocolate'):
        self._slices = slices
        self._flavour = flavour

    def price(self):
        return self.slices * 390

    def like(self, favorite_flavour):
        f1 = set(self._flavour)
        f2 = set(favorite_flavour)
        res = f1 & f2
        return len(res) >= ((len(f1) + len(f2)) // 2)

    def __str__(self):
        return '[Cake, slices=%d, flavour=%s]' % (self._slices, self._flavour)


if __name__ == '__main__':
    fire.Fire(Cake)

Feladatok

Harry Potter API

A Harry Potter API-ról letölthető adatok alapján oldjuk meg az alábbi feladatokat:

1
2
3
- Készíts adatosztályt a karakterekhez (Karakter néven)
- Töltsd be a JSON fájlból az adatosztályba a karakterek adatait. Készíts egy `statisztika.txt` nevű fájlt, amiben benne van, hogy melyik házból hány ember van az adathalmazban, milyen hajszínű emberből van a legtöbb, milyen szemszínű emberből van a legtöbb, illetve kik azok az adatbázisban, akik nem emberek.
- Alakítsd át a struktúrát, hogy házakra bontva tartalmazza az adatokat. Az `image` tulajdonságot töröld ki, és mentsd el az eredményt a `hogwarts-by-house.json` fájlba.

Korábbi feladatok

A korábbi dátumos/argumentumkezelős feladatok átírása az új modulok segítéségével

Új modulok felfedezése

A feladat egy új modul megkeresése és kipróbálása. Tetszőlegesen lehet olyan modul, ami érdekel, vagy a későbbiekben tudod használni. A CooSpace órai feladatnál a kipróbált kódokat is fel kell tenni.

Kapcsolódó linkek


Utolsó frissítés: 2021-03-25 10:58:03