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, contain 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 next-to-last 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ı.

*Data attributes* correspond to "instance variables" in Smalltalk, and
to "data members" in C++.  Data attributes need not be declared; like
local variables, they spring into existence when they are first
assigned to.  For example, if "x" is the instance of "MyClass" created
above, the following piece of code will print the value "16", without
leaving a trace:

   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 "C" --- "h"
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. The
idiomatic approach is to use "dataclasses" for this purpose:

   from dataclasses import dataclass

   @dataclass
   class Employee:
       name: str
       dept: str
       salary: int

   >>> john = Employee('john', 'computer lab', 1000)
   >>> 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ı.
