TEKNİK YAZI / Developer Tools ve Açık Kaynak

Python Tuple: Değişmezlik, Hashable Anahtarlar ve namedtuple

Python tuple yapısı: listeden farkı, sığ değişmezlik, sözlük anahtarı olma koşulu, açma (unpacking), namedtuple ve typing.NamedTuple örnekleri.

Python tuple, sıralı fakat öğe atamasıyla değiştirilemeyen bir koleksiyondur. Sabit yapılı kayıtlar, çoklu dönüş değerleri ve hashable bileşenlerden oluşan sözlük anahtarları için kullanışlıdır; değişmezlik gerekmeyen dinamik koleksiyonlarda list daha açık bir seçimdir.

Aşağıda tuple’ın listeden farkı, değişmezliğin sınırı, sözlük anahtarı olabilmenin koşulu, açma (unpacking) ve isimli alanlar sunan namedtuple ile typing.NamedTuple örnekleri yer alıyor.

Tuple Nedir, Listeden Farkı Ne?

Tuple, Python’da sıralı (ordered) ve değişmez (immutable) bir veri yapısıdır. Listeye benzer: indeksle erişilir, dilimlenir, üzerinde dolaşılır. Fark, oluşturulduktan sonra öğe eklenememesi, silinememesi ve bir konumdaki öğenin başka bir nesneyle değiştirilememesidir:

point = (3, 7)
point[0] = 5  # TypeError: 'tuple' object does not support item assignment

Bu değişmezlik sığdır (shallow). Tuple hangi nesnelere işaret ettiğini sabitler, ama işaret ettiği nesne değişebilir bir nesneyse o nesnenin içeriği yine değişebilir:

record = ("ayse", [1, 2])
record[1].append(3)
print(record)  # ('ayse', [1, 2, 3])

Bu nedenle tuple kullanmak, içindeki nesnelerin değişmeyeceğini ya da kodun iş parçacığı güvenli (thread-safe) olduğunu tek başına garanti etmez.

Neden Tuple Kullanmalı?

Niyeti belgeler: Alan sayısı ve sırası sabit olan veriyi (koordinat, RGB rengi, bir fonksiyonun birden çok dönüş değeri) tuple olarak tutmak, bu yapının değişmeyeceğini hem okuyana hem araçlara söyler. Fonksiyonel programlamadaki değişmez veri fikri de buna dayanır.

Sözlük anahtarı olabilir: Sözlük ve küme, anahtarı hash değerine göre yerleştiren bir hash table üzerine kuruludur; bu yüzden anahtarın hashable olması gerekir. Liste hashable değildir. Tuple ise yalnız bütün öğeleri hashable ise hashable’dır:

grid = {(0, 0): "başlangıç"}  # tamsayılardan oluşan tuple: geçerli anahtar
print(hash((1, "a", (2, 3))))  # bütün öğeler hashable: bir tamsayı yazdırır

hash(("user", [1, 2]))  # TypeError: unhashable type: 'list'

Aynı tuple’ı sözlük anahtarı olarak kullanmaya çalışmak da TypeError verir; hata mesajının ayrıntısı Python sürümüne göre değişir.

Bellek ve oluşturma maliyeti: CPython’da tuple, büyüyebilen listeye göre daha az ek kapasite taşır ve yalnız sabitlerden oluşan bir tuple ifadesi derleme sırasında tek bir sabit olarak saklanır. Gerçek fark Python sürümüne, veri boyutuna ve kullanım biçimine bağlıdır; karar vermeden önce kendi iş yükünüzde ölçün:

python3 -m timeit "(1, 2, 3, 4, 5)"
python3 -m timeit "[1, 2, 3, 4, 5]"

Tuple Oluşturma

# Parantezle oluşturma
coordinates = (10, 20)
rgb_color = (255, 128, 0)
empty_tuple = ()
single_element = (42,)  # tek elemanlı tuple için virgül şart; (42) yalnız bir sayıdır

# Parantezsiz oluşturma (tuple packing)
point = 10, 20, 30
name, age, city = "Ayşe", 25, "İstanbul"  # çoklu atama

# Liste veya başka bir yinelenebilir nesneden
from_list = tuple([1, 2, 3, 4])
from_string = tuple("hello")  # ('h', 'e', 'l', 'l', 'o')
from_range = tuple(range(5))  # (0, 1, 2, 3, 4)

# İç içe tuple
matrix = ((1, 2), (3, 4), (5, 6))

# İçinde dict olduğu için hashable değildir, sözlük anahtarı olamaz
complex_data = ("user", (2024, 11, 16), {"active": True})

Tuple Açma (Unpacking)

Tuple açma, sıralı değerleri ayrı değişkenlere dağıtır:

# Temel açma
point = (3, 7)
x, y = point  # x=3, y=7

# Fonksiyondan birden çok değer döndürme
def get_user_info():
    return "Ayşe", 30, "Mühendis"  # tuple döndürür

name, age, job = get_user_info()

# İki değişkenin değerini değiştirme
a, b = 10, 20
a, b = b, a  # a=20, b=10

# * ile genişletilmiş açma; middle her zaman liste olur
numbers = (1, 2, 3, 4, 5)
first, *middle, last = numbers  # first=1, middle=[2, 3, 4], last=5

# Kullanılmayacak değerler için _
data = ("important", "skip", "also_skip", "needed")
important, _, _, needed = data

namedtuple ve typing.NamedTuple

collections modülündeki namedtuple, tuple öğelerine isimle erişim ekler; oluşan nesne yine bir tuple’dır:

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(11, 22)
print(p.x, p.y)  # isimle erişim: 11 22
print(p[0], p[1])  # indeksle erişim de çalışır: 11 22

User = namedtuple("User", "id username email")
user = User(1, "ayse", "ayse@example.com")
print(user._replace(username="ayse.y"))  # değiştirilmiş yeni bir kopya döndürür

Tür ipucu (type hint) kullanan kodda typing.NamedTuple aynı yapıyı sınıf sözdizimiyle tanımlar; alan türleri ve varsayılan değerler okunaklı hâle gelir:

from typing import NamedTuple

class Point(NamedTuple):
    x: float
    y: float = 0.0

p = Point(3.0)
print(p)  # Point(x=3.0, y=0.0)
print(p._replace(y=4.0))  # Point(x=3.0, y=4.0)
p.x = 5.0  # AttributeError: alanlara atama yapılamaz

Tür ipuçları çalışma zamanında denetlenmez: Point("a") hata vermez, bu uyumsuzluğu mypy gibi bir tür denetleyicisi yakalar.

Pratik Kullanım Alanları

Fonksiyondan birden çok değer döndürme:

def calculate_statistics(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

minimum, maximum, average = calculate_statistics([1, 2, 3, 4, 5])

Sözlüklerde bileşik anahtar:

# Izgara koordinatlarını anahtar olarak kullanma
grid = {}
grid[(0, 0)] = "başlangıç"
grid[(1, 0)] = "doğu"
grid[(0, 1)] = "kuzey"

# Tarihe göre önbellek
cache = {}
cache[(2024, 11, 16)] = "o günün verisi"

enumerate ile indeks ve değer: Her adımda (indeks, değer) tuple’ı üretir.

fruits = ["apple", "banana", "orange"]
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

zip ile birden çok listeyi birlikte dolaşmak: Her adımda listelerin aynı konumdaki öğelerinden bir tuple üretir.

names = ["Ayşe", "Mehmet", "Zeynep"]
ages = [25, 30, 35]
cities = ["İstanbul", "Ankara", "İzmir"]

for name, age, city in zip(names, ages, cities):
    print(f"{name}: {age}, {city}")

Tuple mı, Liste mi?

Tuple kullanın:

Liste kullanın:

Sonuç

Tuple, verinin sırasını ve alan sayısını sabit bir sözleşme olarak ifade etmek istediğinizde iyi bir seçimdir. Koleksiyonun büyümesi veya öğelerinin değişmesi bekleniyorsa list; alan adları ve tür ipuçları gerekiyorsa typing.NamedTuple; değiştirilebilir alanlar ve daha zengin davranış gerekiyorsa dataclass daha okunaklıdır. Değişmezliğin sığ olduğunu ve performans farkının ancak gerçek iş yükünde ölçülerek değerlendirilebileceğini akılda tutun.

Birincil kaynaklar