Alt sınıflandırma: Geleneksel bir özniteliğe sahip bir özelliği geçersiz kılmak mümkün mü?


14

Varsayalım, kapsayıcı bir kavramın farklı uygulamaları veya uzmanlıkları olan bir sınıf ailesi oluşturmak istiyoruz. Bazı türetilmiş özellikler için makul bir varsayılan uygulama olduğunu varsayalım. Bunu temel bir sınıfa koymak istiyoruz

class Math_Set_Base:
    @property
    def size(self):
        return len(self.elements)

Böylece bir alt sınıf, bu oldukça aptal örnekte öğelerini otomatik olarak sayabilir

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self,*elements):
        self.elements = elements

Concrete_Math_Set(1,2,3).size
# 3

Ancak, bir alt sınıf bu varsayılanı kullanmak istemezse ne olur? Bu çalışmıyor:

import math

class Square_Integers_Below(Math_Set_Base):
    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

Square_Integers_Below(7)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute

Bir özelliği bir özellik ile geçersiz kılmanın yolları olduğunu fark ettim, ama bundan kaçınmak istiyorum. Temel sınıfın amacı, kullanıcı için hayatı mümkün olduğunca kolaylaştırmak olduğundan, (alt sınıfın dar bakış açısından) kıvrımlı ve gereksiz bir erişim yöntemi uygulayarak şişkinlik katmamaktır.

Bu yapılabilir mi? Değilse bir sonraki en iyi çözüm nedir?

Yanıtlar:


6

Özellik, aynı ada sahip bir örnek özniteliğine göre öncelikli olan bir veri tanımlayıcıdır. Benzersiz bir __get__()yöntemle veri olmayan bir tanımlayıcı tanımlayabilirsiniz : örnek özniteliği , aynı ada sahip veri olmayan tanımlayıcıya göre önceliklidir , belgelere bakın . Buradaki sorun, non_data_propertyaşağıda tanımlanan sadece hesaplama amaçlıdır (bir ayarlayıcı veya silme tanımlayamazsınız), ancak örneğinizde durum böyle görünüyor.

import math

class non_data_property:
    def __init__(self, fget):
        self.__doc__ = fget.__doc__
        self.fget = fget

    def __get__(self, obj, cls):
        if obj is None:
            return self
        return self.fget(obj)

class Math_Set_Base:
    @non_data_property
    def size(self, *elements):
        return len(self.elements)

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self, *elements):
        self.elements = elements


class Square_Integers_Below(Math_Set_Base):
    def __init__(self, cap):
        self.size = int(math.sqrt(cap))

print(Concrete_Math_Set(1, 2, 3).size) # 3
print(Square_Integers_Below(1).size) # 1
print(Square_Integers_Below(4).size) # 2
print(Square_Integers_Below(9).size) # 3

Ancak bu, bu değişiklikleri yapmak için temel sınıfa erişiminiz olduğunu varsayar.


Bu mükemmel yakın. Öyleyse, yalnızca alıcıyı, atamayı engellemekten başka bir şey yapmayan bir ayarlayıcı ekliyorsa, bir poperty anlamına gelir? İlginç. Muhtemelen bu cevabı kabul edeceğim.
Paul Panzer

Evet, dokümanlara göre, bir özelliği her zaman (her üç açıklayıcı yöntemlerini tanımlar __get__(), __set__()ve__delete__() ) ve bir zam AttributeErroronlar için herhangi bir işlev sağlamadıkları takdirde. Bkz . Özellik uygulama Python eşdeğeri .
Arkelis

Mülkiyet setterveya __set__mülkiyet umurumda değil sürece , bu işe yarayacak. Bununla birlikte, bunun sadece sınıf düzeyinde ( self.size = ...) değil, örneğin örnek düzeyinde bile (örneğin Concrete_Math_Set(1, 2, 3).size = 10eşit derecede geçerli olacağını ) özelliğin üzerine kolayca yazabileceğiniz anlamına geldiği konusunda sizi uyarıyorum . Düşünceler için yiyecek :)
r.ook

Ve Square_Integers_Below(9).size = 1basit bir özellik olduğu için de geçerlidir. Bu özel "boyut" kullanım durumunda, bu akward gibi görünebilir (bunun yerine bir özellik kullanın), ancak genel durumda "alt sınıfta hesaplanan bir pervane kolayca geçersiz kılınır", bazı durumlarda iyi olabilir. Ayrıca öznitelik erişimini de kontrol edebilirsiniz, __setattr__()ancak bunaltıcı olabilir.
Arkelis

1
Bunlar için geçerli bir kullanım durumu olabileceğine katılmıyorum, sadece gelecekte hata ayıklama çabalarını zorlaştırabileceği için uyarıdan bahsetmek istedim. Hem siz hem de OP sonuçların farkında olduğunuz sürece her şey yolunda.
r.ook

10

Bu sadece ücretsiz hizmet verebilir uzun soluklu bir cevap olacak ... ama sorunuz da benim bulguları (ve ağrı) paylaşmak istiyorum tavşan deliğinden aşağı bir yolculuk için götürdü.

Nihayetinde bu cevabı asıl sorununuza yardımcı olmayan bulabilirsiniz. Aslında, benim sonucum şu - bunu hiç yapmazdım. Bunu söyledikten sonra, daha fazla ayrıntı arıyorsanız, bu sonucun arka planı sizi biraz eğlendirebilir.


Bazı yanlış anlamaların giderilmesi

İlk cevap, çoğu durumda doğru olsa da, her zaman böyle değildir . Örneğin, şu sınıfı düşünün:

class Foo:
    def __init__(self):
        self.name = 'Foo!'
        @property
        def inst_prop():
            return f'Retrieving {self.name}'
        self.inst_prop = inst_prop

inst_prop, a olmasına rağmen property, geri döndürülemez bir örnek niteliğidir:

>>> Foo.inst_prop
Traceback (most recent call last):
  File "<pyshell#60>", line 1, in <module>
    Foo.inst_prop
AttributeError: type object 'Foo' has no attribute 'inst_prop'
>>> Foo().inst_prop
<property object at 0x032B93F0>
>>> Foo().inst_prop.fget()
'Retrieving Foo!'

Her şey nereye bağlı senin propertyis ilk etapta tanımladı. Eğer @property"kapsam" sınıfında tanımlanmışsa (ya da gerçekten namespace) sınıf özniteliğine dönüşür. Örneğimde, sınıfın kendisi inst_propsomutlaştırılana kadar kimsenin farkında değil . Tabii ki, burada bir mülk olarak çok kullanışlı değil.


Ama önce miras çözümüyle ilgili yorumunuzu ele alalım ...

Peki kalıtım bu konuyu tam olarak nasıl etkiliyor? Aşağıdaki makale konuya ve Yöntem Çözüm Sırası'na biraz dalmaktadır. biraz ilişkilidir, ancak çoğunlukla Derinlik yerine kalıtımın genişliği tartışılmaktadır.

Bulgularımızla birlikte, aşağıdaki kurulum göz önüne alındığında:

@property
def some_prop(self):
    return "Family property"

class Grandparent:
    culture = some_prop
    world_view = some_prop

class Parent(Grandparent):
    world_view = "Parent's new world_view"

class Child(Parent):
    def __init__(self):
        try:
            self.world_view = "Child's new world_view"
            self.culture = "Child's new culture"
        except AttributeError as exc:
            print(exc)
            self.__dict__['culture'] = "Child's desired new culture"

Bu satırlar yürütüldüğünde ne olacağını düşünün:

print("Instantiating Child class...")
c = Child()
print(f'c.__dict__ is: {c.__dict__}')
print(f'Child.__dict__ is: {Child.__dict__}')
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')

Böylece sonuç:

Instantiating Child class...
can't set attribute
c.__dict__ is: {'world_view': "Child's new world_view", 'culture': "Child's desired new culture"}
Child.__dict__ is: {'__module__': '__main__', '__init__': <function Child.__init__ at 0x0068ECD8>, '__doc__': None}
c.world_view is: Child's new world_view
Child.world_view is: Parent's new world_view
c.culture is: Family property
Child.culture is: <property object at 0x00694C00>

Nasıl olduğunu fark et:

  1. self.world_view uygulanırken, self.culturebaşarısız rağmen
  2. cultureiçinde mevcut değil Child.__dict__(mappingproxysınıfta sınıfın, örnekle karıştırılmaması)__dict__ )
  3. Olsa culturevarc.__dict__ kaynak gösterilmemiş.

Neden bir özellik olarak sınıf world_viewüzerine yazıldığını tahmin edebilirsiniz Parent, bu yüzden Childde üzerine yazabilirsiniz. Bu arada, culturekalıtsal olduğundan, sadece aşağıdakilerin içinde mappingproxybulunurGrandparent :

Grandparent.__dict__ is: {
    '__module__': '__main__', 
    'culture': <property object at 0x00694C00>, 
    'world_view': <property object at 0x00694C00>, 
    ...
}

Aslında kaldırmaya çalışırsanız Parent.culture:

>>> del Parent.culture
Traceback (most recent call last):
  File "<pyshell#67>", line 1, in <module>
    del Parent.culture
AttributeError: culture

Bunun için var olmadığını bile fark edeceksiniz Parent. Çünkü nesne doğrudan geri dönüyor Grandparent.culture.


Peki, Karar Emri ne olacak?

Bu yüzden asıl Çözüm Siparişini gözlemlemek istiyoruz, Parent.world_viewbunun yerine kaldırmayı deneyelim :

del Parent.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

Acaba sonuç ne?

c.world_view is: Family property
Child.world_view is: <property object at 0x00694C00>

Önceden world_view propertyatamayı başarılı bir şekilde başarmış olsak da, Büyükbaba'ya geri döndü self.world_view! Ama ya world_viewdiğer cevap gibi, sınıf düzeyinde zorla değişirsek ? Ya silersek? Geçerli sınıf niteliğini bir özellik olarak atarsak ne olur?

Child.world_view = "Child's independent world_view"
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

del c.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

Child.world_view = property(lambda self: "Child's own property")
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

Sonuç:

# Creating Child's own world view
c.world_view is: Child's new world_view
Child.world_view is: Child's independent world_view

# Deleting Child instance's world view
c.world_view is: Child's independent world_view
Child.world_view is: Child's independent world_view

# Changing Child's world view to the property
c.world_view is: Child's own property
Child.world_view is: <property object at 0x020071B0>

Bu ilginçtir, çünkü c.world_viewatadığımız örnek örneğine geri yüklenir Child.world_view. İnstance niteliğini kaldırdıktan sonra, class özelliğine geri döner. Ve Child.world_viewözelliği yeniden atadıktan sonra , örnek özelliğine erişimini anında kaybediyoruz.

Bu nedenle, aşağıdaki çözünürlük sırasını tahmin edebiliriz :

  1. Bir sınıf özniteliği varsa ve bir ise property, değerinigetter veya fget(daha sonra bu konuda daha fazla) yoluyla alın. Geçerli sınıf ilk önce Temel sınıf son.
  2. Aksi takdirde, bir örnek niteliği varsa, örnek niteliği değerini alın.
  3. Aksi takdirde, propertysınıf olmayan özniteliği alın. Geçerli sınıf ilk önce Temel sınıf son.

Bu durumda, kökü kaldıralım property:

del Grandparent.culture
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')

Hangi verir:

c.culture is: Child's desired new culture
Traceback (most recent call last):
  File "<pyshell#74>", line 1, in <module>
    print(f'Child.culture is: {Child.culture}')
AttributeError: type object 'Child' has no attribute 'culture'

Ta-da! Childşimdi cultureiçine zorla sokma kendi kendi vardır c.__dict__. Child.cultureo tanımlanan asla bu yana, tabii ki, yoksa Parentveya Childsınıf özniteliği ve Grandparent'ın çıkarıldı.


Sorunumun temel nedeni bu mu?

Aslında hayır . Atarken biz hala gözlemliyoruz Aldığınız hata, self.cultureolduğu tamamen farklı . Ancak miras emri, zeminin cevabına zemin hazırlar - ki bu da propertykendisidir.

Daha önce bahsedilen getteryöntemin yanı sıra, propertykollarını da birkaç düzgün hile var. Bu durumda en alakalı olan, çizgi ile tetiklenen setterveya fsetyöntemidir self.culture = .... Senin bu yana propertyherhangi uygulamak vermedi setterveya fgetfonksiyonunu, piton ne yapacağını bilmiyor ve bir atar AttributeError(yani yerine can't set attribute).

Ancak bir setteryöntem uyguladıysanız :

@property
def some_prop(self):
    return "Family property"

@some_prop.setter
def some_prop(self, val):
    print(f"property setter is called!")
    # do something else...

ChildSınıfı başlatırken şunları elde edersiniz:

Instantiating Child class...
property setter is called!

An almak yerine, AttributeErrorşimdi some_prop.setteryöntemi çağırıyorsunuz . Böylece nesneniz üzerinde daha fazla kontrol sahibi olursunuz ... önceki bulgularımızla, mülke ulaşmadan önce üzerine yazılmış bir sınıf niteliğine sahip olmamız gerektiğini biliyoruz . Bu, temel sınıf içinde tetikleyici olarak uygulanabilir. İşte yeni bir örnek:

class Grandparent:
    @property
    def culture(self):
        return "Family property"

    # add a setter method
    @culture.setter
    def culture(self, val):
        print('Fine, have your own culture')
        # overwrite the child class attribute
        type(self).culture = None
        self.culture = val

class Parent(Grandparent):
    pass

class Child(Parent):
    def __init__(self):
        self.culture = "I'm a millennial!"

c = Child()
print(c.culture)

Sonuç:

Fine, have your own culture
I'm a millennial!

TA-DAH! Artık devralınan bir mülk üzerinde kendi örnek özelliğinizin üzerine yazabilirsiniz!


Sorun çözüldü mü?

... Pek sayılmaz. Bu yaklaşımla ilgili sorun şu ki, artık uygun bir setteryönteme sahip olamazsınız . Değerlerinizi ayarlamak istediğiniz durumlar vardır property. Ama şimdi set zaman self.culture = ...o olacak her zaman size tanımlanan işlev ne olursa olsun üzerine getter(bu durumda, gerçekten sadece hangi @propertysarılı kısmı. Sen edebilirsiniz daha nüanslı önlemler katmakla kalmıyor öyle ya da böyle her zaman daha fazla sadece dahil edeceğiz self.culture = .... Örneğin:

class Grandparent:
    # ...
    @culture.setter
    def culture(self, val):
        if isinstance(val, tuple):
            if val[1]:
                print('Fine, have your own culture')
                type(self).culture = None
                self.culture = val[0]
        else:
            raise AttributeError("Oh no you don't")

# ...

class Child(Parent):
    def __init__(self):
        try:
            # Usual setter
            self.culture = "I'm a Gen X!"
        except AttributeError:
            # Trigger the overwrite condition
            self.culture = "I'm a Boomer!", True

Bu oluyor waaaaay daha başka cevap daha komplikesize = None sınıf düzeyinde.

Ayrıca ve veya ek yöntemleri işlemek için kendi tanımlayıcınızı yazmayı da düşünebilirsiniz . Ancak günün sonunda , atıfta bulunulduğunda, her zaman önce tetiklenir ve ne zaman başvurulursa her zaman ilk olarak tetiklenir. Denediğim kadar etrafta dolaşmak yok.__get____set__self.culture__get__self.culture = ...__set__


Meselenin özü, IMO

Burada gördüğüm sorun, kekini yiyip yiyemezsin. property, veya gibi yöntemlerden kolay erişime sahip bir tanımlayıcı anlamına gelir . Bu yöntemlerin farklı bir amaca ulaşmasını da istiyorsanız, sadece sorun istiyorsunuz. Belki de yaklaşımı yeniden düşünürüm:getattrsetattr

  1. Bunun propertyiçin gerçekten ihtiyacım var mı?
  2. Bir yöntem bana farklı bir şekilde hizmet edebilir mi?
  3. Bir ihtiyacım varsa property, üzerine yazmam için herhangi bir neden var mı?
  4. Alt sınıf gerçekten aynı aileye ait mi? property geçerli mi?
  5. Herhangi birinin / tümünün üzerine propertyyazmam gerekirse, yeniden atama yanlışlıkla propertys'yi geçersiz kılabileceğinden, ayrı bir yöntem bana yeniden atamaktan daha iyi hizmet eder mi?

Nokta 5 için, benim yaklaşım overwrite_prop()taban sınıfında geçerli sınıf özniteliğinin üzerine yazılan bir yöntem propertyolacaktır , böylece artık tetiklenmez:

class Grandparent:
    # ...
    def overwrite_props(self):
        # reassign class attributes
        type(self).size = None
        type(self).len = None
        # other properties, if necessary

# ...

# Usage
class Child(Parent):
    def __init__(self):
        self.overwrite_props()
        self.size = 5
        self.len = 10

Gördüğünüz gibi, hala biraz çelişkili olsa da, en azından bir şifreden daha açık size = None . Bununla birlikte, nihayetinde, mülkün üzerine yazmayacağım ve tasarımımı kökünden yeniden düşüneceğim.

Şimdiye kadar yaptıysanız - bu yolculuğu benimle yürüdüğünüz için teşekkür ederim. Eğlenceli küçük bir egzersizdi.


2
Vay canına, çok teşekkürler! Bunu sindirmek biraz zaman alacaktır, ama sanırım ben istedim.
Paul Panzer

6

A @propertysınıf düzeyinde tanımlanır. Dokümantasyon nasıl çalıştığını üzerinde ayrıntılı ayrıntılı anlatır, ama o söylemek yeterli ayarı veya alma belirli bir yöntemi çağırmak içine mülkiyet kararlılığını. Ancakproperty bu süreci yöneten nesne sınıfın kendi tanımıyla tanımlanır. Yani, bir sınıf değişkeni olarak tanımlanır, ancak bir örnek değişkeni gibi davranır.

Bunun bir sonucu olarak sınıf düzeyinde serbestçe yeniden atayabilirsiniz :

print(Math_Set_Base.size)
# <property object at 0x10776d6d0>

Math_Set_Base.size = 4
print(Math_Set_Base.size)
# 4

Ve tıpkı diğer herhangi bir sınıf düzeyi adı gibi (örn. Yöntemler), bir alt sınıfta açıkça farklı bir şekilde tanımlayarak geçersiz kılabilirsiniz:

class Square_Integers_Below(Math_Set_Base):
    # explicitly define size at the class level to be literally anything other than a @property
    size = None

    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

print(Square_Integers_Below(4).size)  # 2
print(Square_Integers_Below.size)     # None

Gerçek bir örnek oluşturduğumuzda, örnek değişkeni aynı adın sınıf değişkenini gölgelendirir. propertyNesne normalde bu süreci (yani uygulayarak Alıcılar ve ayarlayıcılar) ama sınıf düzeyindeki adı bir özellik olarak tanımlanmıştır değilken, hiçbir özel olmuyor işlemek için bazı maskaralık kullanır ve başka değişkenin beklediğiniz gibi yani davranır.


Teşekkürler, bu çok basit. Sınıfımın ve atalarının hiçbir yerinde hiçbir özellik olmasa bile, ilk önce miras ağacında, hatta rahatsız etmeden önce bir sınıf düzeyinde isim aramaya devam edeceği anlamına mı geliyor __dict__? Ayrıca, bunu otomatikleştirmenin herhangi bir yolu var mı? Evet, bu sadece bir satır ama proprties vb ayrıntılar aşina değilseniz okumak için çok şifreli bir şeydir
Paul Panzer

@PaulPanzer Bunun arkasındaki prosedürlere bakmadım, bu yüzden size kendime tatmin edici bir cevap veremedim. İsterseniz cpython kaynak kodundan bulmayı deneyebilirsiniz . Süreci otomatikleştirmeye gelince, bunu ilk etapta bir mülk haline getirmekten ya da sadece kodunuzu okuyanların ne yaptığınızı bilmesine izin vermek için bir yorum / doktora eklemek dışında bunu yapmanın iyi bir yolu olduğunu düşünmüyorum. . Gibi bir şey# declare size to not be a @property
Yeşil Pelerin Adam

4

Hiçbir şekilde (atama size) atamanız gerekmez . sizetemel sınıftaki bir özelliktir, bu nedenle alt sınıftaki bu özelliği geçersiz kılabilirsiniz:

class Math_Set_Base:
    @property
    def size(self):
        return len(self.elements)

    # size = property(lambda self: self.elements)


class Square_Integers_Below(Math_Set_Base):

    def __init__(self, cap):
        self._cap = cap

    @property
    def size(self):
        return int(math.sqrt(self._cap))

    # size = property(lambda self: int(math.sqrt(self._cap)))

Kare kökü önceden hesaplayarak (mikro) optimize edebilirsiniz:

class Square_Integers_Below(Math_Set_Base):

    def __init__(self, cap):
        self._size = int(math.sqrt(self._cap))

    @property
    def size(self):
        return self._size

Bu harika bir nokta, sadece bir alt özelliği olan bir üst özelliği geçersiz kılın! +1
r.ook

Bu bir şekilde basit bir şey yapmak, ama ilgilenen ve basit atama yapacak bir memoizing özelliği yazma zorunluluğu empoze etmemek için tio özelliği olmayan bir geçersiz kılma izin vermek için özellikle sormak yaptı. iş.
Paul Panzer

Kodunuzu neden bu kadar karmaşık hale getirmek istediğinizden emin değilim. Bunun sizebir örnek niteliği değil, bir özellik olduğunu fark etmenin küçük bir fiyatı için, fantezi bir şey yapmanız gerekmez .
chepner

Benim kullanım durumum, birçok özelliğe sahip birçok çocuk sınıfı; tüm bunlar için bir yerine 5 satır yazmak zorunda kalmamak belli bir fanteziyi, IMO'yu haklı çıkarıyor.
Paul Panzer

2

sizeSınıfınızda tanımlamak istediğiniz gibi görünüyor :

class Square_Integers_Below(Math_Set_Base):
    size = None

    def __init__(self, cap):
        self.size = int(math.sqrt(cap))

Başka bir seçenek de capsınıfınızda depolamak ve onu sizebir özellik olarak tanımlanmış (temel sınıfın özelliğini geçersiz kılan) ile hesaplamaktır size.


2

Ben böyle bir ayarlayıcı eklemenizi öneririz:

class Math_Set_Base:
    @property
    def size(self):
        try:
            return self._size
        except:
            return len(self.elements)

    @size.setter
    def size(self, value):
        self._size = value

Bu şekilde, varsayılan .sizeözelliği şu şekilde geçersiz kılabilirsiniz :

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self,*elements):
        self.elements = elements

class Square_Integers_Below(Math_Set_Base):
    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

print(Concrete_Math_Set(1,2,3).size) # 3
print(Square_Integers_Below(7).size) # 2

1

Ayrıca sonraki şey yapabilirsiniz

class Math_Set_Base:
    _size = None

    def _size_call(self):
       return len(self.elements)

    @property
    def size(self):
        return  self._size if self._size is not None else self._size_call()

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self, *elements):
        self.elements = elements


class Square_Integers_Below(Math_Set_Base):
    def __init__(self, cap):
        self._size = int(math.sqrt(cap))

Bu temiz, ama gerçekten ihtiyacınız yok _sizeya da _size_callorada. Fonksiyon çağrısında sizebir koşul olarak pişmiş olabilir ve kullanılmayacak ekstra bir sınıf referansı almak yerine try... except... test etmek için kullanabilirsiniz _size. Yine de, size = Nonebunun ilk etapta basit üzerine yazmadan daha şifreli olduğunu hissediyorum .
r.ook
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.