Obje olarak Class
metaclass’ları anlamadan önce, Pythondaki class’ı tamamıyla anlamamız gerekir. Ve Python için class’ların özel bir anlamı vardır. Python classları Smalltalk dilinden alınmıştır.
Çoğu dilde, sınıflar sadece bir nesnenin nasıl üretileceğini tarif eden kod parçalarıdır. Bu Python’da da kısmen doğrudur:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
Ancak sınıflar Python’da bundan daha da fazlasıdır. Sınıflar da nesnelerdir.
Evet, nesneler.
class
anahtar kelimesini kullandığınızda Python bunu yürütür ve bir nesne oluşturur. Aşağıdaki ifade
>>> class ObjectCreator(object):
... pass
...
ObjectCreator
isminde bir objeyi bellekte oluşturur.
Bu objenin (sınıf) kendisi de instance oluşturma yeteneğine sahiptir ve bu yüzden bir sınıftır.
Ama yine de bir nesnedir ve bu nedenle:
- değişkene atabilirsiniz
- kopyalayabilirsiniz
- özellikler ekleyebilirsiniz
- fonksiyona parametre olarak geçebilirsiniz
- vb.
>>> print(ObjectCreator) # bir class'i print edebilirsiniz. Çünkü kendisi de bir objedir
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # bir parametre olarak verebilirsiniz
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # özellikler ekleyebilirsiniz
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # bir değişkene atayabilirsiniz.
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
Dinamik olarak class oluşturma
Sınıflar nesneler olduğundan, herhangi bir nesne gibi oluşturabilirsiniz.
İlk olarak, class kullanarak bir sınıf oluşturabilirsiniz:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return class, instance değil
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # fonksiyon class döndürür, instance değil
<class '__main__.Foo'>
>>> print(MyClass()) # bu classı kullanarak bir obje oluşturabilirsiniz
<__main__.Foo object at 0x89c6d4c>
Ama bunu yapmak o kadar da dinamik değil, çünkü hala tüm sınıfı kendiniz yazmanız gerekir.
Sınıflar nesneler olduğundan, bir şey tarafından üretilmelidirler.
class anahtar kelimesini kullandığınızda, Python bu nesneyi otomatik olarak oluşturur. Ama Python’daki bir çok şeyde olduğu gibi, elle yapmak için bir yol açar.
type
fonksiyonunu hatırlıyor musunuz? Ne tür bir nesne olduğunu bilmenize izin veren işlevi:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
Aslında, type
‘ın tamamen farklı bir özelliği de bulunur. Eylem anında bir class da oluşturabilir type
class açıklamasını parametre olarak alır ve bir class döndürür.
(Biliyorum, aynı işlevin parametreye göre tamamen farklı kullanımlara sahip olması biraz garip. Bu Python’da geriye doğru uyumluluk nedeniyle bir sorundur.)
type
şu şekilde çalışır:
type(name, bases, attrs)
Burada:
name
: class ismibases
: ebeveyn class’ın demeti (inheritance nedeniyle boş da olabilir)attrs
: isim ve değer özelliklerini içeren sözlük
örneğin:
>>> class MyShinyClass(object):
... pass
class’ı elle aşağıdaki gibi oluşturulabilir:
>>> MyShinyClass = type('MyShinyClass', (), {}) # bir class objesi döndürür
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # class için bir instance döndürür
<__main__.MyShinyClass object at 0x8997cec>
MyShinyClass
‘ı hem class ismi hem de class referansını tutacak bir değişken olarak kullandığımıza dikkat edin. Bunları farklı olarak da tanımlayabilirdik ama işleri karmaşık hale getirmeye gerek yok.
type
bir sözlük alarak class özelliklerini tanımlar:
>>> class Foo(object):
... bar = True
ifadesi aşağıdakine dönüştürülebilir:
>>> Foo = type('Foo', (), {'bar':True})
Ve normal bir class gibi kullanılabilir:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
Ve tabiki inherit edilebilir:
>>> class FooChild(Foo):
... pass
ifadesi aşağıdakine dönüştürülebilir:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar Foo'dan inherit edildi
True
Ayrıca, sınıfınıza yöntemler eklemek isteyeceksiniz. Bir işlev tanımlayın ve bunu bir özellik olarak atayın.
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
Ve normal olarak oluşturulan bir sınıf nesnesine yöntemler eklemek gibi, dinamik olarak sınıf oluşturduktan sonra daha fazla yöntem de ekleyebilirsiniz.
>>> def echo_bar_more(self):
... print('başka bir fonksiyon')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
Bununla nereye vardığımızı görüyorsunuz: Python’da, sınıflar nesnelerdir ve eylem anında dinamik olarak bir sınıf oluşturabilirsiniz.
class anahtar kelimesini kullandığınızda Python bunları yapar. Ve bunu metaclass kullanarak yapar.
metaclass nedir (sonunda)
Metaclasslar, sınıflar yaratan ‘şeyler’dir.
Nesneler oluşturmak için sınıf tanımlarız.
Ama Python sınıflarının da nesneler olduğunu öğrendik.
Aslında metaclasslar bu nesneleri oluştururlar. Bunlar class’ların class’larıdır. Bunları şu şekilde resmedebiliriz:
MyClass = MetaClass()
my_object = MyClass()
type
fonksiyonunun aşağıdakine benzer bir şey yaptığını öğrendik:
MyClass = type('MyClass', (), {})
Çünkü, type
fonksiyonu da aslında bir metaclass. type
Python tarafından arkaplanda tüm class’ların oluşturulmasını sağlayan bir metaclass’dır.
Şimdi merak ediyorsunuz değil mi? “type neden küçük harflerle yazıldı da Type olarak yazılmadı?”
Eh, bu durumun str ile bir tutarlılık oluşturmak için yapıldığını düşünebiliriz; dize nesneleri oluşturan str sınıfı, ve integer nesneleri oluşturan int sınıfı gibi. type
ise sadece sınıf nesneleri oluşturan sınıftır.
class özelliklerini kontrol ederek bunu görebiliriz.
Python’daki her şey ve her şey bir nesnedir. Bu integer, dizi, fonksiyon ve sınıfları içerir. Hepsi nesnelerdir. Ve hepsi bir sınıftan yaratılır:
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
Peki her hangi bir __class__
için __class__
nedir?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
Yani,
metaclasslar class nesneleri oluşturan şeylerdir.
İsterseniz class fabrikası olarak da isimlendirebilirsiniz.
type
Python’daki yerleşik metaclass’dır. Ancak kendi metaclasslarımızı da oluşturabiliriz.
__metaclass__
özelliği
Python 2’de, bir sınıf yazdığınızda __metaclass__ özelliği ekleyebilirsiniz (Python 3 syntaxı için sonraki bölüme bakınız):
class Foo(object):
__metaclass__ = bir şeyler...
[...]
Bunu yaparsanız, Python sınıf Foo’yu oluşturmak için metaclass kullanacaktır.
Dikkat edin, bu kısım biraz zorlu.
Birincisi, class Foo (object)
yazıyorsunuz, ancak Foo sınıfı nesnesi henüz bellekte oluşturulmaz.
Python, sınıf tanımında __metaclass__‘a bakacaktır. Bunu bulursa, Foo obje sınıfını oluşturmak için kullanacaktır. Bulamazsa, sınıf oluşturmak için type
kullanacaktır.
Bu yazdığımızı birkaç kez okuyun.
Aşağıdakini yaptığınızda:
class Foo(Bar):
pass
Python şunu yapar:
Foo için bir __metaclass__ özelliği var mı?
Eğer evet ise, Foo adı ile bir sınıf nesnesi oluşturmak (evet bir sınıf nesnesi) için metaclassı kullanır.
Python metaclass‘u bulamazsa, MODULE seviyesinde metaclass‘a bakacaktır ve aynı şeyi yapmayı dener.
Ardından, herhangi bir metaclass bulamazsa, sınıf nesnesini oluşturmak için Bar’ın (ilk ebeveyn) kendi metaclassını (varsayılan type olabilir) kullanacaktır.
metaclass özelliklerinin kalıtsal olmayacağına dikkat edin.
Şimdi büyük soru, __metaclass__ olarak ne kullanabiliriz?
Cevap: bir sınıf oluşturabilecek herhangi bir şeydir.
Ve bir sınıfı ne oluşturabilir? type veya bunu kullanan bir şey.
Python 3’te Metaclass
Metaclass’ı ayarlamak için syntax Python 3’te değiştirildi:
class Foo(object, metaclass=something):
...
__metaclass__ özelliği artık kullanılmaz.
Metaclass davranışı ancak büyük ölçüde aynı kaldı.
Python 3 için meta sınıflara eklenen yeni bir şey de meta sınıfına artık anahtar kelime tanımları olarak özellikleri geçebileceğinizdir:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
Aşağıdaki bölümü okuyarak Python’un bunu nasıl hallettiğine bakalım.
Özel metaclasses
Bir meta sınıfının ana amacı, oluşturulduğunda, sınıfı otomatik olarak değiştirmektir.
Genellikle mevcut bağlama uygun sınıflar oluşturmak istediğiniz zaman API’lere bunu yaparsınız.
Şu saçma örneği düşünelim: Modülünüzdeki tüm sınıflara ait özelliklerin büyük harflerle yazılması gerektiğine karar verdiğinizi düşünün. Bunu yapmak için birkaç yol bulunur, ancak bir yol da modül seviyesinde bir metaclass ayarlamaktır.
Bu şekilde, bu modülün tüm sınıfları bu metaclassı kullanılarak oluşturulacak. Tabiki tüm özellikleri büyük harfe çevirmek için metaclass’a bunu söylemek zorundayız.
Neyse ki, metaclass aslında herhangi çağrılabilir bir şey olabilir. İlla resmi şekilde tanımlı bir sınıf olması gerekmez.
Bu nedenle, bir işlevi kullanarak basit bir örnekle başlayacağız.
# type'a verdiğiniz aynı argümanlar metaclass'a da otomatik olarak geçirilir.
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
"""
Büyük harfe dönüştürülen özellikleri olan bir class objesi döndürür.
"""
# '__' ile başlamayan herhangi bir özellik seç ve büyük harfe dönüştür:
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
# class oluşturmayı `type` halletsin
return type(future_class_name, future_class_parents, uppercase_attrs)
__metaclass__ = upper_attr # bu modüldeki tüm classları etkiler
bar = 'bip'
Kontrol edelim:
>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'
Şimdi aynı şeyi metaclass için gerçek bir sınıf kullanarak yapalım:
# unutmayın ki `type` aslında `str` ve `int` gibi bir sınıf
# dolayısıyla inherit edilebilir
class UpperAttrMetaclass(type):
# __new__ fonksiyonu __init__ öncesinde çağırılır
# Bu metot bir obje oluşturur ve döndürür
# __init__ ise sadece objeyi bir parametre olarak başlatır
# __new__'i çok az sıklıkla kullanırız;
# Sadece bir objenin nasıl oluştuğunu kontrol etmek istediğimizde
# Burada oluşturulan obje bir class ve biz bunu özelleştirmek istiyoruz
# Dolayısıyla __new__'i override ediyoruz
# __init__ ile de benzer şeyler yapabiliriz
# ya da daha üst kullanımlar için __call__ ile
# Ancak bunu görmüyoruz:
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
return type(future_class_name, future_class_parents, uppercase_attrs)
Yukarıdakileri yeniden yazalım, ancak daha kısa ve daha gerçekçi değişken isimleri ile. Böylece, neyin ne anlama geldiğini anlayabilelim:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type(clsname, bases, uppercase_attrs)
Ekstra argüman cls’yi fark etmişsinizdir. Burada aslında özel bir şey yok: __new__, ilk parametre olarak her zaman tanımlandığı sınıfı alır. Sınıf yöntemleri için ilk parametre veya tanımlama sınıfı olarak self kullanımı gibi.
Ama bu kullanım uygun bir OOP değil. type‘ı doğrudan çağırıyoruz ve ebeveynin __new__ özelliğini override etmiyoruz veya çağırmıyoruz. Bunun yerine şunu yapalım:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type.__new__(cls, clsname, bases, uppercase_attrs)
super
kullanarak bunu daha temiz hale de getirebiliriz. super inheritance’ı kolaylaştırır (çünkü metaclasses inherit eden metaclass oluşturabiliriz, ki ilk metaclass da type’ı inherit eder.):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return super(UpperAttrMetaclass, cls).__new__(
cls, clsname, bases, uppercase_attrs)
Python 3’tee bu çağrıyı anahtar kelime argümanları ile yapabiliriz
class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
...
metaclass ile şuna çevirilir:
class MyMetaclass(type):
def __new__(cls, clsname, bases, dct, kwargs1=default):
...
İşte bu kadar. Aslında metaclass gerçekten bukadar.
Meta sınıfları kullanarak oluşturulan kodun karmaşıklığının arkasındaki neden metaclass’dan kaynaklanmaz. Metaclass’ları genellikle tanıma dayanan farklı şeyler yapmak için, inheritance’ı manipüle etmek, vb için kullanırız.
Gerçekten de, metaclasslar özellikle sihirli şeyler yapmak için yararlıdırlar ve bu nedenle karmaşık şeylerdir. Ama kendileri oldukça basittir:
- class oluşumunu yakalar
- classı modifiye eder
- modifiye edilmiş classı döndürür
Fonksiyonlar yerine neden metaclass kullanalım?
metaclass herhangi bir çağrılabiliri kabul edebilirse, neden açıkça daha karmaşık olduğu belli olan bir sınıf kullanalım?
Bunun birkaç nedeni vardır:
- Amaç açıktır. UpperAttrMetaclass(type)’ı okuduğunuzda, ne olduğunu bilirsiniz
- OOP kullanabilirsiniz. Metaclass metaclass inherit edebilir. Metaclasslar hatta metaclass kullanabilir.
- Bir sınıfın alt sınıfları, meta sınıfının belirttiği takdirde meta sınıfının instanceları olacaktır, ancak metaclass-fonksiyonu ile değil.
- Kodunuzu daha iyi hale getirebilirsiniz. Yukarıdaki örnekteki gibi basit bir şey için meta sınıfları kullanmayın. Genellikle karmaşık şeyler için kullanılır. Birkaç yöntem yapma ve bunları bir sınıfta gruplama yeteneğine sahip olmak, kod okumayı daha kolay hale getirmek için çok faydalıdır.
- new, init ve call‘da kullanabilirsiniz.
metaclass neden kullanılır?
Şimdi büyük soru. Neden bu hataya-yatkın özelliği kullanacaksınız?
Eh, genellikle kullanmazsınız:
Metaclasslar, kullanıcıların% 99’unun asla endişelenmemesi gerektiği derin özelliklerdir. Onlara ihtiyacınız olup olmadığını merak ediyorsanız, bunlara ihtiyacınız yok!
Python Guru Tim Peters
Bir metaclass için ana kullanım durumu bir API oluşturmaktır. Buna tipik bir örnek Django ORM’dir. Aşağıdaki gibi bir şeyi tanımlamanıza izin verir:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
Ancak şunu yaparsanız:
person = Person(name='bob', age='35')
print(person.age)
Bir IntegerField
objesi dödndürmez. Bir int
döndürür, ve bunu direkt olarak veritabanından çeker.
Bu şu şekilde mümkün olur: models.Model
bir __metaclass__
tanımlar ve bir takım sihir kullanlarak basit bir şeekilde tanımladığınız Person
‘ı kompleks bir veritabanı alanına döndürür.
Django, metaclass kullanarak karmaşık bir işlemi basit bir API’yi çağrısına dönüştürür.
Son söz
İlk olarak, sınıfların instance oluşturabilecek nesneler olduğunu biliyorsunuz.
Aslında, sınıflar kendileri de nesnelerdir. Metaclass nesneleri.
>>> class Foo(object): pass
>>> id(Foo)
142630324
Python’da her şey bir nesnedir ve tüm sınıflar bir sınıfın veya meta sınıfın objeleridir.
type hariç.
type kendi kendine bir metaclasstır. Bu Python’da kendi kendinize yeniden üretebileceğiniz bir şey değildir ve ancak uygulama seviyesinde biraz hile ile yapılır.
İkincisi, metaclasslar karmaşıktır. Çok basit sınıf değişiklikleri için bunları kullanmak istemezsiniz. Sınıfları iki farklı teknikle değiştirebilirsiniz:
- monkey patching
- sınıf dekoratörleri
Sınıf değişikliğine ihtiyacınız olan durumların % 99’u için bunları kullanmak daha iyidir.
Aslında, %98 oranında da sınıf değişikliklerine ihtiyaç duymazsınız.