Type hints in python

Type hints in python

Bu yazımızda Python'da kodunuzu daha anlaşılır hale getirmenin yollarından birisi olan Type Hints'den bahsediyoruz.
Arda Akdere21 Oca 2023

Python'da geliştirdiğiniz bir proje, içinden çıkılmaz bir hal mi aldı? Belki de artık hangi fonksiyonun hangi işlevi yerine getirdiğini ilk bakışta kestiremiyorsunuz. Hatta hangi değişkenin hangi tipte olduğunu, ilgili değişkenin fonksiyondaki konumunu gözlemlemeden anlayamıyorsunuz veya bunları daha en baştan yaşayacağınızı hissediyorsunuz ama almanız gereken aksiyonlardan emin değilsiniz ya da tüm bunları başkasından teslim aldığınız kod üzerinde yaşıyorsunuz. Python her ne kadar öğrenmesi, kodlaması, okuması kolay bir dil olsa da bu bahsettiğim senaryoların yaşanması her zaman olasıdır. Örneğin kodu yazan kişi; fonksiyon, metot veya değişkenlerin isimlerini, görevlerini anımsatmayacak bir şekilde belirlerse veya oluşturduğu fonksiyonları iyi organize edemeyip, her birine birbirinden bağımsız birden çok görev atarsa beklenen olur.
 

 

Örneğin elimizde şöyle bir kod olsun:

import random

# Generates a nickname by adding a number after the word
def generate_nickname(name):
    return name + str(random.randint(0, 100))

print(generate_nickname('jack'))
# jack47

 

generate_nickname fonksiyonu, ona sağladığımız string türündeki name argümanının sonuna 0'dan 100'e kadar olan sayılardan rastgele bir tanesini ekliyor ve bu değeri bize döndürüyor. Böylelikle, rastgele bir kullanıcı adı oluşturma işlevini yerine getiriyor. Fonksiyonun isminden (generate_nickname) ve argümanına verdiğimiz name isimlendirmesinden, fonksiyonun bize döndüreceği ve sağlamamız gereken veri tipini (str, int, bool..) tahmin edebiliyoruz. Bunu ne zaman ki bu şekilde çıkarımlara gerek kalmadan, programatik bir şekilde belirtmek istiyorsak type hints ile karşılaşırız. Şimdi aynı kodun aşağıdaki versiyonuna bakalım:

import random

# Generates a nickname by adding a number after the word
def generate_nickname(name: str) -> str:
    return name + str(random.randint(0, 100))

print(generate_nickname('jack'))
# jack47

 

Artık fonksiyonumuzun name argümanının str türünde olması gerektiğini, yanına yazdığımız str ifadesiyle belirtebiliyoruz. Aynı zamanda str ifadesi ile fonksiyonumuzun hangi tipte bir değişken döndürdüğünü de gösterebilmekteyiz. Bunu aynı şekilde diğer veri tipleri için de uygulayabiliriz.

import random

# Generates a nickname by adding a number after the word
def generate_nickname(name: str) -> str:
    return name + str(random.randint(0, 100))

print(generate_nickname('jack'))
# jack47

def multiply_by_three(x: float) -> float:
    return x * 3.0

def is_valid(min: int, max: int) -> bool:
    return min < 10 and max > 250

def print_nicknames(nickname_list: list) -> None:
    for nickname in nickname_list:
        print(nickname)

 

Fonksiyonumuzun hangi tipte bir veri aldığını daha fonksiyonumuzu yazarken belirttik. Belirtmeseydik, kod yine kendi içinde tutarlı bir davranış sergileyip gayet güzel bir şekilde çalışabilirdi. Fakat veri tiplerini belirterek gelecekte bu kod üzerinde değişiklik yapmak istediğimizde fonksiyonun aldığı argümanın veri tipini anlamaya ekstra bir zaman harcamayacağız. Aynı durum döndürdüğü veri tipi için de geçerli. Yukarıdaki print_nicknames fonksiyonuna bakarsak None yani bir şey döndürmediğini de belirttiğimizi görebiliriz.

Birden çok veri tipi sunduğumuz diğer bir senaryoya bakalım:

from typing import Union

def add(x: Union[int, float], y: Union[int, float]) -> Union[int, float]:
    return x + y

 

Python ile dahili (built-in) gelen typing kütüphanesinden Union özel fonksiyonunu koda dahil ettik. typing kütüphanesi, bize type hints konusunda oldukça yardımcı oluyor. Yukarıdaki kodda Union yapısını kullanarak, fonksiyonumuzun birden çok veri tipini argüman olarak kabul edebileceğini ve aynı şekilde döndüreceği değerlerin de int veya float tiplerinde olabileceğini belirttik.

Kodun daha temiz halini inceleyelim:

from typing import Union

number = Union[int, float]

def add(x: number, y: number) -> number:
    return x + y

# Alternatif olarak Python 3.10 sürümüyle beraber Union yerine aşağıdaki syntax kullanılabiliyor.
number = int | float

def add(x: number, y: number) -> int | float:
    return x + y

 

Şimdi callable ve iterable'a bakalım. Bu ikisinin kullanım mantığı olarak az öncekilerden hiçbir farkı yok. Sadece örnekleri çoğaltarak var olan özelliklerin farkında olunması iyi olacaktır.

from typing import Callable

IntFloatFunction = Callable[[int, int], float]

def multiply(x: int, y: int) -> float:
    return float(x * y)

func_var: IntFloatFunction = multiply

print(func_var(1, 2))
# 2.0

 

Callable’ı, fonksiyon tipinde olan değişkenleri belirtmek için kullanıyoruz.

Yukarıdaki örnekte callable'ı, func_var değişkeninin kabul edeceği fonksiyonun argüman tiplerini vurgulamak için kullandık. Bu durumda func_var değişkenimiz, eşitlendiği Callable[[int, int], float]'a bakılınca  2 adet int türünde argüman alabileceği ve float türünde bir değeri dönüş yapması gerektiğini görüyoruz.

Bir başka kullanımda ise aşağıdaki örnekte olduğu gibi Callable'ı  bir fonksiyonun, alacağı argümanın tipini belirtirken de görebilmekteyiz.

from typing import Callable

def multiply(x: int, y: int) -> float:
    return float(x * y)

def calculator(multiply_function: Callable[[int, int], float], add_function = None, diff_function = None) -> float:
    .
    ..
    ...
    ....

    return output

calculator(multiply_function=multiply)

 

Bir diğer type hint olan Iterable, üzerinde döngü (for, while kullanılarak) kurulabilen değişkenlerin tipini temsil etmektedir. Aşağıdaki kod bloğunda, collect adındaki fonksiyonun aldığı items adındaki argümanın Iterable türünde olması gerektiğini belirtmiş oluyoruz.

from typing import Callable, Iterable

def collect(items: Iterable[int]) -> int:
    total = 0
    for item in items:
        total += item
    
    return total

print(collect([1, 2, 3, 4, 5, 6, 7]))

 

Son olarak bir değişken oluştururken veri tiplerini nasıl belirtebileceğimizi örnekleyen aşağıdaki koda bakalım. Kod bloğunda sırasıyla, list, tuple, dict ve str veri tipinde olan değişkenler oluşturduk. Bu değişkenlerin hangi veri tipine ait olduklarını vurgulamak için Type Hint’leri, değişken isimlendirmelerinden sonra koyduğumuz iki nokta karakterinden hemen sonra belirttik.

numbers_list: list[int] = [1, 2, 3, 4, 5, 6]
numbers_tuple: tuple[int, int, str, int] = (42, 12, 'text', 8)
numbers_dict: dict[int, str] = {1: 'one', 2: 'two', 3: 'three', 4: 'four'}
text: str = 'name_surname'

 

Type hints kullanımının kodun çalışmasına hiçbir etkisi olmayacaktır. Python dinamik bir dil olduğu için, örneğin bir fonksiyonun alacağı argümanın veri tipinin int olarak belirlenmesi ona str türünde bir veri gönderilmesine engel olmayacaktır. Hatta RuntimeError ile de karşılaşılmayacaktır. O halde, gözümüzden kaçan bölümleri nasıl Debug edeceğiz? Burada da mypy kütüphanesi işin içine giriyor. pip install mypy şeklinde kütüphanemizi kurduktan sonra mypy kod_dosyanız.py şeklinde kodunuzu çalıştırdığınızda size, gözünüzden kaçırdığınız typing hatalarını listeleyecektir. Bundan sonra ise gözünüzden kaçan tip hatalarını kodu satır satır inceleyerek değil, doğrudan debug edebilirsiniz.

Aynı zamanda Visual Studio Code gibi IDE’ler, bizlere mypy gibi framework’leri doğrudan anlık kod yazımına entegre edebileceğimiz özellikler sunar. Yani debug sürecinizi en hızlı şekilde gerçekleştirebileceğimiz olanaklar da mevcut.

 

Type hints’in avantaj ve dezavantajları aşağıdaki şekilde sıralanabilir:

  • Kod büyüse de gözden kaçan hataların tespit edilmesini kolaylaştırır.
  • Sadece kendimiz için değil kodu okuyan diğer kişilerin de işini kolaylaştırmış olur.
  • Dokümantasyon hazırlığında faydası olur.
  • Koddaki hatalar geliştirme sürecinde daha hızlı tespit edilir.
  • Default olarak Runtime'a hiçbir etkisi olmaz. Böylelikle yararlarının yanı sıra deneysellikte zorluk yaratmaz.
  • Gereksiz her yerde kullanıldığında kodda karmaşaya, okunaklılığın azalmasına yol açabilir. Örneğin değişken tanımlarında kullanmak tercih edilmeyebilir.

Python’ın yüksek seviyeli bir dil olmasının yanında getirdiği dinamik semantiği, oluşturulan değişkenlerin tekrar tekrar farklı veri tiplerinde kullanılabilmesine imkan tanır. Böylece oluşturulan değişkenlerin belli bir veri tipine olan bağımlılığı ortadan kalkar Bu özellik, Python’ı Python yapan basit söz dizimine katkıda bulunsa da büyüyen projelerde karmaşıklığa neden olabilmektedir. Yazımızda ele aldığımız type hints kavramı, oluşturulan değişkenlerin veri tiplerini belirtmemize olanak tanıyor. Bu şekilde kod üzerindeki hakimiyetimizi yazıda ele aldığımız kapsamda kaybetmemiş oluruz. Bu tamamen tercihimize kalmış olup Python’da belirtmediğimiz bir veri tipi için herhangi bir sorun ile karşılaşmayız.
 

Python’da yer alan temel veri tiplerine, Miuul Not Defteri’nden Python’ın ilk günleri, değişkenleri ve veri tipleri başlıklı yazıdan erişebilirsiniz.
 

Kaynaklar

Miuul topluluğunun bir parçası ol!

Abone ol butonuna tıklayarak Miuul'dan pazarlama ve haber içerikleri almayı onaylıyorum.