Last chance! 50% off DataCamp Premium
Sale ends in
with deyimine, Python'da kaynakları ele almanın özlü bir yoluna, zaten aşina olabilirsiniz. Peki perde arkasında nasıl çalıştığını hiç merak ettiniz mi? with anahtar sözcüğünün gücü context manager'lardan gelir.
Context manager'lar, Python'da kaynak yönetimine yapılandırılmış bir yaklaşım sunan temel bir tasarım kalıbıdır. Kaynakların edinilmesini, doğru şekilde kullanılmasını ve hatalar ya da istisnalar olsa bile en sonunda serbest bırakılmasını veya temizlenmesini garanti ederler.
Bu, dosyalar, ağ bağlantıları veya veritabanı tanıtıcıları gibi kaynaklarla çalışırken özellikle önemlidir. Context manager kullanarak, bir dosyayı kapatmayı ya da bir kilidi serbest bırakmayı unutma endişesinden kurtularak daha temiz ve güvenilir kod yazabiliriz.
Bu eğitimde, Python'un varsayılan olarak sunduğu context manager'ların ötesine geçerek kendi context manager'larımızı yazmayı öğreneceğiz.
Perde arkasında, context manager'lar iki özel yöntemi tanımlayan nesnelerdir: __enter__ ve __exit__. __enter__ yöntemi, with bloğuna girdiğinizde çağrılır ve dönüş değeri bu blok içindeki bir değişkene atanır. Öte yandan __exit__ yöntemi, with bloğu normalde ya da bir istisnayla sona erse de çağrılır.
Bu yapı, doğru kaynak yönetimini sağlar. Bunu daha iyi anlamak için Python'daki bazı yerleşik context manager'lara bakalım:
Klasik bir dosya açma örneğini ele alalım:
with open('file.txt', 'r') as file:
data = file.read()
Burada, open('file.txt', 'r') bir context manager olarak davranır. with bloğuna girdiğinizde, dosya nesnesinin enter yöntemi çağrılır, dosya açılır ve file değişkenine atanır. Ardından dosya içeriğine erişmek için file.read() kullanabilirsiniz.
Sonrasında, dosya nesnesinin __exit__ yöntemi, with bloğu çıkarken bir istisna oluşsa bile mutlaka çağrılır. Bu yöntem, dosyayı kapatmakla ilgilenir; böylece açık dosya tanıtıcıları bırakmamış olursunuz.
2. İş Parçacığı Kilitleme
Dosyaların ötesinde, context manager'lar threading.Lock() kullanılarak iş parçacığı eşzamanlaması için de kullanılabilir:
import threading
lock = threading.Lock()
with lock:
# Critical section
print("This code is executed under lock protection.")
Burada, lock bir threading.Lock nesnesi, yani başka bir context manager'dır. with bloğuna girdiğinizde __enter__ yöntemi kilidi edinir, böylece aynı anda yalnızca bir iş parçacığı kritik bölümü çalıştırabilir. Son olarak, with bloğundan çıkarken __exit__ yöntemi kilidi serbest bırakır ve diğer iş parçacıklarının devam etmesine izin verir.
3. Veritabanı Bağlantıları
Benzer şekilde, context manager'lar veritabanı bağlantılarını da yönetebilir:
import sqlite3
with sqlite3.connect('database.db') as connection:
cursor = connection.cursor()
cursor.execute("SELECT * FROM table")
rows = cursor.fetchall()
sqlite3.connect('database.db') çağrısı bir context manager'dır. with bloğuna girildiğinde veritabanına bir bağlantı kurulur ve connection değişkenine atanır. Ardından veritabanıyla etkileşim kurmak için bir cursor kullanabilirsiniz. with bloğundan çıkarken __exit__ yöntemi bağlantının kapatılmasını garanti eder ve kaynak sızıntılarını önler.
4. Ağ Soketleri
Context manager'lar ağ iletişimini bile ele alabilir:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('localhost', 8080))
s.sendall(b'Hello, world')
data = s.recv(1024)
Burada, socket.socket(socket.AF_INET, socket.SOCK_STREAM) bir context manager gibi davranan bir soket nesnesi oluşturur. __enter__ yöntemi soketi oluşturur; with bloğu içinde bağlanabilir, veri gönderebilir ve alabilirsiniz. __exit__ yöntemi iş bittiğinde soketin düzgün şekilde kapatılmasını sağlar.
5. Dizin Taraması
Bu kez, os.scandir('.') dizin girdileri üzerinde yineleme yapmanın bir yolunu sağlar:
with os.scandir('.') as entries:
for entry in entries:
print(entry.name)
Burada os.scandir('.') context manager olarak davranır. __enter__ yöntemi bir dizin taraması açar ve with bloğu içinde girdiler üzerinde yineleme yapabilirsiniz. __exit__ yöntemi çıkışta dizin taramasını temizler.
Gördüğünüz gibi, context manager'ların gücüyle with deyimi, ayırma ve geri alma işlemlerini otomatikleştirerek kaynak yönetimini basitleştirir.
Şimdi, kaynak yönetimi üzerinde daha ince ayarlı denetim sağlamak için kendi özel manager'larınızı nasıl yazacağınıza bakalım.
Özel context manager yazmanın iki temel yaklaşımı vardır: sınıf tabanlı ve fonksiyon tabanlı.
Sınıf tabanlı yaklaşım, context manager yazmanın en yapılandırılmış ve esnek yöntemidir. Burada, __enter__ ve __exit__ özel yöntemlerini uygulayan bir sınıf tanımlarsınız.
Bir çalıştırma süresini ölçen Timer sınıfı örneğine bakalım:
import time
class Timer:
def __enter__(self):
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.end_time = time.time()
elapsed_time = self.end_time - self.start_time
print(f"Elapsed time: {elapsed_time} seconds")
# Example usage
if __name__ == "__main__":
with Timer() as timer:
# Code block to measure the execution time
time.sleep(2) # Simulate some time-consuming operation
Elapsed time: 2.002082347869873 seconds
Burada, Timer sınıfı, with bloğuna girildiğinde başlangıç zamanını yakalamak için __enter__ yöntemini tanımlar. Blok içinde nesneye erişim sağlamak üzere self döndürülür. __exit__ yöntemi, with bloğundan çıkarken geçen süreyi hesaplar ve yazdırır.
Daha kısa ama esneklikten ödün verebilecek bir yaklaşımı tercih ediyorsanız, fonksiyon tabanlı yöntem iyi bir seçenek olabilir. Burada, herhangi bir fonksiyonu bir context manager'a dönüştürmek için contextlib modülündeki contextmanager dekoratörünü kullanırsınız.
Bunun nasıl çalıştığına bakalım:
import time
from contextlib import contextmanager
@contextmanager
def timer():
start_time = time.time()
yield
end_time = time.time()
elapsed_time = end_time - start_time
print(f"Elapsed time: {elapsed_time} seconds")
# Example usage
if __name__ == "__main__":
with timer():
time.sleep(2)
Elapsed time: 2.0020740032196045 seconds
@contextmanager dekoratörü, timer fonksiyonunu bir context manager'a dönüştürür. Fonksiyon içinde start_time yakalanır ve yield ifadesi yürütmeyi durdurur; böylece with bloğu içindeki kod çalışır.
Son olarak, bitiş zamanı yakalanıp geçen süre yazdırılarak __exit__ işlevselliği sağlanır.
Özetle, __enter__ mantığını yield'dan önce, __exit__ mantığını ise sonrasına yazarsınız. Her iki yaklaşım da aynı sonucu elde eder; seçim yapınız yapı ve okunabilirlik tercihlerinize bağlıdır.
Artık Python'da özel context manager yazmayı anladığınıza göre, son derece faydalı olabilecek bazı pratik örneklere bakalım.
Dosya işlemleri birçok uygulamada yaygındır. Dosyalardan okuma ya da dosyalara yazma fark etmeksizin, doğru yönetim veri bütünlüğünü ve kaynak verimliliğini sağlar. Özel bir context manager kullanarak bir dosyaya metin yazmak istediğimiz bir senaryoyu düşünelim.
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
# Example usage
if __name__ == "__main__":
with FileManager("example.txt", "w") as file:
file.write("Hello, world!\n")
Bu örnekte, dosya işlemlerini ele alan Python'daki open işlevini taklit etmek için bir FileManager sınıfı tanımlıyoruz. Her yöntemin ne yaptığını parçalayalım:
__init__(self, filename, mode): Bu yöntem sınıfın kurucusudur. Açılacak dosyayı ve hangi kipte açılacağını (ör. okuma, yazma, birleştirme) belirten filename ve mode ile FileManager nesnesini başlatır.__enter__(self): Her zamanki gibi, with bloğuna girildiğinde çağrılır. filename ile belirtilen dosyayı, mode ile belirtilen kipte açar. Açılan dosya nesnesi self.file'a atanır ve with bloğu içinde kullanılmak üzere döndürülür.__exit__(self, exc_type, exc_value, traceback): Blok içinde bir istisna oluşup oluşmadığına bakılmaksızın, with bloğundan çıkarken çağrılır. self.file None değilse self.file.close() çağrılarak dosyanın düzgün şekilde kapatılmasını sağlar. Bu, kaynak sızıntılarını engeller ve doğru temizliği garanti eder.Yukarıdaki kodu çalıştırdıktan sonra example.txt içeriğini incelerseniz, yazılan metni görmelisiniz:
$ cat example.txt
Hello, world!
Elbette, FileManager sınıfının avantajı, dosyalarla çalışmak için istediğiniz özel mantığı uygulayabilmenizdir. Örneğin, açılan veya kapatılan dosyaların bir metin dosyasına kaydını tutacak şekilde sınıfı değiştirebilirsiniz.
import datetime
class FileManager:
def __init__(self, filename, mode, log_filename="file_log.txt"):
self.filename = filename
self.mode = mode
self.log_filename = log_filename
self.file = None
def log_action(self, action):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(self.log_filename, "a") as log_file:
log_file.write(f"{timestamp} - {action}: {self.filename}\n")
def __enter__(self):
self.file = open(self.filename, self.mode)
self.log_action("Opened")
return self.file
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
self.log_action("Closed")
# Example Usage
with FileManager("example.txt", "r") as file:
content = file.read()
print(content)
Hello, world!
Bu değiştirilmiş sürümde:
datetime modülü içe aktarılır.log_action yöntemi eklenir.log_filename parametresi eklenir. Varsayılan değer 'file_log.txt' olarak ayarlanır.__enter__ ve __exit__ yöntemleri içinde log_action kullanılarak dosya adı ve eylem (açıldı ya da kapatıldı) günlük dosyasına eklenir.log_action içinde, geçerli zaman damgasını "YYYY-MM-DD HH:MM:SS" biçiminde almak için datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") kullanılır.Şimdi, file_log.txt içeriğini yazdırırsak, günlükleri görmeliyiz:
$ cat file_log.txt
2024-04-03 12:37:02 - Opened: example.txt
2024-04-03 12:37:02 - Closed: example.txt
Özel bir context manager olarak uygulayabileceğimiz bir diğer pratik kullanım durumu da bir SQLite veritabanı yönetim aracıdır:
import sqlite3
class DatabaseConnection:
def __init__(self, database_name):
self.database_name = database_name
self.connection = None
def __enter__(self):
self.connection = sqlite3.connect(self.database_name)
return self.connection
def __exit__(self, exc_type, exc_value, traceback):
if self.connection:
self.connection.close()
Bu yine sqlite3.connect işlevini taklit eder, ancak özel veritabanı yönetim mantığı eklenerek daha da iyileştirilebilir. Yöneticinin çalışıp çalışmadığını bazı örnek işlevlerle kontrol edelim:
# Example usage
def create_table():
with DatabaseConnection("example.db") as connection:
cursor = connection.cursor()
cursor.execute(
"""CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
email TEXT)"""
)
def insert_data(username, email):
with DatabaseConnection("example.db") as connection:
cursor = connection.cursor()
cursor.execute(
"INSERT INTO users (username, email) VALUES (?, ?)", (username, email)
)
connection.commit()
def fetch_data():
with DatabaseConnection("example.db") as connection:
cursor = connection.cursor()
cursor.execute("SELECT * FROM users")
return cursor.fetchall()
if __name__ == "__main__":
create_table()
insert_data("john_doe", "john@example.com")
insert_data("jane_doe", "jane@example.com")
users = fetch_data()
print("Users in the database:")
for user in users:
print(user)
Users in the database:
(1, 'john_doe', 'john@example.com')
(2, 'jane_doe', 'jane@example.com')
(3, 'john_doe', 'john@example.com')
(4, 'jane_doe', 'jane@example.com')
Veritabanı işlemlerini işlevlere bölerek ve az önce oluşturduğumuz özel yöneticiyi kullanarak çok daha sürdürülebilir kod yazabiliriz.
Bu bölümde, Python'da context manager'larla ilgili ileri düzey konuları ele alacağız. Bunlar arasında hata yönetimi ve iç içe context manager kullanımı yer alır.
Hata yönetimi programlamanın temel bir yönüdür ve Python'daki context manager'lar, kaynak yönetimi işlemleri içinde hataları yönetmek için kullanışlı bir yol sunar.
FileManager sınıfı örneğinde, bir istisna oluşsa bile doğru temizliği sağlamak için __exit__ yönteminin içinde hata yönetimi uygulanabilir. Örneğin, dosyaya veri yazılırken (yani with bloğu içindeyken) bir hata oluşursa, kaynak sızıntılarını önlemek için dosyanın yine de kapatılmasını isteriz. __exit__ yöntemini şu şekilde değiştirebiliriz:
def __exit__(self, exc_type, exc_value, traceback):
if self.file:
self.file.close()
if exc_type is not None:
print(f"An error occurred: {exc_value}")
Burada, önce doğru temizliği sağlamak için self.file.close() ile dosyayı kapatıyoruz. Ardından bir istisna olup olmadığını kontrol ediyoruz (exc_type is not None). Eğer varsa, neyin yanlış gittiğine dair geri bildirim sağlamak için bir hata mesajı yazdırıyoruz.
Burada:
exc_type, AttributeError ve IndexError gibi istisna sınıfıdır.exc_value, mesajı içeren istisna örneğidir.traceback, hatanın tam iz bilgisidir.Şimdi, bilerek hatalı bir kodla FileManager sınıfını çalıştıralım:
with FileManager("example.txt", "r") as file:
# Simulate a sample error
content = file.read()
print(content[150])
An error occurred: string index out of range
Dolayısıyla, özel yöneticilerinizde istisnaları ele alırken özel mantık uygulamak için aşağıdaki örnek iş akışını takip edebilirsiniz:
def __exit__(self, exc_type, exc_value, traceback):
# Perform cleanup
...
# Handle the exception
if isinstance(exc_value, AttributeError):
# Handle AttributeError here
print(f"Exception message: {exc_value}")
# Handle another exception
elif isinstance(exc_value, AnotherError):
...
Context manager'ları iç içe kullanmak, tek bir with deyimi içinde, her biri kendi context manager'ına sahip birden fazla kaynağı aynı anda yönetmenizi sağlar. Bu, birden fazla kaynağı içeren karmaşık işlemlerle uğraşırken özellikle kullanışlıdır. Örneklerimizde context manager'ları nasıl iç içe kullanabileceğimize bakalım.
Örneğin, aynı bağlam içinde bir dosya açmak ve veritabanı işlemleri yapmak isteyebiliriz. Belki de bir CSV dosyasının içeriğini satırlar halinde bir veritabanına aktarmak istiyoruz. Bunu tek bir with deyimi içinde FileManager ve DatabaseConnection context manager'larını iç içe kullanarak başarabiliriz:
if __name__ == "__main__":
with FileManager("example.txt", "w") as file, DatabaseConnection("example.db") as connection:
...
Bu örnekte, FileManager ve DatabaseConnection context manager'ları aynı with deyimi içinde iç içe kullanılır. Bu sayede dosyayı açıp veritabanı bağlantısını aynı anda kurabiliriz. Takip eden dosya yazma ve veritabanı işlemleri bu birleşik bağlam içinde gerçekleştirilir.
Aklınızda bulundurmanız gereken bir nokta kodun okunabilirliğidir. Aynı anda kullanılan bağlam sayısının ikiyi geçmemesini öneririm.
Python'daki context manager'lar, bir with deyimi içinde kaynak yönetimine yapılandırılmış bir yaklaşım sunarak kaynakların doğru şekilde ayrılmasını ve geri alınmasını sağlar.
Bu eğitim boyunca context manager'ların temellerini öğrendik, pratik örneklerden geçtik ve hata yönetimi ile iç içe kullanım gibi ileri konuları tartıştık. Özel context manager yazmayı öğrenerek daha temiz ve güvenilir kod yazabilir, hata yönetimini iyileştirebilir ve kaynakları verimli şekilde yönetebilirsiniz.
Özel context manager yazma konusu “İleri Düzey Python” başlığının altında yer alır. Becerilerinizi daha da geliştirmek için bazı ilgili kaynaklar şunlardır:
2 yılı aşkın deneyime sahip bir veri bilimi içerik üreticisiyim ve Medium’da en büyük takipçi kitlelerinden birine sahibim. Yapay zekâ ve makine öğrenimi üzerine, biraz da alaycı bir üslupla yazılmış, ayrıntılı makaleler kaleme almayı seviyorum; çünkü onları biraz daha az sıkıcı hale getirmek için bir şeyler yapmak gerekiyor. 130’un üzerinde makale ve bir de DataCamp kursu ürettim; bir diğeri de hazırlık aşamasında. İçeriklerim 5 milyondan fazla kişi tarafından görüldü; bunların 20 bini Medium ve LinkedIn’de takipçim oldu.
Python Yolculuğunuza Bugün Devam Edin!
Program
Kurs
Kurs
blog
Dario Radečić
15 dk.

blog
Abid Ali Awan
14 dk.
Eğitim
Adel Nehme
Eğitim
Kurtis Pykes
8 dk.