Python kendisini yalnızca popüler bir komut dosyası dili olarak sunmakla kalmaz, aynı zamanda nesne yönelimli programlama paradigmasını da destekler. Sınıflar verileri tanımlar ve hepsi tek bir nesne altında toplanan bu verileri işlemek için yöntemler sağlar. Ayrıca sınıflar, somut uygulama ayrıntılarını verilerin soyut temsillerinden ayırarak soyutlamaya izin verir.
Sınıf kullanan kodun okunması, anlaşılması ve bakımı genellikle daha kolaydır.
Sınıf kavramına giriş
Bir sınıf, belirli bir nesnenin temel özelliklerini tanımlayan bir şablon olarak işlev görür:
class Person(object): """Basit bir sınıf.""" # docstring species = "Homo Sapiens" # sınıf özniteliği def __init__(self, name): # özel metot """Bu başlatıcıdır. Bu özel bir yöntemdir (aşağıya bakın). """ self.name = name # örnek özniteliği def __str__(self): # özel yöntem """Bu metot, Python aşağıdakileri denediğinde çalıştırılır; nesneyi bir dizeye dönüştürmek için veya print() vb. işlevleri kullanırken bu dizeyi döndürmek için. """ return self.name def rename(self, renamed): # normal yöntem """Ad niteliğini yeniden atayın ve yazdırın."" self.name = renamed print("Şimdi benim adım {}".format(self.name))
Yukarıdaki örneğe bakarken dikkat edilmesi gereken birkaç husus vardır.
- Sınıf, nitelikler (veriler) ve yöntemlerden (işlevler) oluşur.
- Nitelikler ve yöntemler basitçe normal değişkenler ve işlevler olarak tanımlanır.
- İlgili docstring’de belirtildiği gibi, init() yöntemi başlatıcı olarak adlandırılır. Diğer nesne yönelimli dillerdeki constructor’a eşdeğerdir ve yeni bir nesne veya sınıfın yeni bir örneğini oluşturduğunuzda ilk çalıştırılan yöntemdir.
- Tüm sınıf için geçerli olan nitelikler ilk olarak tanımlanır ve sınıf nitelikleri olarak adlandırılır.
- Bir sınıfın belirli bir örneği (bir nesne) için geçerli olan niteliklere örnek nitelikleri denir. Genellikle init() içinde tanımlanırlar; bu gerekli değildir, ancak önerilir (init() dışında tanımlanan nitelikler tanımlanmadan önce erişilme riski taşıdığından).
- Sınıf tanımına dahil edilen her yöntem, söz konusu nesneyi ilk parametresi olarak geçirir. Bu parametre için self kelimesi kullanılır (self kelimesinin Python’da kendine özgü bir anlamı olmadığından, self kullanımı aslında gelenekseldir, ancak bu Python’un en saygın geleneklerinden biridir ve her zaman buna uymalısınız).
- Diğer dillerde nesne yönelimli programlamaya alışkın olanlar birkaç şeye şaşırabilirler. Birincisi, Python’un gerçek bir private öğe kavramı yoktur, bu nedenle her şey varsayılan olarak C++/Java public anahtar sözcüğünün davranışını taklit eder.
- Sınıfın bazı yöntemleri aşağıdaki biçime sahiptir: functionname(self, other_stuff). Bu tür yöntemlerin tümüne “sihirli yöntemler” denir ve Python’da sınıfların önemli bir parçasıdır. Örneğin, Python’da operatör aşırı yükleme sihirli metotlarla gerçekleştirilir.
Şimdi Person sınıfımızın birkaç nesnesini oluşturalım!
>>> # nesneler >>> kelly = Person("Kelly") >>> joseph = Person("Joseph") >>> john_doe = Person("John Doe")
Şu anda kelly, joseph ve john_doe olmak üzere üç Person nesnemiz var.
Nokta operatörünü kullanarak her bir örnekten sınıfın niteliklerine erişebiliriz. Sınıf ve örnek öznitelikleri arasındaki farka tekrar dikkat edin:
>>> # Nitelikler >>> kelly.species 'Homo Sapiens' >>> john_doe.species 'Homo Sapiens' >>> joseph.species 'Homo Sapiens' >>> kelly.name 'Kelly' >>> joseph.name 'Joseph'
Aynı nokta operatörünü kullanarak sınıfın metotlarını çalıştırabiliriz:
>>> # Methods >>> john_doe.__str__() 'John Doe' >>> print(john_doe) 'John Doe' >>> john_doe.rename("John") 'Now my name is John'
Maymun Yaması – Monkey Patching
Bu durumda, “maymun yaması”, bir sınıfa tanımlandıktan sonra yeni bir değişken veya yöntem eklemek anlamına gelir. Örneğin, A sınıfını şu şekilde tanımladığımızı varsayalım
class A(object): def __init__(self, num): self.num = num def __add__(self, other): return A(self.num + other.num)
Ancak şimdi kodun ilerleyen kısımlarına başka bir fonksiyon eklemek istiyoruz. Bu fonksiyonun aşağıdaki gibi olduğunu varsayalım.
def get_num(self): return self.num
Peki bunu A’ya bir yöntem olarak nasıl ekleyeceğiz? Çok basit, aslında bu fonksiyonu bir atama deyimiyle A’ya yerleştiriyoruz.
A.get_num = get_num
Bu neden işe yarıyor? Çünkü fonksiyonlar tıpkı diğer nesneler gibi nesnelerdir ve metotlar sınıfa ait fonksiyonlardır.
get_num işlevi tüm mevcut (önceden oluşturulmuş) ve yeni A örnekleri için kullanılabilir olmalıdır.
Bu eklemeler, söz konusu sınıfın (veya alt sınıflarının) tüm örneklerinde otomatik olarak kullanılabilir. Örneğin:
foo = A(42) A.get_num = get_num bar = A(6); foo.get_num() # 42 bar.get_num() # 6
Diğer bazı dillerin aksine, bu tekniğin belirli yerleşik türler için çalışmadığını ve iyi bir stil olarak kabul edilmediğini unutmayın.
Yeni tarz ve eski tarz sınıflar
Python 2.2’de sınıfları ve türleri birleştirmek için yeni stil sınıflar tanıtıldı. En üst düzey nesne türünden miras alırlar. Yeni tarz bir sınıf kullanıcı tanımlı bir türdür ve yerleşik türlere çok benzer.
# yeni stil sınıf class New(object): pass # yeni tarz örnek new = New() new.__class__ # <class '__main__.New'> type(new) # <class '__main__.New'> issubclass(New, object) # True
Eski tarz sınıflar nesneden miras almaz. Eski tarz örnekler her zaman yerleşik bir örnek türüyle uygulanır.
# eski tarz sınıf class Old: pass # eski tarz örnek old = Old() old.__class__ # <class __main__.Old at ...> type(old) # <type 'instance'> issubclass(Old, object) # False
Python 3’te eski tarz sınıflar kaldırıldı.
Python 3’teki yeni stil sınıflar object’ten dolaylı olarak miras alır, bu nedenle artık MyClass(object) belirtmeye gerek yoktur.
class MyClass: pass my_inst = MyClass() type(my_inst) # <class '__main__.MyClass'> my_inst.__class__ # <class '__main__.MyClass'> issubclass(MyClass, object) # True
Bağlı/bağlı olmayan ve statik yöntemler
Bağlı ve bağlı olmayan metotlar fikri Python 3’te kaldırılmıştır. Python 3’te bir sınıf içinde bir yöntem bildirdiğinizde, bir def anahtar sözcüğü kullanırsınız, böylece bir işlev nesnesi oluşturursunuz. Bu normal bir fonksiyondur ve çevreleyen sınıf onun isim alanı olarak çalışır. Aşağıdaki örnekte A sınıfı içinde f metodunu bildiriyoruz ve bu A.f fonksiyonuna dönüşüyor:
class A(object): def f(self, x): return 2 * x A.f # <function A.f at ...> (in Python 3.x)
Python 2’de davranış farklıydı: sınıf içindeki fonksiyon nesneleri, belirli bir sınıf örneğine bağlı olmadıkları için bağlı olmayan yöntemler olarak adlandırılan instancemethod türündeki nesnelerle dolaylı olarak değiştirildi. Temel fonksiyona .func özelliğini kullanarak erişmek mümkündü.
A.f # <unbound method A.f> (in Python 2.x) A.f.__class__ # <type 'instancemethod'> A.f.__func__ # <function f at ...>
Bu son davranışlar inspect ile doğrulanır – Python 3’te yöntemler işlev olarak tanınırken, Python 2’de bu ayrım korunur.
import inspect inspect.isfunction(A.f) # True inspect.ismethod(A.f) # False
Python 3:
import inspect inspect.isfunction(A.f) # False inspect.ismethod(A.f) # True
Python’un her iki sürümünde de A.f fonksiyonu/yöntemi, ilk argüman olarak A sınıfının bir örneğini geçirmeniz koşuluyla doğrudan çağrılabilir.
A.f(1, 7) # Python 2: TypeError: unbound method f() must be called with # A instance as first argument (got int instance instead) # Python 3: 14 a = A() A.f(a, 20) # Python 2 & 3: 40
Şimdi a’nın A sınıfının bir nesnesi olduğunu varsayalım, o zaman a.f nedir? Sezgisel olarak bu, A sınıfının aynı f metodu olmalıdır, sadece bir şekilde a nesnesine uygulandığını “bilmelidir” – Python’da buna a’ya bağlı metot denir.
Özlü ayrıntılar şu şekildedir: a.f yazmak a’nın sihirli __getattribute__ yöntemini çağırır, bu yöntem önce a’nın f adında bir özniteliğe sahip olup olmadığını kontrol eder (sahip değildir), ardından A sınıfının böyle bir ada sahip bir yöntem içerip içermediğini kontrol eder (içerir) ve m.__func__ içinde orijinal A.f’ye ve m.__self__ içinde a nesnesine bir referansı olan yöntem türünde yeni bir m nesnesi oluşturur. Bu nesne bir fonksiyon olarak çağrıldığında, basitçe şunu yapar: m(…) => m.__func__(m.__self__, …). Böylece bu nesneye bağlı bir yöntem denir çünkü çağrıldığında ilk argüman olarak bağlı olduğu nesneyi sağlamayı bilir. (Bu şeyler Python 2 ve 3’te de aynı şekilde çalışır).
a = A() a.f # <bound method A.f of <__main__.A object at ...>> a.f(2) # 4 #Not: a.f bağlı yöntem nesnesi *her çağırışınızda* yeniden oluşturulur: a.f is a.f # False # Bir performans optimizasyonu olarak, bağlı yöntemi nesnenin __dict__ değerinde saklayabiliriz, bu durumda yöntem nesnesi sabit kalacaktır: a.f = a.f a.f is a.f # True
Son olarak, Python’da sınıf metotları ve statik metotlar vardır – özel metot türleri. Sınıf yöntemleri normal yöntemlerle aynı şekilde çalışır, ancak bir nesne üzerinde çağrıldıklarında nesne yerine nesnenin sınıfına bağlanırlar. Böylece m.__self__ = type(a) olur. Böyle bir bağlı yöntemi çağırdığınızda, ilk argüman olarak a’nın sınıfını geçirir. Statik metotlar daha da basittir: hiçbir şeye bağlanmazlar ve herhangi bir dönüştürme yapmadan sadece temel fonksiyonu döndürürler.
class D(object): multiplier = 2 @classmethod def f(cls, x): return cls.multiplier * x @staticmethod def g(name): print("Hello, %s" % name) D.f # <bound method type.f of <class '__main__.D'>> D.f(12) # 24 D.g # <function D.g at ...> D.g("world") # Hello, world
Sınıf yöntemlerinin, örneğe erişildiğinde bile sınıfa bağlı olduğunu unutmayın:
d = D() d.multiplier = 1337 (D.multiplier, d.multiplier) # (2, 1337) d.f # <bound method D.f of <class '__main__.D'>> d.f(10) # 20
En düşük seviyede, fonksiyonların, metotların, staticmetotların vb. aslında get, set ve isteğe bağlı olarak del özel metotlarını çağıran tanımlayıcılar olduğunu belirtmek gerekir.
Sınıf yöntemleri – alternatif başlatıcılar
Sınıf yöntemleri, sınıfların örneklerini oluşturmak için alternatif yollar sunar. Açıklamak için bir örneğe bakalım.
Nispeten basit bir Person sınıfımız olduğunu varsayalım:
class Person(object): def __init__(self, first_name, last_name, age): self.first_name = first_name self.last_name = last_name self.age = age self.full_name = first_name + " " + last_name def greet(self): print("Hello, my name is " + self.full_name + ".")
Bu sınıfın örneklerini ad ve soyadı ayrı ayrı belirtmek yerine tam adı belirterek oluşturmanın bir yolu olması kullanışlı olabilir. Bunu yapmanın bir yolu, last_name’in isteğe bağlı bir parametre olması ve verilmediği takdirde tam adın geçildiğini varsaymak olabilir:
class Person(object): def __init__(self, first_name, age, last_name=None): if last_name is None: self.first_name, self.last_name = first_name.split(" ", 2) else: self.first_name = first_name self.last_name = last_name self.full_name = self.first_name + " " + self.last_name self.age = age def greet(self): print("Hello, my name is " + self.full_name + ".")
Ancak, bu kod parçasıyla ilgili iki ana sorun vardır:
- First_name ve last_name parametreleri artık yanıltıcıdır, çünkü first_name için tam bir ad girebilirsiniz. Ayrıca, bu tür esnekliğe sahip daha fazla durum ve/veya daha fazla parametre varsa, if/elif/else dallanmaları hızla can sıkıcı hale gelebilir.
- O kadar önemli değil ama yine de belirtmekte fayda var: ya last_name None ise ama first_name boşluklarla iki ya da daha fazla şeye bölünmüyorsa? Başka bir girdi doğrulama ve/veya istisna işleme katmanımız daha oldu…
Sınıf yöntemlerine başvuralım. Tek bir başlatıcı yerine, from_full_name adında ayrı bir başlatıcı oluşturacağız ve bunu (yerleşik) classmethod dekoratörü ile süsleyeceğiz.
class Person(object): def __init__(self, first_name, last_name, age): self.first_name = first_name self.last_name = last_name self.age = age self.full_name = first_name + " " + last_name @classmethod def from_full_name(cls, name, age): if " " not in name: raise ValueError first_name, last_name = name.split(" ", 2) return cls(first_name, last_name, age) def greet(self): print("Hello, my name is " + self.full_name + ".")
from_full_name’in ilk argümanı olarak self yerine cls’ye dikkat edin. Sınıf yöntemleri, belirli bir sınıfın örneğine değil (self genellikle bunu ifade eder), sınıfın tamamına uygulanır. Dolayısıyla, eğer cls bizim Person sınıfımızsa, from_full_name sınıf metodundan dönen değer Person(first_name, last_name, age) olur ve Person sınıfının bir örneğini oluşturmak için Person’ın init özelliğini kullanır. Özellikle, Person sınıfının Employee alt sınıfını oluşturacak olursak, from_full_name Employee sınıfında da çalışacaktır.
Bunun beklendiği gibi çalıştığını göstermek için, init‘de dallanma olmadan birden fazla şekilde Person örnekleri oluşturalım:
In [2]: bob = Person("Bob", "Bobberson", 42) In [3]: alice = Person.from_full_name("Alice Henderson", 31) In [4]: bob.greet() Hello, my name is Bob Bobberson. In [5]: alice.greet() Hello, my name is Alice Henderson.
Default values for instance variables
Değişken değişmez türde bir değer içeriyorsa (örneğin bir dize), aşağıdaki gibi varsayılan bir değer atamakta bir sakınca yoktur
class Rectangle(object): def __init__(self, width, height, color='blue'): self.width = width self.height = height self.color = color def area(self): return self.width * self.height # Sınıfın bazı örneklerini oluşturun default_rectangle = Rectangle(2, 3) print(default_rectangle.color) # blue red_rectangle = Rectangle(2, 3, 'red') print(red_rectangle.color) # red
Oluşturma işleminde liste gibi değiştirilebilir nesneleri başlatırken dikkatli olmak gerekir. Aşağıdaki örneği ele alalım:
class Rectangle2D(object): def __init__(self, width, height, pos=[0,0], color='blue'): self.width = width self.height = height self.pos = pos self.color = color r1 = Rectangle2D(5,3) r2 = Rectangle2D(7,8) r1.pos[0] = 4 r1.pos # [4, 0] r2.pos # [4, 0] r2'nin konumu da değişti
Bu davranış, Python’da varsayılan parametrelerin işlev bildiriminde değil, işlev yürütülürken bağlanması gerçeğinden kaynaklanır. Örnekler arasında paylaşılmayan bir varsayılan örnek değişkeni elde etmek için, aşağıdaki gibi bir yapı kullanılmalıdır:
class Rectangle2D(object): def __init__(self, width, height, pos=None, color='blue'): self.width = width self.height = height self.pos = pos or [0, 0] # varsayılan değer [0, 0] self.color = color r1 = Rectangle2D(5,3) r2 = Rectangle2D(7,8) r1.pos[0] = 4 r1.pos # [4, 0] r2.pos # [0, 0] r2'nin konumu da değişti
Sınıf yapısı
Sınıf kompozisyonu nesneler arasında açık ilişkilere izin verir. Aşağıdaki örnekte, insanlar ülkelere ait şehirlerde yaşamaktadır. Kompozisyon, insanların kendi ülkelerinde yaşayan tüm insanların sayısına erişmelerini sağlar:
class Country(object): def __init__(self): self.cities=[] def addCity(self,city): self.cities.append(city) class City(object): def __init__(self, numPeople): self.people = [] self.numPeople = numPeople def addPerson(self, person): self.people.append(person) def join_country(self,country): self.country = country country.addCity(self) for i in range(self.numPeople): person(i).join_city(self) class Person(object): def __init__(self, ID): self.ID=ID def join_city(self, city): self.city = city city.addPerson(self) def people_in_my_country(self): x= sum([len(c.people) for c in self.city.country.cities]) return x US=Country() NYC=City(10).join_country(US) SF=City(5).join_country(US) print(US.cities[0].people[0].people_in_my_country()) # 15
Sınıf ve örnek değişkenleri
Örnek değişkenleri her örnek için benzersizdir, sınıf değişkenleri ise tüm örnekler tarafından paylaşılır.
class C: x = 2 # class değişkeni def __init__(self, y): self.y = y # instance değişkeni C.x # 2 C.y # AttributeError: type object 'C' has no attribute 'y' c1 = C(3) c1.x # 2 c1.y # 3 c2 = C(4) c2.x # 2 c2.y # 4
Sınıf değişkenlerine bu sınıfın örneklerinden erişilebilir, ancak sınıf niteliğine atama yapmak, sınıf değişkenini gölgeleyen bir örnek değişken yaratacaktır
c2.x = 4 c2.x # 4 C.x # 2
Sınıf değişkenlerini örneklerden değiştirmenin bazı beklenmedik sonuçlara yol açabileceğini unutmayın.
class D: x = [] def __init__(self, item): self.x.append(item) # bunun bir tanımlama olmadiğini unutmayin! d1 = D(1) d2 = D(2) d1.x # [1, 2] d2.x # [1, 2] D.x # [1, 2]
Tüm Sınıf Üyelerini Listeleme
dir() fonksiyonu, bir sınıfın üyelerinin bir listesini almak için kullanılabilir:
dir(Class)
Örneğin:
>>> dir(list) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Yalnızca “sihirli olmayan” üyeleri aramak yaygındır. Bu, __ ile başlamayan isimlere sahip üyeleri listeleyen basit bir kavrama kullanılarak yapılabilir:
>>> [m for m in dir(list) if not m.startswith('__')] ['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Uyarılar:
Sınıflar bir __dir__() metodu tanımlayabilir. Eğer bu metot mevcutsa dir() metodu __dir__() metodunu çağırır, aksi takdirde Python sınıfın üyelerinin bir listesini oluşturmaya çalışır. Bu, dir fonksiyonunun beklenmedik sonuçlar doğurabileceği anlamına gelir. Resmi python dokümantasyonundan iki önemli alıntıya bakalım:
Nesne dir() işlevini sağlamazsa, işlev, tanımlanmışsa nesnenin dict niteliğinden ve tür nesnesinden bilgi toplamak için elinden gelenin en iyisini yapmaya çalışır. Sonuçta elde edilen liste tam olmayabilir ve nesnenin özel bir getattr() işlevi varsa hatalı olabilir.
Not: dir() öncelikle etkileşimli bir komut isteminde kullanım için bir kolaylık olarak sağlandığından, titizlikle veya tutarlı bir şekilde tanımlanmış bir ad kümesi sağlamaya çalışmaktan çok daha ilginç bir ad kümesi sağlamaya çalışır ve ayrıntılı davranışı sürümler arasında değişebilir. Örneğin, argüman bir sınıf olduğunda metasınıf nitelikleri sonuç listesinde yer almaz.
ABCMeta üst sınıfını ayarlama
Abstract sınıflar, miras alınması amaçlanan ancak belirli yöntemleri uygulamaktan kaçınan, yalnızca alt sınıfların uygulaması gereken yöntem imzalarını geride bırakan sınıflardır.
Soyut sınıflar, typed dillerdeki arayüz kavramına benzer şekilde, yöntem uygulamasına ihtiyaç duymadan sınıf soyutlamalarını yüksek seviyede tanımlamak ve uygulamak için kullanışlıdır.
Soyut bir sınıf tanımlamak için kavramsal bir yaklaşım, sınıf yöntemlerini saplamak ve ardından erişilirse NotImplementedError yükseltmektir. Bu, alt sınıfların soyut sınıfları geçersiz kılmadan üst yöntemlere erişmesini önler. Bunun gibi:
class Fruit: def check_ripeness(self): raise NotImplementedError("check_ripeness method not implemented!")
class Apple(Fruit): pass a = Apple() a.check_ripeness() # NotImplementedError
Bu şekilde bir soyut sınıf oluşturmak, geçersiz kılınmayan yöntemlerin uygunsuz kullanımını önler ve kesinlikle yöntemlerin alt sınıflarda tanımlanmasını teşvik eder, ancak bunların tanımlanmasını zorunlu kılmaz. abc modülü ile, ebeveynlerinin ve atalarının soyut sınıf yöntemlerini geçersiz kılmadıklarında çocuk sınıfların örneklenmesini önleyebiliriz:
from abc import ABCMeta class AbstractClass(object): # metasınıf niteliği her zaman bir sınıf değişkeni olarak ayarlanmalıdır __metaclass__ = ABCMeta # abstractmethod dekoratörü bu yöntemi tanımsız olarak kaydeder @abstractmethod def virtual_method_subclasses_must_define(self): # Tamamen boş bırakılabilir veya bir temel uygulama sağlanabilir #Normalde boş bir yorumlamanın dolaylı olarak `None` döndürdüğünü unutmayın, ancak kayıt yaptırıldığında bu davranış artık zorunlu değildir.
Artık basitçe alt sınıf oluşturmak ve geçersiz kılmak mümkündür:
class Subclass(AbstractClass): def virtual_method_subclasses_must_define(self): return
Singleton sınıfı
Singleton, bir sınıfın örneklenmesini tek bir örnek/nesne ile sınırlayan bir kalıptır. Python singleton tasarım kalıpları hakkında daha fazla bilgi için buraya bakın.
class Singleton: def __new__(cls): try: it = cls.__it__ except AttributeError: it = cls.__it__ = object.__new__(cls) return it def __repr__(self): return '<{}>'.format(self.__class__.__name__.upper()) def __eq__(self, other): return other is self
Başka bir yöntem de sınıfınızı süslemektir:
class Singleton: """ Singleton'ları uygulamayı kolaylaştırmak için thread-safe olmayan bir yardımcı sınıf. Bu sınıf, singleton olması gereken sınıf için bir üst sınıf olarak değil, bir dekoratör olarak kullanılmalıdır. Dekoratör sınıf sadece `self` argümanını alan bir `__init__` fonksiyonu tanımlayabilir. Bunun dışında, dekore edilmiş sınıf için geçerli olan herhangi bir kısıtlama yoktur. Singleton örneğini almak için `Instance` yöntemini kullanın. `__call__` kullanmaya çalışmak bir `TypeError` oluşmasına neden olur. Sınırlamalar: Süslenmiş sınıf miras alınamaz. """ def __init__(self, decorated): self._decorated = decorated def Instance(self): """ Singleton örneğini döndürür. İlk çağrıldığında, bir dekore edilmiş sınıfın yeni bir örneğini oluşturur ve `__init__` yöntemini çağırır. Sonraki tüm çağrılarda, önceden oluşturulmuş örnek döndürülür. """ try: return self._instance except AttributeError: self._instance = self._decorated() return self._instance def __call__(self): raise TypeError('Singletons must be accessed through `Instance()`.') def __instancecheck__(self, inst): return isinstance(inst, self._decorated)
Kullanmak için Instance yöntemini kullanabilirsiniz
@Singleton class Single: def __init__(self): self.name=None self.val=0 def getName(self): print(self.name) x=Single.Instance() y=Single.Instance() x.name='I\'m single' x.getName() # çıktı: I'm single y.getName() # çıktı: I'm single
Temel miras/kalıtım
Python’daki kalıtım Java, C++ gibi diğer nesne yönelimli dillerde kullanılan benzer fikirlere dayanır. Yeni bir sınıf mevcut bir sınıftan aşağıdaki şekilde türetilebilir.
class BaseClass(object): pass class DerivedClass(BaseClass): pass
BaseClass zaten var olan (ebeveyn) sınıftır ve DerivedClass, BaseClass’tan nitelikleri miras alan (veya alt sınıf oluşturan) yeni (çocuk) sınıftır. Not: Python 2.2’den itibaren tüm sınıflar, tüm yerleşik türler için temel sınıf olan object sınıfından örtük olarak miras alır.
Aşağıdaki örnekte, object’ten örtük olarak miras alan bir üst Rectangle sınıfı tanımlıyoruz:
class Rectangle(): def __init__(self, w, h): self.w = w self.h = h def area(self): return self.w * self.h def perimeter(self): return 2 * (self.w + self.h)
Rectangle sınıfı, Square sınıfını tanımlamak için bir temel sınıf olarak kullanılabilir, çünkü kare, dikdörtgenin’ın özel bir durumudur.
class Square(Rectangle): def __init__(self, s): # ana oluşturucuyu çağırır, w ve h'nin her ikisi de s'ye eşit super(Square, self).__init__(s, s) self.s = s
Square sınıfı otomatik olarak Rectangle sınıfının tüm niteliklerini ve object sınıfını miras alacaktır. super(), Rectangle sınıfının __init__() yöntemini çağırmak için kullanılır, aslında temel sınıfın herhangi bir geçersiz kılınmış yöntemini çağırır. Not: Python 3’te super() argüman gerektirmez.
Türetilmiş sınıf nesneleri, temel sınıflarının niteliklerine erişebilir ve bunları değiştirebilir:
r.area() # Çıktı: 12 r.perimeter() # Çıktı: 14 s.area() # Çıktı: 4 s.perimeter() # Çıktı: 8
Kalıtım ile çalışan yerleşik fonksiyonlar
issubclass(DerivedClass, BaseClass)
: DerivedClass, BaseClass’ın bir alt sınıfıysa True döndürür
isinstance(s, Class)
: s, Class’ın veya Class’tan türetilmiş sınıfların bir örneğiyse True döndürür
# alt sınıf kontrolü issubclass(Square, Rectangle) # Çıktı: True # instantiate r = Rectangle(3, 4) s = Square(2) isinstance(r, Rectangle) # Çıktı: True isinstance(r, Square) # Çıktı: False # Dikdörtgen bir kare değildir isinstance(s, Rectangle) # Çıktı: True # Kare bir dikdörtgendir isinstance(s, Square) # Çıktı: True
@Property – Özellikler
Python sınıfları, normal nesne değişkenleri gibi görünen, ancak özel davranış ve dokümantasyon ekleme olasılığı olan özellikleri destekler.
class MyClass(object): def __init__(self): self._my_string = "" @property def string(self): """Son derece önemli bir dize.""" return self._my_string @string.setter def string(self, new_value): assert isinstance(new_value, str), \ "Bana bir dize verin, %r değil!" type(new_value) self._my_string = new_value @string.deleter def x(self): self._my_string = None
MyClass sınıfının nesneleri .string özelliğine sahip gibi görünecektir, ancak davranışı artık sıkı bir şekilde kontrol edilmektedir:
mc = MyClass() mc.string = "String!" print(mc.string) del mc.string
Yukarıdaki gibi kullanışlı sözdiziminin yanı sıra, özellik sözdizimi bu niteliklere doğrulama veya diğer eklentilerin eklenmesine olanak tanır. Bu, özellikle kullanıcıya belirli bir düzeyde yardım verilmesi gereken genel API’lerde faydalı olabilir.
Özelliklerin bir başka yaygın kullanımı da sınıfın ‘sanal nitelikler’ sunmasını sağlamaktır – aslında saklanmayan ancak yalnızca istendiğinde hesaplanan nitelikler.
class Character(object): def __init__(name, max_hp): self._name = name self._hp = max_hp self._max_hp = max_hp # Bir set yöntemi sağlamayarak hp'yi yalnızca okunur hale getirin @property def hp(self): return self._hp # Bir set yöntemi sağlamayarak name'i yalnızca okunur hale getirin @property def name(self): return self.name def take_damage(self, damage): self.hp -= damage self.hp = 0 if self.hp <0 else self.hp @property def is_alive(self): return self.hp != 0 @property def is_wounded(self): return self.hp < self.max_hp if self.hp > 0 else False @property def is_dead(self): return not self.is_alive bilbo = Character('Bilbo Baggins', 100) bilbo.hp # Çıktı : 100 bilbo.hp = 200 # Çıktı: AttributeError: can't set attribute # hp niteliği yalnızca okunur. bilbo.is_alive # Çıktı : True bilbo.is_wounded # Çıktı : False bilbo.is_dead # Çıktı : False bilbo.take_damage( 50 ) bilbo.hp # Çıktı : 50 bilbo.is_alive # Çıktı : True bilbo.is_wounded # Çıktı : True bilbo.is_dead # Çıktı : False bilbo.take_damage( 50 ) bilbo.hp # Çıktı : 0 bilbo.is_alive # Çıktı : False bilbo.is_wounded # Çıktı : False bilbo.is_dead # Çıktı : True
Özellik dekoratörünün kullanımı
Özellik dekoratörü, bir sınıfta nitelikler gibi davranan yöntemleri tanımlamak için kullanılabilir. Bunun yararlı olabileceği bir örnek, başlangıçta (pahalı) bir arama ve daha sonra basit bir erişim gerektirebilecek bilgilerin açığa çıkarılmasıdır.
foobar.py modülü verildiğinde:
class Foo(object): def **init**(self): self.__bar = None @property def bar(self): if self.__bar is None: self.__bar = pahalı\bir\arama\işlevi() return self.__bar
Sonra da:
>>> from foobar import Foo >>> foo = Foo() >>> print(foo.bar) # Başlatma işleminden sonra bar None olduğundan bu işlem biraz zaman alacaktır 42 >>> print(foo.bar) # Bar artık bir değere sahip olduğundan bu çok daha hızlıdır 42
Okuma-yazma özellikleri için özellik dekoratörünü kullanma
Set ve get için özel davranış uygulamak üzere @property kullanmak istiyorsak, aşağıdaki kalıbı kullanırız:
class Cash(object): def __init__(self, value): self.value = value @property def formatted(self): return '${:.2f}'.format(self.value) @formatted.setter def formatted(self, new): self.value = float(new[1:])
Kullanımı:
>>> wallet = Cash(2.50) >>> print(wallet.formatted) $2.50 >>> print(wallet.value) 2.5 >>> wallet.formatted = '$123.45' >>> print(wallet.formatted) $123.45 >>> print(wallet.value) 123.45