Kolay gelsin.
Nesne Tabanlı Programlama (Devamı) — Yazbel Python Belgeleri burayı okuyarken @classmethod
'u fark ettim. Ama tam anlayamadım. Bu @classmethod
tam olarak nedir? Neden kullanırız?
Değerli bilgileriniz için şimdiden teşekkürler.
Kolay gelsin.
Nesne Tabanlı Programlama (Devamı) — Yazbel Python Belgeleri burayı okuyarken @classmethod
'u fark ettim. Ama tam anlayamadım. Bu @classmethod
tam olarak nedir? Neden kullanırız?
Değerli bilgileriniz için şimdiden teşekkürler.
class SampleClass:
class_attribute = []
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
self.instance_attribute = 0
def instanceMethod(self):
self.instance_attribute += 1
return self.instance_attribute
@classmethod
def classMethod(cls):
cls.class_attribute.append(5)
return cls.class_attribute
Bu class
'ı biraz kurcalarsanız olayı anlayabilirsiniz diye düşünüyorum. Gelin birlikte kurcalayalım.
Öncelikle SampleClass
isimli class
'ımızın class_attribute
adında bir niteliği olduğunu görüyoruz. Bu bir class attribute
'tur. Bu, şu demek oluyor, bir instance
'e ihtiyaç duymadan bu niteliğe erişebilirsiniz. Örnek:
class SampleClass:
class_attribute = []
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
self.instance_attribute = 0
def instanceMethod(self):
self.instance_attribute += 1
return self.instance_attribute
@classmethod
def classMethod(cls):
cls.class_attribute.append(5)
return cls.class_attribute
print(SampleClass.class_attribute) # Output: []
Gördüğünüz gibi elimizde bu class
'ın bir instance
'i olmadan class_attribute
'a erişebiliyoruz. Sebep? Çünkü bu bir class attribute
'u, bir class instance
'nin attribute
'u değil.
Mesela benzer şekilde SampleClass.instance_attribute
'a erişemezsiniz çünkü bunun için self
'e, yani bir instance
'e ihtiyacınız var: self
ile erişebildiğiniz şeylere ancak bir instance
'iniz varken erişebilirsiniz çünkü self
zaten instance
'in kendisidir.
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
self.instance_attribute = 0
Burada üç tane instance attribute
'u tanımlanmış, self
ile tanımlanmış olmaları dikkatinizi çeksin. Bu attribute
'lara ancak self
ile, yani bir instance
yardımı ile erişebilirsiniz.
Mesela
class SampleClass:
class_attribute = []
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
self.instance_attribute = 0
def instanceMethod(self):
self.instance_attribute += 1
return self.instance_attribute
@classmethod
def classMethod(cls):
cls.class_attribute.append(5)
return cls.class_attribute
# Works properly
instance = SampleClass("param1", "param2")
print(instance.param1)
# AttributeError: type object 'SampleClass' has no attribute 'param1'
print(SampleClass.param1)
def instanceMethod(self):
self.instance_attribute += 1
return self.instance_attribute
Burada bir metot tanımlıyoruz fakat self
parametresi alması, bir instance attribute
'a erişmesi dikkatinizi çeksin ki bu bir instance method
idir. Dolayısıyla bu metodu kullanabilmek için de bir objeye ihtiyaç var, bir instance
yaratılmalı.
class SampleClass:
class_attribute = []
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
self.instance_attribute = 0
def instanceMethod(self):
self.instance_attribute += 1
return self.instance_attribute
@classmethod
def classMethod(cls):
cls.class_attribute.append(5)
return cls.class_attribute
# Works properly
instance = SampleClass("param1", "param2")
print(instance.instanceMethod())
# TypeError: SampleClass.instanceMethod() missing 1 required positional argument: 'self'
print(SampleClass.instanceMethod()) # self parametresi bir argüman bekliyor
@classmethod
def classMethod(cls):
cls.class_attribute.append(5)
return cls.class_attribute
İşte burası. self
'in ve instance
'in üzerine bu kadar gitmemin ardından burada self
'in olmaması göze batıyor olmalı. Burada bir @classmethod
decorator function
'ının kullanılması, metodun self
yerine cls
parametresi alması, bir class_attribute
'una erişilmesi dikkatinizi çekmiştir ki bu metot bir class method
idir. Yani bu, şu demek, bu metot self
ile tanımlanan niteliklere erişemez çünkü bunların bir instance
'e ihtiyacı vardır fakat class attribute
'lara erişebilir çünkü bunların bir instance
'e ihtiyacı yoktur.
class SampleClass:
class_attribute = []
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
self.instance_attribute = 0
def instanceMethod(self):
self.instance_attribute += 1
return self.instance_attribute
@classmethod
def classMethod(cls):
cls.class_attribute.append(5)
return cls.class_attribute
print(SampleClass.classMethod()) # Output: [5]
Yani bir class
yapısında
instance
'e ihtiyaç duymadan erişebileceğiniz bu yerler birbiriyle yakından ilişkilidir.
Umarım anlatabilmişimdir. Size tavsiyem aynı kod örneği üzerinden kurcalama yapmaya devam etmeniz. Ne durumda hangi niteliğe nasıl ve ne zaman erişebiliyorum vb. sorular sorarak ihtiyacınız olan kısımları print
ederek kendi sorularınıza cevap bulabilirsiniz.
Teşekkür ederim <3 Kolay gelsin.
Python’da bir sınıfta bir metot yazmak üzereyken 3 seçeneğiniz vardır:
type(self)
'tir) erişebilirSınıfın içerisinde 1-girintiyle yazdığınız alelade her metot düz metottur. Nesne tabanlı programlamanın sunduğu, geniş çerçevede bahsedilen "nitelik ve metotlar"daki metotlar bunlardır. Madem hem örneğe hem de sınıfa erişilebiliyor bunlar üzerinden, diğer ikisinin yaptığı şeyleri de bu düz metotları kullanarak gerçekleştiremez miyiz? Gerçekleştirebiliriz evet. Ama niyeti ortaya koyma, okunabilirlik, performans gibi nedenlerden dolayı bunu yapmamalıyız, zaten o ikisinin varoluş amaçları da bu kaygıları taşımaktadır.
Peki ilk soru şu: nasıl oluyor da bir sınıf metodu bir anda örneğe erişemez oluyor? İşte o, sizin bahsettiğiniz classmethod
fonksiyonu (aslında sınıf ama olsun) ile metot dekore edilerek (@) sağlanıyor. Benzer durum staticmethod
için de geçerlidir.
Asıl soru: durup dururken neden sınıf metodu yazıyoruz? Sınıf metodları yaygın olarak alternatif constructor görevi görürler, “factory” fonksiyonlarıdırlar. Mesela şöyle bir sınıf olsun:
class Ulke:
def __init__(self, isim, nufus, iller):
self.isim = isim
self.nufus = nufus
self.iller = iller
def il_basina_nufus(self):
return self.nufus / len(self.iller)
Bir iller
listesi alıyoruz, bir isim
string’i ve nufus
tamsayısı ile nesneyi oluşturuyoruz. Normal durumlar, normal metotlar. Bu sınıfı piyasa sürdükten sonra, fazla sayıda ile sahip olan ülkelerin oluşturulmasında, uzun bir iller
listesinin paslanmasının pratik olmadığı tarafınıza bildiriliyor. Diyorlar ki, ya bizim ülkenin listesini bir dosyada tutuyorum da satır satır, senin sınıfa onu göndersem de oradan illeri çekse olmaz mı?
Siz de tamam diyorsunuz ve düşünüyorsunuz. Hmm… __init__
'e bir argüman daha ekleyeyim, illerin_dosya_yolu
, opsiyonel olsun; onu pasladılarsa oradan okurum. Tabii bu durumda iller
'i de opsiyonel yapmam gerekir çünkü illerin_dosya_yolu
nu veren iller
'i paslamayacaktır. Ha tabii şimdi bir de ikisini de (veya hiçbirini!) paslayanları uyarmak gerekir… :d E yapalım bari:
class Ulke:
def __init__(self, isim, nufus, iller=None, illerin_dosya_yolu=None):
# validasyon...
if iller is not None and illerin_dosya_yolu is not None:
raise ValueError("hem `iller` hem de `dosya_yolu` mu? Yapmayın...")
if iller is None and illerin_dosya_yolu is None:
raise ValueError("ne `iller` ne de `dosya_yolu` mu? Yapmayın...")
# illeri dosyadan okuyoruz eğer dosya yolu verildiyse
if illerin_dosya_yolu is not None:
with open(illerin_dosya_yolu) as fh:
iller = [*map(str.strip, fh)]
self.isim = isim
self.nufus = nufus
self.iller = iller
def il_basina_nufus(self):
return self.nufus / len(self.iller)
Yani…Gideri var gibi ama daha iyisini yapabiliriz. İşte sınıf metodları burada imdadımıza koşuyor bu alternatif constructor olmalarıyla; bir factory fonksiyonu olarak kullanacağız. Gelenek de bu metotların ismini from
ile başlatmaktır. Yani “şuradan bir nesne oluştur”. Bizim örnekte adını from_file
koyabiliriz. Türkçe isterseniz dosyadan
olabilir:
class Ulke:
# eski haline geri döndü
def __init__(self, isim, nufus, iller):
self.isim = isim
self.nufus = nufus
self.iller = iller
# bu zaten düz metot geldi düz metot gidiyor
def il_basina_nufus(self):
return self.nufus / len(self.iller)
# bu yeni! alternatif constructor.
@classmethod
def dosyadan(cls, isim, nufus, illerin_dosya_yolu):
# dosyadan illeri alıverelim
with open(illerin_dosya_yolu) as fh:
iller = [*map(str.strip, fh)]
# şimdi "asıl" constructor'u çağrırız `cls` üzerinden
return cls(isim, nufus, iller)
Kod sadeleşti, ayrı olması gereken şeyler ayrıldı, hakimiyet daha da kolay hale geldi, ekstra validasyonlara, acaba hangisini paslamışlar if’lerine gerek kalmadı. Artık kullanıcılar 2 şekilde Ulke
nesneleri oluşturma özgürlüğündeler:
# Direkt liste paslıyoruz `iller` için
# ve `Ulke(...)` diye ilklendiriyoruz
monaco = Ulke(isim="Monaco",
nufus=39_244,
iller=["Fontvieille", "Monaco-Ville", "La Condamine", "Monte Carlo"])
# Dosya yolu veriyoruz `iller` için
# ve `Ulke.dosyadan(...)` diye ilklendiriyoruz
fas = Ulke.dosyadan(isim="Fas",
nufus=37_112_080,
illerin_dosya_yolu="fas_in_illeri.txt")
Buna bir örneği de Python’daki dict
sınıfında görürüz. dict.fromkeys
metodu vardır; elinizde şimdilik sadece anahtarlar varsa (veya tüm anahtarları aynı değere göndermek istiyorsanız) bu alternatif sözlük oluşturan (fabrika) sınıf metodunu kullanırsınız dict(...)
/ {...}
yerine.
Son olarak da sınıf metodunun ilk parametresi olan, sınıfı temsil eden cls
'ye değinelim. İlk parametreyi artık self
yerine cls
diye adlandırıyoruz (artık self
'e yani örneğe erişimimiz yok (ihtiyacımız da yok), gelenek gereği de cls
diyoruz). Peki ona ne gerek var? Yukarıda return Ulke(isim, nufus, iller)
yazsaydık return cls(isim, nufus, iller)
yerine olmaz mıydı? Olurdu evet, çalışırdı. cls(...)
şeklinde çağırmak inheritance’a saygı duymak içindir. Olur da Ulke
'nin bir alt sınıfı (Ozerk_Ulke
gibi) da bu sınıf metodundan nemalanmak isterse, mesela Ozerk_Ulke.dosyadan(...)
şeklinde, eğer siz return Ulke(isim, nufus, iller)
yazdıysanız, geriye düz Ulke
nesnesi dönecektir! Ama istenen Ozerk_Ulke
sınıfından bir nesne dönmesidir; cls
işte bu işe yarar, yaptığımız o çağrıda o noktada cls == Ozerk_Ulke
olacaktır ve Ozerk_Ulke
nesnesi elde ederiz geriye. (Yan not: inheritance girdiğinde işin içine, **kwargs
eklemek gerekir sınıf metoduna ki alt sınıfın üst sınıfta olmayan niteliklerinin (mesela burada bagli_olunan_ulke
niteliği olabilir) ilklendirilmesine ket vurulmasın; sonra cls(..., **kwargs)
denilir.)
Statik metot da ne örneğe ne sınıfa ihtiyaç duyuyordu, onun da kullanım alanları vardır daha az sık olsa da; sınıf metodu, statik metot ve sınıflarla ilgili bazı diğer bilgiler adına şu videoyu izleyebilirsiniz.
Proje klasorumu kurcaladim, guzel ornek cikar mi diye (find ~/proj -name "*.py" -not -path "*venv*" -exec grep -HA5 classmethod '{}' \;
):
class Config(object):
@classmethod
def from_config(cls, config):
qc = cls()
qc.hosts = list(map(lambda host_port: (host_port[0], int(host_port[1])), map(lambda h: h.split(':'), config.get('hosts').split())))
qc.username = config.get('username')
qc.password = config.get('password')
qc.vhost = config.get('vhost')
[...]
def __init__(self):
self.hosts = []
self.username = None
self.password = None
self.vhost = '/'
[...]
staticmethod
daha az, cunku neden duz fonksiyon yazmak varken sinifin icine koyayim?
Sinifa gobekten bagli ama instance kullanmiyor, veya bulundugu yerin modul yerine sinif ici olmasi daha mantikliysa: