9. Sınıflar

Sınıflar, verileri ve işlevleri bir arada tutan bir araçtır. Yeni bir sınıf oluşturulurken, objeye ait yeni örnekler (instances) oluşturulur. Sınıf örnekleri, durumlarını korumak için onlara iliştirilmiş niteliklere sahip olabilir. Sınıfların durumunu modifiye etmek için ise metotlar kullanılabilir.

Diğer programlama dilleriyle karşılaştırıldığında, Python’un sınıf mekanizması olabildiğince az yeni sözdizimi ve semantik içeren sınıflar ekler. C++ ve Modula-3’te bulunan sınıf mekanizmalarının bir karışımıdır. Python sınıfları Nesne Yönelimli Programlama’nın tüm standart özelliklerini sağlar: sınıf devralma mekanizması birden çok temel sınıfa izin verir, türetilmiş bir sınıf temel sınıfının veya sınıflarının herhangi bir metodunu geçersiz kılabilir ve bir metot aynı ada sahip bir temel sınıfın metodunu çağırabilir. Nesneler farklı miktar ve türlerde veri içerebilir. Modüller için olduğu gibi, sınıflar da Python’un dinamik doğasına uygundur: çalışma sırasında oluşturulurlar ve oluşturulduktan sonra da değiştirilebilirler.

C++ terminolojisinde, normalde sınıf üyeleri (veri üyeleri dahil) public (aşağıdakine bakın Özel Değişkenler) ve tüm üye fonksiyonları virtual’ dır. Modula-3’te olduğu gibi, nesnenin üyelerine metotlarından ulaşmak için bir kısayol yoktur: metodun işlevi, çağrı tarafından örtülü olarak sağlanan objeyi temsil eden açık bir argümanla bildirilir. Smalltalk’ta olduğu gibi, sınıfların kendileri de birer nesnedir. Bu sayede metotlar yeniden isimlendirilebilir veya içe aktarılabilir. C++ ve Modula-3’ün aksine, yerleşik türler kullanıcının üzerlerine yapacağı geliştirmeler için temel sınıflar olarak kullanılabilir. Ayrıca, C++’ta olduğu gibi, özel sözdizimine sahip çoğu yerleşik işleç (aritmetik işleçler, alt simgeleme vb.) sınıf örnekleri için yeniden tanımlanabilir, geliştirilebilir.

(Sınıflara dair evrensel terimlerin yanı sıra, nadiren Smalltalk ve C++ terimlerini kullanacağım. Onun yerine Modula-3 terimlerini kullanacağım çünkü Python’un nesne yönelimli semantiğine C++’tan daha yakın, yine de ancak çok az okuyucunun bunları duymuş olacağını tahmin ediyorum.)

9.1. İsim ve Nesneler Hakkında Birkaç Şey

Nesnelerin bireyselliği vardır ve birden çok ad (birden çok kapsamda) aynı nesneye bağlanabilir. Bu, diğer dillerde örtüşme (aliasing) olarak bilinir. Bu genellikle Python’a ilk bakışta takdir edilmeyen bir özellik olsa da değiştirilemeyen veri türleriyle (sayılar, dizeler, diziler) uğraşırken rahatlıkla göz ardı edilebilir. Ancak, örtüşmeler, listeler, sözlükler ve diğer türlerin çoğu gibi değişebilir nesneleri içeren Python kodunun semantiği üzerinde şaşırtıcı bir etkiye sahiptir. Diğer adlar bazı açılardan işaretçiler (pointers) gibi davrandığından, bu genellikle programın yararına kullanılır. Örneğin, bir nesneyi geçirmek kolaydır çünkü uygulama tarafından geçirilen şey yalnızca bir işaretçidir; bir fonksiyon argüman olarak geçirilen bir nesneyi değiştirirse, çağıran bu değişikliği görür — bu Pascal’daki iki farklı bağımsız değişken geçirme mekanizmasına olan gereksinimi ortadan kaldırır.

9.2. Python Etki Alanları ve Ad Alanları

Sınıflara başlamadan önce, size Python’un etki alanı kuralları hakkında bir şey söylemeliyim. Sınıf tanımlarında ad alanlarının kullanımının bazı püf noktaları vardır ve neler olduğunu tam olarak anlamak için etki alanlarının ve isimlerin nasıl çalıştığını bilmeniz gerekir. Bu arada, bu konuda bilgi edinmek herhangi bir ileri düzey Python programcısı için yararlıdır.

Haydi birkaç tanımlama ile başlayalım.

Ad alanları (namespace), adlardan nesnelere eşlemedir. Çoğu isim şu anda Python sözlükleri olarak uygulanmaktadır, ancak bu fark edilir çapta bir fark yaratmaz (performans hariç) ve gelecekte değişebilir. Ad alanlarına örnek olarak şunlar verilebilir: yerleşik isimler (abs() ve yerleşik özel durum adları gibi fonksiyonları içerir); modüldeki global adlar; ve bir fonksiyon çağırmadaki yerel adlar. Bir bakıma, bir nesnenin nitelik kümesi de bir ad alanı oluşturur. İsimler hakkında bilinmesi gereken önemli şey, farklı ad alanlarındaki adlar arasında kesinlikle bir ilişki olmamasıdır; örneğin, iki farklı modülün her ikisi de karışıklık olmadan maximize fonksiyonunu tanımlayabilir — modül kullanıcılarının modül adını örneklemesi gerekir.

Bu arada, nitelik sözcüğünü bir noktayı takip eden herhangi bir isim için kullanıyorum. Mesela, z.real ifadesinde real, z nesnesine ait bir niteliktir. Açıkçası, modüllerde isimlere yapılan referanslar nitelik referanslarıdır: modname.funcname ifadesinde modname bir modül nesnesi ve funcname onun bir niteliğidir. Bu durumda, modülün nitelikleri ve modüldeki global değişkenler arasında bir eşleşme olur: aynı ad alanını paylaşmaları! 1

Nitelikler salt okunur veya düzenlenebilir olabilir. İkinci durumda, niteliklere atama yapmak mümkündür. Modül nitelikleri düzenlenebilir: modname.the_answer = 42 yazabilirsiniz. Düzenlenebilir nitelikler del ifadesiyle de silinebilir. Örneğin, del modname.the_answer ifadesi the_answer niteliğini modname tarafından adlandırılan nesneden kaldırır.

Ad alanları farklı anlarda oluşturulur ve farklı yaşam sürelerine sahiptir. Yerleşik adları içeren ad alanı, Python yorumlayıcısı başlatıldığında oluşturulur ve hiçbir zaman silinmez. Modül tanımı okunduğunda modül için genel ad alanı oluşturulur; normalde, modül ad alanları da yorumlayıcı çıkıp bitene kadar sürer. Bir komut dosyasından veya etkileşimli olarak okunan yorumlayıcının en üst düzey çağrısı tarafından yürütülen ifadeler __main__ adlı bir modülün parçası olarak kabul edilir, bu nedenle kendi genel ad alanlarına sahiptirler. (Yerleşik isimler aslında bir modülde de yaşar; buna builtins.)

Bir işlevin yerel ad alanı, bir fonksiyon çağrıldığında oluşturulur ve fonksiyon başa çıkamadığı bir hata veya istisna ile karşılaştığında silinir. (Aslında, bir bakıma ad alanı unutulmaktadır diyebiliriz.) Tabii ki, özyinelemeli çağrıların her birinin kendi yerel ad alanı vardır.

scope, bir ad alanının doğrudan erişilebilir olduğu Python programının metinsel bölgesidir. Burada “Doğrudan erişilebilir”, niteliksiz bir başvurunun hedeflediği ismi ad alanında bulmaya çalıştığı anlamına gelir.

Kapsamlar statik olarak belirlense de, dinamik olarak kullanılırlar. Yürütme sırasında herhangi bir zamanda, ad alanlarına doğrudan erişilebilen 3 veya 4 iç içe kapsam vardır:

  • En içte bulunan kapsam, ilk aranan olmakla birlikte yerel isimleri içerir.

  • the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contains non-local, but also non-global names

  • Sondan bir önceki kapsam, geçerli modülün genel adlarını içerir

  • En dıştaki kapsam (en son aranan), yerleşik adlar içeren ad alanıdır

If a name is declared global, then all references and assignments go directly to the middle scope containing the module’s global names. To rebind variables found outside of the innermost scope, the nonlocal statement can be used; if not declared nonlocal, those variables are read-only (an attempt to write to such a variable will simply create a new local variable in the innermost scope, leaving the identically named outer variable unchanged).

Genellikle, yerel kapsam (metinsel olarak) geçerli fonksiyonun yerel adlarına başvurur. Dış fonksiyonlarda, yerel kapsam genel kapsamla aynı ad alanına başvurur: modülün ad alanı. Sınıf tanımları yerel kapsamda başka bir ad alanı yerleştirir.

Kapsamların metinsel olarak belirlendiğini fark etmek önemlidir: bir modülde tanımlanan bir işlevin genel kapsamı, işlevin nereden veya hangi diğer adla adlandırıldığından bağımsız olarak modülün ad alanıdır. Öte yandan, gerçek ad araması dinamik olarak yapılır, çalışma zamanında — ancak, dil tanımı statik ad çözümlemelerine doğru, “derleme” zamanında gelişir, bu nedenle dinamik ad çözümlemesini güvenmeyin! (Aslında, yerel değişkenler zaten statik olarak belirlenir.)

Python’un özel bir cilvesi, eğer global veya nonlocal deyimi geçerli değilse – isimlere yapılan atamaların her zaman en içteki kapsama girmesidir. Atamalar verileri kopyalamaz — adları nesnelere bağlarlar. Aynı şey silme için de geçerlidir: del x deyimi, x bağlamasını yerel kapsam tarafından başvurulan ad alanından kaldırır. Aslında, yeni adlar tanıtan tüm işlemler yerel kapsamı kullanır: özellikle, import ifadeleri ve işlev tanımları modül veya işlev adını yerel kapsamda bağlar.

global deyimi, belirli değişkenlerin genel kapsamda yaşadığını ve orada geri alınması gerektiğini belirtmek için kullanılabilir; nonlocal deyimi, belirli değişkenlerin bir çevreleme kapsamında yaşadığını ve orada geri alınması gerektiğini gösterir.

9.2.1. Kapsamlar ve Ad Alanları Örneği

Bu, farklı kapsamlara ve ad alanlarına başvurmayı ve global ve nonlocal değişken bağlamasını nasıl etkileyeceğini gösteren bir örnektir:

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

Örnek kodun çıktısı şudur:

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

Varsayılan atama olan local atamasının scope_test’in spam bağlamasını nasıl değiştirmediğini unutmayın. nonlocal ataması scope_test’in spam bağlamasını değiştirdi, hatta global ataması modül düzeyindeki bağlamasını değiştirdi.

Ayrıca global atamasından önce spam için herhangi bir bağlama olmadığını görebilirsiniz.

9.3. Sınıflara İlk Bakış

Sınıflar biraz yeni söz dizimi, üç yeni nesne türü ve bazı yeni semantiklere sahiptir.

9.3.1. Sınıf Tanımlama Söz Dizimi

Sınıf tanımlamasının en basit biçimi şöyledir:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

Sınıf tanımlamaları, fonksiyon tanımlamaları (def deyimleri) ile aynı şekilde, herhangi bir etkiye sahip olmadan önce yürütülmelidir. (Bir sınıf tanımını if ifadesinin bir dalına veya bir fonksiyonun içine yerleştirebilirsiniz.)

Uygulamada, bir sınıf tanımı içindeki ifadeler genellikle fonksiyon tanımlamaları olacaktır, ancak diğer ifadelere de izin verilir ve daha sonra bahsedeceğimiz üzere bazen yararlılardır. Bir sınıfın içindeki fonksiyon tanımları normalde, yöntemler için çağırma kuralları tarafından dikte edilen tuhaf bir bağımsız değişken listesi biçimine sahiptir — bu daha sonra açıklanacak.

Sınıf tanımı girildiğinde, yeni bir ad alanı oluşturulur ve yerel kapsam olarak kullanılır — böylece yerel değişkenlere yapılan tüm atamalar bu yeni ad alanına gider. Özellikle, fonksiyon tanımlamaları yeni fonksiyonların adını buraya bağlar.

Bir sınıf tanımı normal olarak bırakıldığında (uç aracılığıyla), bir sınıf nesnesi oluşturulur. Bu temelde sınıf tanımı tarafından oluşturulan ad alanının içerikleri için bir aracıdır, sınıf nesneleri hakkında daha fazla bilgiyi ise sonraki bölümde öğreneceğiz. Orijinal yerel kapsam (sınıf tanımı girilmeden hemen önce geçerli olan) yeniden etkinleştirilir ve sınıf nesnesi burada sınıf tanımı üstbilgisinde verilen sınıf adına bağlı olur (örnekte ClassName).

9.3.2. Sınıf Nesneleri

Sınıf nesneleri iki tür işlemi destekler: nitelik referansları ve örnekleme.

Nitelik referansları, Python’daki tüm nitelik referansları için kullanılan standart söz dizimi olan obj.name kullanır. Geçerli nitelik adları, sınıf nesnesi oluşturulduğunda sınıfın ad alanında bulunan tüm adlardır. Yani, sınıf tanımı şöyle görünüyorsa:

class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'

bu durumda MyClass.i ve MyClass.f geçerli nitelik referanslarıdır ve sırasıyla bir tamsayı ve fonksiyon nesnesi döndürür. Sınıf nitelikleri de atanabilir, böylece atamayla MyClass.i değerini değiştirebilirsiniz. Aynı zamanda __doc__, geçerli bir nitelik olarak sınıfa ait docstring döndürür: "Basit bir örnek sınıf".

Sınıf örnekleme fonksiyon notasyonunu kullanır. Sınıf nesnesinin, sınıfın yeni bir örneğini döndüren parametresiz bir fonksiyon olduğunu varsayın. Örneğin (yukarıdaki sınıfı varsayarsak):

x = MyClass()

sınıfın yeni bir örnek öğesini oluşturur ve bu nesneyi x yerel değişkenine atar.

Örnekleme işlemi (“sınıf nesnesi çağırma”) boş bir nesne oluşturur. Birçok sınıf, belirli bir başlangıç durumuna göre özelleştirilmiş örneklerle nesneler oluşturmayı sever. Bu nedenle bir sınıf, __init__() adlı özel bir metot tanımlayabilir:

def __init__(self):
    self.data = []

When a class defines an __init__() method, class instantiation automatically invokes __init__() for the newly-created class instance. So in this example, a new, initialized instance can be obtained by:

x = MyClass()

Tabii ki, __init__() yöntemi daha fazla esneklik için argümanlara sahip olabilir. Bu durumda, sınıf örnekleme işlecine verilen bağımsız değişkenler __init__() için aktarılır. Mesela:

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

9.3.3. Örnek Nesneleri

Şimdi örnek nesnelerle ne yapabiliriz? Örnek nesneleri tarafından anlaşılan tek işlemler nitelik başvurularıdır. İki tür geçerli öznitelik adı vardır: veri nitelikleri ve metotları.

veri nitelikleri, Smalltalk’taki “örnek değişkenlerine” ve C++’taki “veri üyelerine” karşılık gelir. Veri niteliklerinin önceden tanımlanması gerekmez; yerel değişkenler gibi, ilk değer atandığı anda varlıkları başlar. Örneğin, x yukarıda oluşturulan MyClass örneğiyse, aşağıdaki kod parçası iz bırakmadan 16 değerini yazdırır:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter

Diğer örnek nitelik başvurusu türü bir metot. Metot, bir nesneye “ait” olan bir fonksiyondur. (Python’da, terim metodu sınıf örneklerine özgü değildir: diğer nesne türlerinin de metotları olabilir. Örneğin, liste nesnelerini genişletme, ekleme, kaldırma, sıralama vb. Ancak, aşağıdaki tartışmada, aksi açıkça belirtilmedikçe, yalnızca sınıf örneği nesnelerinin metotlarını ifade etmek için terim metodunu kullanacağız.)

Örnek nesnesinin geçerli metot adları nesnenin sınıfına bağlıdır. Fonksiyon nesneleri olan bir sınıfın tüm nitelikleri, örneklerinin karşılık gelen fonksiyonlarını tanımlar. Bu nedenle, örneğimizde, x.f geçerli bir metot referansıdır, ne de olsa MyClass.f bir fonksiyondur ancak MyClass.i fonksiyon olmadığından x.i değildir. Ancak x.f, MyClass.f ile aynı şey değildir çünkü bir fonksiyon nesnesi değil, metot nesnesi ‘dir.

9.3.4. Metot Nesneleri

Genellikle, bir metot bağlandıktan hemen sonra çağrılır:

x.f()

MyClass örneğinde, hello world string’ini döndürür. Lakin, hemen bir metot çağırmak gerekli değildir: x.f bir metot nesnesidir, yani daha sonra depolanabilir ve çağrılabilir. Mesela:

xf = x.f
while True:
    print(xf())

daima hello world yazdırmaya devam edecek

Bir yöntem çağrıldığında tam olarak ne gerçekleşir? f() fonksiyon tanımı bir argüman belirtmiş olsa da, x.f() öğesinin yukarıda bir argüman olmadan çağrıldığını fark etmiş olabilirsiniz. Peki argümana ne oldu? Elbette Python, argüman gerektiren bir fonksiyon, argüman verilmemiş iken çağırılırsa — fonksiyon aslında kullanılmasa bile hata verir…

Aslında, cevabı tahmin etmiş olabilirsiniz: yöntemlerle ilgili özel şey, örnek nesnesinin fonksiyonun ilk argüman olarak geçirilmesidir. Örneğimizde, x.f() çağrısı tam olarak MyClass.f(x) ile eşdeğerdir. Genel olarak, n tane argümana sahip bir metodu çağırmak, ilk argümandan önce metodun örnek nesnesi eklenerek oluşturulan bir argüman listesiyle karşılık gelen fonksiyonu çağırmaya eşdeğerdir.

Metotların nasıl çalıştığını hala anlamıyorsanız, nasıl uygulandığına bakmak belki de sorunları açıklığa kavuşturabilir. Bir örneğin, veri olmayan bir niteliğine başvurulduğu zaman bu örneğin ait olduğu sınıfa bakılır. Ad bir fonksiyon nesnesi olan geçerli bir sınıf özniteliğini gösterirse, örnek nesne ve fonksiyon nesnesi soyut bir nesnede paketlenerek (işaretçiler) bir metot nesnesi oluşturulur. İşte bu metot nesnesidir. Metot nesnesi bir argüman listesiyle çağrıldığında, örnek nesneden ve argüman listesinden yeni bir argüman listesi oluşturulur ve fonksiyon nesnesi bu yeni argüman listesiyle çağrılır.

9.3.5. Sınıf ve Örnek Değişkenleri

Genel olarak, örnek değişkenleri o örneğe özgü veriler içindir ve sınıf değişkenleri sınıfın tüm örnekleri tarafından paylaşılan nitelikler ile metotlar içindir:

class Dog:

    kind = 'canine'         # class variable shared by all instances

    def __init__(self, name):
        self.name = name    # instance variable unique to each instance

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # shared by all dogs
'canine'
>>> e.kind                  # shared by all dogs
'canine'
>>> d.name                  # unique to d
'Fido'
>>> e.name                  # unique to e
'Buddy'

İsim ve Nesneler Hakkında Birkaç Şey ‘te anlatıldığı gibi, paylaşılan veriler listeler ve sözlükler gibi mutable nesnelerini içeren şaşırtıcı etkilere sahip olabilir. Örneğin, aşağıdaki koddaki tricks listesi sınıf değişkeni olarak kullanılmamalıdır, çünkü yalnızca tek bir liste tüm Dog örnekleri tarafından paylaşılacaktır:

class Dog:

    tricks = []             # mistaken use of a class variable

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']

Doğru olan ise veriyi paylaşmak yerine bir örnek değişkeni kullanmaktır:

class Dog:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # creates a new empty list for each dog

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']

9.4. Rastgele Açıklamalar

Aynı nitelik adı hem bir örnekte hem de bir sınıfta oluşuyorsa, nitelik araması örneğe öncelik verir:

>>> class Warehouse:
        purpose = 'storage'
        region = 'west'

>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east

Veri niteliklerine metotların yanı sıra bir nesnenin sıradan kullanıcıları (“istemciler”) tarafından başvurulabilir. Başka bir deyişle, sınıflar saf soyut veri türlerini uygulamak için kullanılamaz. Aslında, Python’daki hiçbir şey, veri gizlemeyi zorlamaz. Bu durum geleneksel kullanımından dolayı bu şekildedir. (Öte yandan, C ile yazılan Python uygulamaları, uygulama ayrıntılarını tamamen gizleyebilir ve gerekirse bir nesneye erişimi kontrol edebilir; bu, C ile yazılmış Python uzantıları tarafından kullanılabilir.)

İstemciler veri niteliklerini dikkatle kullanmalıdır — istemciler veri özniteliklerini damgalayarak yöntemler tarafından tutulan değişmezleri bozabilir. İstemcilerin, metotların geçerliliğini etkilemeden bir örnek nesnesine kendi veri niteliklerini ekleyebileceğini unutmayın. Tabii ki burada da ad çakışmalarından kaçınılmalıdır, adlandırma kuralları ise kaçınmak için oldukça faydalı olabilir.

Yöntemlerin içinden veri niteliklerine (veya diğer metotlara!) başvurmak için kısa yol yoktur. Bu aslında metotların okunabilirliğini arttırıyor, çünkü bir metoda bakarken yerel değişkenleri ve örnek değişkenlerini karıştırma ihtimali bırakmamış oluyoruz.

Genellikle, bir metodun ilk bağımsız değişkenine self denir. Bu bir kullanım geleneğinden başka bir şey değildir, yani self adının Python için kesinlikle özel bir anlamı yoktur. Bununla birlikte, kurala uymadığınızda kodunuzun diğer Python programcıları tarafından daha az okunabilir olabileceğini ve yazılabilecek potansiyel bir sınıf tarayıcısı programının bu kurala dayanıyor olabileceğini unutmayın

Sınıf niteliği olan herhangi bir fonksiyon nesnesi, bu sınıfın örnekleri için bir metot tanımlar. Fonksiyon tanımının metinsel olarak sınıf tanımına dahil olması gerekli değildir: sınıftaki yerel bir değişkene fonksiyon nesnesi atamak da uygundur. Mesela:

# Function defined outside the class
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1

    def g(self):
        return 'hello world'

    h = g

Buradaki f, g ve h fonksiyonları nesnelerine başvuran C sınıfının bütün nitelikleridir ve sonuç olarak hepsi Ch sınıfının örneklerinin metotları olarak tümüyle g ‘ye eşdeğerdir. Bu kullanım şeklinin genellikle yalnızca bir programı okuyan kişinin kafasını karıştırmaya yaradığını unutmayın.

Metotlar, self bağımsız değişkeninin metot niteliklerini kullanarak diğer metotları çağırabilir:

class Bag:
    def __init__(self):
        self.data = []

    def add(self, x):
        self.data.append(x)

    def addtwice(self, x):
        self.add(x)
        self.add(x)

Metotlar, global adlara sıradan işlevler gibi başvuru yapabilir. Bir metotla ilişkili genel kapsam, tanımını içeren modüldür. (Bir sınıf hiçbir zaman genel kapsam olarak kullanılmaz.) Global verileri bir metotta kullanmak için nadiren iyi bir nedenle karşılaşılsa da, genel kapsamın birçok meşru kullanımı vardır: bir kere, genel kapsamlıya içe aktarılan fonksiyon ve modüller, metotlar ve içinde tanımlanan fonksiyonlar ve sınıflar tarafından kullanılabilir. Genellikle, metodu içeren sınıfın kendisi bu genel kapsamda tanımlanır ve sonraki bölümde bir yöntemin kendi sınıfına başvurmak istemesinin bazı iyi nedenlerini bulacağız.

Her değer bir nesnedir ve bu nedenle bir sınıf (type olarak da adlandırılır) bulundurur. object.__class__ olarak depolanır.

9.5. Kalıtım

Tabii ki, bir dil özelliği kalıtımı desteklemeden “sınıf” adına layık olmaz. Türetilmiş sınıf tanımının söz dizimi şöyle görünür:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

BaseClassName adı, türetilmiş sınıf tanımını içeren bir kapsamda tanımlanmalıdır. Temel sınıf adı yerine, diğer rasgele ifadelere de izin verilir. Bu, örneğin, temel sınıf başka bir modülde tanımlandığında yararlı olabilir:

class DerivedClassName(modname.BaseClassName):

Türetilmiş bir sınıf tanımının yürütülmesi, temel sınıfla aynı şekilde devam eder. Sınıf nesnesi inşa edildiğinde, temel sınıf hatırlanır. Bu, nitelik başvurularını çözmek için kullanılır: istenen nitelik sınıfta bulunmazsa, arama temel sınıfa bakmaya devam eder. Temel sınıfın kendisi başka bir sınıftan türetilmişse, bu kural özyinelemeli olarak uygulanır.

Türetilmiş sınıfların somutlaştırılmasında özel bir şey yoktur: DerivedClassName() sınıfın yeni bir örneğini oluşturur. Metot başvuruları aşağıdaki gibi çözümlenir: ilgili sınıf niteliği aranır, gerekirse temel sınıflar zincirinin aşağısına inilir ve bu bir fonksiyon nesnesi veriyorsa metot başvurusu geçerlidir.

Türetilmiş sınıflar, temel sınıflarının metotlarını geçersiz kılabilir. Metotlar aynı nesnenin diğer yöntemlerini çağırırken özel ayrıcalıkları olmadığından, aynı temel sınıfta tanımlanan başka bir metodu çağıran bir temel sınıfın metodu, onu geçersiz kılan türetilmiş bir sınıfın metodunu çağırabilir. (C++ programcıları için: Python’daki tüm yöntemler etkili bir şekilde sanal.)

Türetilmiş bir sınıfta geçersiz kılma yöntemi aslında yalnızca aynı adlı temel sınıf yöntemini değiştirmek yerine genişletmek isteyebilir. Temel sınıf metodunu doğrudan çağırmanın basit bir yolu vardır: sadece BaseClassName.methodname(self, arguments) çağırın. Bu bazen müşteriler için de yararlıdır. (Bunun yalnızca temel sınıfa genel kapsamda BaseClassName olarak erişilebiliyorsa çalıştığını unutmayın.)

Python’un kalıtımla çalışan iki yerleşik fonksiyonu vardır:

  • Bir örneğin türünü denetlemek için isinstance() kullanın: isinstance(obj, int) yalnızca obj.__class__ int veya int sınıfından türetilmiş bir sınıfsa True olacaktır.

  • Sınıf kalıtımını denetlemek için issubclass() kullanın: issubclass(bool, int) True ‘dur, çünkü bool int ‘in bir alt sınıfıdır. Ancak, issubclass(float, int) False olduğundan float, int alt sınıfı değildir.

9.5.1. Çoklu Kalıtım

Python, çoklu kalıtım biçimini de destekler. Birden çok temel sınıf içeren bir sınıf tanımı şöyle görünür:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

Basitçe, bir üst sınıftan devralınan nitelikleri aramayı, hiyerarşide çakışmanın olduğu aynı sınıfta iki kez arama yapmadan, derinlik öncelikli, soldan sağa olarak düşünebilirsiniz. Bu nedenle, bir nitelik DerivedClassName içinde bulunamazsa, Base1 ‘de, sonra (özyinelemeli olarak) Base1 temel sınıflarında aranır ve orada bulunamazsa Base2 vb.

Aslında, durum bundan biraz daha karmaşıktır: yöntem çözümleme sırası, super() için işbirliği çağrılarını desteklemek için dinamik olarak değişir. Bu yaklaşım, diğer bazı çoklu kalıtım dillerinde sonraki çağrı yöntemi olarak bilinir ve tekli kalıtım dillerinde bulunan süper çağrıdan daha güçlüdür.

Çoklu kalıtımın tüm durumları bir veya daha fazla elmas ilişkisi gösterdiğinden (üst sınıflardan en az birine, en alttaki sınıftan birden çok yol üzerinden erişilebildiği ilişkiler) dinamik sıralama gereklidir. Örneğin, tüm sınıflar object öğesini devralır, bu nedenle çoklu kalıtım durumu object ‘e ulaşmak için birden fazla yol sağlar. Temel sınıflara birden çok kez erişilmesini önlemek için, dinamik algoritma arama sırasını her sınıfta belirtilen soldan sağa sıralamayı koruyacak şekilde doğrular, her üst öğeyi yalnızca bir kez çağırır ve monotondur (yani bir sınıf, üst sınıfının öncelik sırasını etkilemeden alt sınıflandırılabilir). Birlikte ele alındığında, bu özellikler çoklu kalıtım ile güvenilir ve genişletilebilir sınıflar tasarlamayı mümkün kılar. Daha fazla ayrıntı için bkz. https://www.python.org/download/releases/2.3/mro/

9.6. Özel Değişkenler

Python’da bir nesnenin içinden erişilmesi dışında asla erişilemeyen “özel” örnek değişkenleri yoktur. Ancak, çoğu Python kodu tarafından izlenen bir kural vardır: alt çizgi (örneğin _spam) ile öneklenmiş bir ad API’nin genel olmayan bir parçası olarak kabul edilmelidir (bir fonksiyon, metot veya veri üyesi olsun). Bir uygulama detayıdır ve önceden haber verilmeksizin değiştirilebilir.

Sınıf-özel üyeler için geçerli bir kullanım örneği olduğundan (yani alt sınıflar tarafından tanımlanan adlara sahip adların ad çakışmasını önlemek için), name mangling adı verilen böyle bir mekanizma için sınırlı destek vardır. __spam formunun herhangi bir tanımlayıcısı (en az iki satır altı, en fazla bir alt çizgi) metinsel olarak _classname__spam ile değiştirilir; Bu mangling, bir sınıfın tanımı içinde gerçekleştiği sürece tanımlayıcının söz dizimsel konumuna bakılmaksızın yapılır.

Ad mangling, alt sınıfların sınıf içi metot çağrılarını kesmeden metotları geçersiz kılmasına izin vermek için yararlıdır. Mesela:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

Yukarıdaki örnek, MappingSubclass sırasıyla Mapping sınıfında _Mapping__update ve mappingSubclass sınıfında _MappingSubclass__update ile değiştirildiği için __update tanımlayıcısı tanıtsa bile çalışır.

Mangling kurallarının çoğunlukla kazaları önlemek için tasarlandığını unutmayın; özel olarak kabul edilen bir değişkene erişmek veya değiştirmek hala mümkündür. Bu, hata ayıklayıcı gibi özel durumlarda bile yararlı olabilir.

exec() veya eval() koduna geçirilen kodun çağırma sınıfının sınıf adını geçerli sınıf olarak görmediğine dikkat edin; bu, etkisi aynı şekilde birlikte bayt derlenmiş kodla sınırlı olan global deyiminin etkisine benzer. Aynı kısıtlama getattr(), setattr() ve delattr() ve doğrudan __dict__ atıfta bulunurken de geçerlidir.

9.7. Oranlar ve Bitişler

Sometimes it is useful to have a data type similar to the Pascal “record” or C “struct”, bundling together a few named data items. An empty class definition will do nicely:

class Employee:
    pass

john = Employee()  # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

Belirli bir soyut veri türünü bekleyen Python kodunun bir parçası genellikle bunun yerine bu veri türünün yöntemlerine öykünen bir sınıfa geçirilebilir. Örneğin, bir dosya nesnesinden bazı verileri biçimlendiren bir fonksiyonunuz varsa, bunun yerine verileri bir dize arabelleğinden alan ve bağımsız değişken olarak geçiren read() ve readline() yöntemlerine sahip bir sınıf tanımlayabilirsiniz.

Örnek yöntem nesnelerinin de nitelikleri vardır: m.__self__ yöntemi olan örnek nesnedir m(), ve m.__func__ yönteme karşılık gelen fonksiyon nesnesidir.

9.8. Yineleyiciler

Şimdiye kadar büyük olasılıkla çoğu kapsayıcı nesnenin bir for deyimi kullanılarak döngüye alınabileceğini fark etmişsinizdir:

for element in [1, 2, 3]:
    print(element)
for element in (1, 2, 3):
    print(element)
for key in {'one':1, 'two':2}:
    print(key)
for char in "123":
    print(char)
for line in open("myfile.txt"):
    print(line, end='')

Bu erişim tarzı açık, özlü ve kullanışlıdır. Yineleyicilerin kullanımı Python’u istila eder ve birleştirir. Perde arkasında for deyimi kapsayıcı nesne üzerinde iter() öğesini çağırır. Fonksiyon, kapsayıcıdaki öğelere teker teker erişen __next__() metodunu tanımlayan bir yineleyici nesnesi döndürür. Başka öğe olmadığında, __next__(), for döngüsünün sonlandırılacağını bildiren bir StopIteration hatası oluşturur. next() yerleşik fonksiyonunu kullanarak __next__() yöntemini çağırabilirsiniz; Bu örnek, her şeyin nasıl çalıştığını gösterir:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    next(it)
StopIteration

Yineleme protokolünün arkasındaki mekaniği gördükten sonra, sınıflarınıza yineleme davranışı eklemek kolaydır. __next__() metodu ile bir nesne döndüren __iter__() metodunu tanımlayın. Sınıf __next__() tanımlarsa, __iter__() sadece self döndürebilir:

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s

9.9. Üreteçler

Üreteçler yineleyiciler oluşturmak için basit ve güçlü bir araçtır. Normal fonksiyonlar gibi yazılırlar, ancak veri döndürmek istediklerinde yield deyimini kullanırlar. Üzerinde her next() çağrıldığı zaman, üreteç kaldığı yerden devam eder (tüm veri değerlerini ve hangi deyimin en son yürütüldüğını hatırlar). Bu örnek, üreteçlerin oluşturulmasının ne kadar da kolay olabileceğini gösterir:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]
>>> for char in reverse('golf'):
...     print(char)
...
f
l
o
g

Üreteçlerle yapılabilecek her şey, önceki bölümde açıklandığı gibi sınıf tabanlı yineleyicilerle de yapılabilir. Üreteçleri bu kadar kompakt yapan şey: __iter__() ve __next__() yöntemlerinin otomatik olarak oluşturulmasıdır.

Başka bir önemli özellik, yerel değişkenlerin ve yürütme durumunun çağrılar arasında otomatik olarak kaydedilmesidir. Bu, fonksiyonun yazılmasını kolaylaştırdı ve self.index ve self.data gibi değişkenleri kullanmaya kıyasla çok daha net hale getirdi.

Otomatik metot oluşturma ve kaydetme programı durumuna ek olarak, üreteçler sonlandırıldığında otomatik olarak StopIteration ‘ı yükseltirler. Birlikte, bu özellikler normal bir işlev yazmaktan daha fazla çaba harcamadan yinelemeler oluşturmayı kolaylaştırır.

9.10. Üreteç İfadeleri

Bazı basit üreteçler, listelere benzer bir söz dizimi kullanılarak ve köşeli ayraçlar yerine parantezlerle kısaca kodlanabilir. Bu ifadeler, üreteçlerin kapsayıcı bir fonksiyon tarafından hemen kullanıldığı durumlar için tasarlanmıştır. Üreteç ifadeleri tam üreteç tanımlarından daha kompakt ancak daha az çok yönlüdür ve aynı özellikle liste anlamalarından daha bellek dostu olma eğilimindedir.

Örnekler:

>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> unique_words = set(word for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

Dipnotlar

1

Bir şey hariç. Modül nesneleri, __dict__ denen sadece okunabilir bir özelliğe sahiptir, bu da modülün ad alanını uygulamak için bir sözlük döndürür: __dict__ ismi bir özellik olsa da global bir isim değildir. Kuşkusuz, bu durum ad alanlarının soyutlanış özelliklerine aykırı, dolayısıyla sadece programın durması sonrası çalışan hata ayıklayıcılar gibilerinin kullanımına kısıtlanmalı.