İçeriğe geç

Python Reduce ile JSON Walker Yazmak

Merhabalar.

Giriş.

Python genellikle nesne yönelimli bir scripting dili olarak sunulsada çoklu paradigmaya dayalı, farklı yaklaşımlara yerleşik olarak destek verebilen güçlü bir dildir. Örneğin fonksiyonel programlama.

Bugün bu paradigmanın bir yansıması olan ve Python’ın temel kütüphanesinin sahip olduğu reduce fonksiyonuna, kullanımına ve reduce yardımıyla işlevsel bir JSON walker yazmaya göz atacağız.

reduce fonksiyonu, reduction ya da folding (indirgeme, kesinti, katlama) olarak adlandırılan bir matematiksel işlemin uygulanmasına dayanır.

Bu işlem bir dizinin elemanlarının işlenerek kümülatif olarak tek bir sonuç üretmesidir. Örneğin bir sayı dizisinin çarpımı ya da toplamı gibi.


Isınma turları ve kullanım.

Python 2 yerleşik olarak barındırsa da Python 3 te reduce fonksiyonu için functools modülünden içeri aktarım gerektirir.

Öncelikle basit başlayalım ve 1..5 den kadar olan sayıların toplamını bulan bir fonksiyon yazalım.

>>> total = 0
>>> number_list = [1, 2, 3, 4, 5]

>>> for number in number_list: total += number

>>> total
15

Buraya kadar pek havalı bir şey yok. Basit bir for loop ile işlemi gerçekleştirdik. Peki reduce kullansaydık?

>>> from functools import reduce

>>> reduce(lambda a,b : a+b, [1, 2, 3, 4, 5])
15

Yukarıdaki örnekte 2li eleman işlemleri için kullanılacak olan fonksiyonu lambda yardımıyla anonim bir fonksiyon tanımladık. Custom bir fonksiyon yazarak farklı bir reduction islemi daha yapalım ve kullanımını iyice kavrayalım.

def multiple(a, b):
    print(f"{a} x {b} = {a * b}")
    return a * b
reduce(multiple, [1, 2, 3, 4, 5])
1 x 2 = 2
2 x 3 = 6
6 x 4 = 24
24 x 5 = 120
120

Reducing 3 aşamadan oluşuyor;

  • Dizinin ilk 2 elemanını belirtilen fonksiyondan geçirip ara bir değer oluştur.
  • Oluşturulan ara değer ile bir sonraki elemanı fonksiyondan geçir, tekrar bir ara değer oluştur.
  • Dizi tükenene kadar bu yukarıdaki işlemlere devam et.

Fakat Python bundan daha fazlasını önerir. Reduce fonksiyonunun tam kullanımı ve argümanları aşağıdaki gibidir;

functools.reduce(function, iterable[, initializer])

initializer herhangi bir nesne olabilir ve işleme bir başlangıç noktası tanımlamak için kullanılır. Eğer bu initializer fonksiyona sağlanırsa, sağlanan initalizer, sağlanan fonksiyonun ilk argümanı olarak fonksiyona geçirilerek bir ara değer üretilir.

Yukarıda yazdığımız custom multiple fonksiyonu ile kullanımını kavrayacak olursak;

>>> numbers = [1,2,3]

>>> reduce(multiple, numbers, 100)
100 x 1 = 100
100 x 2 = 200
200 x 3 = 600
600

initializer(100), ilk argüman olarak fonksiyona geçirildi. buna bağıl olarak oluşturulan ara değer takip eden reducing işlemlerinde kullanıldı ve nihai değer oluşana kadar oluşturulan ara değerlere dayanak oldu.

Başka bir örnek;

>>> reduce(multiple, numbers, "py")
py x 1 = py
py x 2 = pypy
pypy x 3 = pypypypypypy
'pypypypypypy'

initializer argümanı, reducing yapılacak iterable’ın boş gelmesi durumunda bir hata raise edilmesinden ziyade varsayılan bir değer döndürmek içinde kullanılabilir.

>>> reduce(lambda a, b : a + b, [], 100)
100

Dict reduction ve JSON walker.

Yazının asıl konusuna artık geçebiliriz. Bir JSON walker yazmak.

Örnek bir JSON

{
    "pk" : 1,
    "personal" : {
        "age": 19,
        "first_name" : "john",
        "last_name": "doe",
        "address" : {
            "coordinates" : {
                "longitude" : "481148",
                "latitude": "114811"
            },
            "city" : "new york",
            "street" : "48. street",
            "raw": "lorem ipsum dolor sit amet."
        }
    },
    "languages" : [
        {
            "name" : "python",
            "degree": "advanced"
        },
        {
            "name" : "golang",
            "degree": "beginner"
        }
    ]
}

Bu developer verisi üzerinde örneğin açık adres verisine ulaşmak isteseydik?

Easen, recursive cagrıma sahip aşağıdaki gibi bir fonksiyon iş görecektir;

key sayısı 1’e indirgenene kadar, oluşturulan her ara değerin içinde bir sonraki key aranacaktır.

def reduce_json_with_recursion(data: dict, keys: List[Union[str, int]]):
    if len(keys) == 0:
        return

    elif len(keys) == 1:
        return data[keys[0]] if keys[0] in data.keys() else None
    try:
        return reduce_json_with_recursion(data[keys[0]], keys[1:])
    except (IndexError, KeyError):
        return

yukarıdaki JSON üzerinden test edecek olursak;

>>> reduce_json_with_recursion(developer_data, ["personal", "address", "raw"])
'lorem ipsum dolor sit amet.'
>>> reduce_json_with_recursion(developer_data, ["personal", "address", "coordinates"])
{'longitude': '481148', 'latitude': '114811'}

Güzel!

Peki bu indirgeme işlemini yerleşik olarak bulunan reduce ile gerçekleştirse idik?

import operator

def reduce_json(data: dict, keys: List[Union[str, int]]):

    if len(keys) == 0:
        return
    try:
        return functools.reduce(operator.getitem, keys, data)
    except (KeyError, IndexError):
        return

Çok daha temiz ve okunaklı görünüyor değil mi?

KeyError ve IndexError durumlarının handle edilmesinin sebebi, reducing sırasında, data içerisinde olmayan bir keyin istenmesi ya da bir liste içerisindeki bulunamayan index sonucu üretilecek hatalarının önüne geçilmesi. Burada bir logging yapmak tabi ki daha iyi bir çözüm.

>>> reduce_json(developer_data, ["languages", -1, "name"])
'golang'
>>> reduce_json(developer_data, ["personal","address", "coordinates"])
{'longitude': '481148', 'latitude': '114811'}

That’s it!


İşlevsel bir JSON walker yazmak reduce yardımıyla bu kadar kolay.

Reduce hakkında daha fazla okumak isterseniz RealPython.com’ın güzel bir tutoriali bulunuyor.

Bu blog kapsamında yazılan kod ve testlere ulaşmak isterseniz.

İyi günler!

 

Tarih:Blog

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.