Sınıf ve örnek nitelikleri arasındaki fark nedir?


132

Aşağıdakiler arasında anlamlı bir ayrım var mı:

class A(object):
    foo = 5   # some default value

vs.

class B(object):
    def __init__(self, foo=5):
        self.foo = foo

Çok sayıda örnek oluşturuyorsanız, iki stil için performans veya alan gereksinimleri açısından herhangi bir fark var mı? Kodu okuduğunuzda, iki stilin anlamının önemli ölçüde farklı olduğunu düşünüyor musunuz?


1
Burada benzer bir sorunun sorulduğunu ve yanıtlandığını fark ettim: stackoverflow.com/questions/206734/… Bu soruyu silmeli miyim?
Dan Homerick

2
Sorunuz bu, silmekten çekinmeyin. Senin olduğuna göre, neden başkasının fikrini soruyorsun?
S.Lott

Yanıtlar:


146

Performans değerlendirmelerinin ötesinde, önemli bir anlamsal fark vardır. Sınıf özniteliği durumunda, atıfta bulunulan yalnızca bir nesne vardır. Örnek-öznitelik-set-at-somutlamasında, başvurulan birden çok nesne olabilir. Örneğin

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
...  def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo    
[]

4
Yalnızca değiştirilebilir türler paylaşılır. Gibi intve strhala sınıftan ziyade her bir örneğe bağlılar.
Babu

11
@Babu: Hayır, intve strvardır da tamamen aynı şekilde, paylaştı. Bunu isveya ile kolayca kontrol edebilirsiniz id. Veya sadece her bir örneğe __dict__ve sınıflara bakın __dict__. Değişmez türlerin paylaşılıp paylaşılmaması genellikle çok önemli değildir.
abarnert

17
Ancak, yaparsanız a.foo = 5, her iki durumda da b.foogeri dönüş göreceğinizi unutmayın []. Bunun nedeni, ilk durumda, a.fooaynı ada sahip yeni bir örnek özniteliğiyle class özniteliğinin üzerine yazmanızdır .
Konstantin Schubert

39

Aradaki fark, sınıftaki özniteliğin tüm örnekler tarafından paylaşılmasıdır. Bir örnekteki öznitelik o örneğe özgüdür.

C ++ 'dan geliyorsa, sınıftaki öznitelikler daha çok statik üye değişkenleri gibidir.


1
Paylaşılanlar sadece değiştirilebilir tipler değil mi? Kabul edilen cevap, işe yarayan bir liste gösterir, ancak bu bir int ise, attr örneğiyle aynı gibi görünür: >>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5
Rafe

6
@Rafe: Hayır, tüm türler paylaşılır. a.foo.append(5)Kafanızın karışmasının nedeni , değere yeni bir ad a.fooverirken a.foo = 5, değinilen değeri değiştiren şeyin olmasıdır . Böylece, sınıf niteliğini gizleyen bir örnek niteliği elde edersiniz. Alex'in versiyonunda aynısını deneyin ve bunun değişmediğini göreceksiniz . a.foo5a.foo = 5b.foo
abarnert

30

İşte çok güzel bir gönderi ve aşağıdaki gibi özetleyin.

class Bar(object):
    ## No need for dot syntax
    class_var = 1

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

## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = MyClass(2)

## Finds i_var in foo's instance namespace
foo.i_var
## 2

## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1

Ve görsel biçimde

görüntü açıklamasını buraya girin

Sınıf öznitelik ataması

  • Sınıfa erişilerek bir sınıf özelliği ayarlanırsa, tüm örnekler için değeri geçersiz kılar.

    foo = Bar(2)
    foo.class_var
    ## 1
    Bar.class_var = 2
    foo.class_var
    ## 2
  • Bir örneğe erişilerek bir sınıf değişkeni ayarlanırsa, yalnızca o örnek için değeri geçersiz kılar . Bu, esasen sınıf değişkenini geçersiz kılar ve onu, sezgisel olarak yalnızca bu örnek için kullanılabilen bir örnek değişkenine dönüştürür .

    foo = Bar(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    Bar.class_var
    ## 1

Sınıf özelliğini ne zaman kullanırsınız?

  • Sabitleri saklama . Sınıf özniteliklerine sınıfın öznitelikleri olarak erişilebildiğinden, bunları Sınıf çapında, Sınıfa özgü sabitleri depolamak için kullanmak genellikle güzeldir

    class Circle(object):
         pi = 3.14159
    
         def __init__(self, radius):
              self.radius = radius   
        def area(self):
             return Circle.pi * self.radius * self.radius
    
    Circle.pi
    ## 3.14159
    c = Circle(10)
    c.pi
    ## 3.14159
    c.area()
    ## 314.159
  • Varsayılan değerleri tanımlama . Önemsiz bir örnek olarak, sınırlı bir liste (yani, yalnızca belirli sayıda veya daha az sayıda öğeyi tutabilen bir liste) oluşturabilir ve varsayılan olarak 10 öğelik bir sınır seçebiliriz.

    class MyClass(object):
        limit = 10
    
        def __init__(self):
            self.data = []
        def item(self, i):
            return self.data[i]
    
        def add(self, e):
            if len(self.data) >= self.limit:
                raise Exception("Too many elements")
            self.data.append(e)
    
     MyClass.limit
     ## 10

19

Buradaki yorumlarda ve çift olarak işaretlenen diğer iki sorudaki kişilerin hepsi aynı şekilde bu konuda kafaları karışmış göründüğünden, Alex Coventry'nin üzerine ek bir cevap eklemeye değer olduğunu düşünüyorum .

Alex'in bir liste gibi değişebilir bir tipte bir değer ataması gerçeğinin, şeylerin paylaşılıp paylaşılmadığıyla hiçbir ilgisi yoktur. Bunu idişlev veya isoperatörle görebiliriz:

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
...     def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

( object()Diyelim ki, bunun yerine neden kullandığımı merak ediyorsanız 5, bu, burada girmek istemediğim diğer iki konuyla karşılaşmaktan kaçınmak içindir; iki farklı nedenden dolayı, tamamen ayrı olarak oluşturulmuş 5s, sayının aynı örneği 5. Ancak tamamen ayrı olarak oluşturulmuş object()URL'ler olamaz.)


Öyleyse, neden a.foo.append(5)Alex'in örneğinde etkiliyor b.foo, ama a.foo = 5benim örneğimde etkilemiyor? Peki, a.foo = 5Alex'in örneğini deneyin ve b.fooorayı da etkilemediğine dikkat edin .

a.foo = 5sadece a.foobir isim yapmaktır 5. Bu b.foo, ya da a.fooatıfta bulunmak için kullanılan eski değer için başka herhangi bir adı etkilemez . * Bir sınıf özelliğini gizleyen bir örnek niteliği oluşturmamız biraz yanıltıcı olabilir, ** ancak bunu aldığınızda hiçbir şey karmaşık değildir burada oluyor.


Umarım Alex'in neden bir liste kullandığı artık açıktır: bir listeyi değiştirebilmeniz gerçeği, iki değişkenin aynı listeyi adlandırdığını göstermenin daha kolay olduğu anlamına gelir ve aynı zamanda gerçek hayat kodunda iki listeniz olup olmadığını bilmenin daha önemli olduğu anlamına gelir. aynı liste için iki isim.


* C ++ gibi bir dilden gelen insanlar için kafa karışıklığı, Python'da değerlerin değişkenlerde depolanmamasıdır. Değerler değer topraklarında kendi başlarına yaşarlar, değişkenler sadece değerlerin isimleridir ve atama sadece bir değer için yeni bir ad oluşturur. Yardımcı olacaksa, her Python değişkenini a shared_ptr<T>yerine bir T.

** Bazı insanlar, örneklerin ayarlayabileceği veya ayarlayamayacağı bir örnek özelliği için "varsayılan değer" olarak bir sınıf özelliğini kullanarak bundan yararlanır. Bu bazı durumlarda faydalı olabilir, ancak kafa karıştırıcı da olabilir, bu yüzden dikkatli olun.


0

Bir durum daha var.

Sınıf ve örnek nitelikleri Tanımlayıcıdır .

# -*- encoding: utf-8 -*-


class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        return self.val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')


def main():
    b = Base()
    print("Access to class attribute, return: ", Base.attr_1)
    print("Access to instance attribute, return: ", b.attr_2)

if __name__ == '__main__':
    main()

Yukarıdakiler çıktı:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

Sınıf veya örnek üzerinden aynı türden örnek erişimi farklı sonuç döndürür!

Ve c.PyObject_GenericGetAttr tanımında, ve harika bir gönderi buldum .

Açıklamak

Özellik, oluşturan sınıfların sözlüğünde bulunursa. nesneler MRO, sonra aranan özniteliğin bir Veri Tanımlayıcıya işaret edip etmediğini kontrol edin (bu, hem yöntemleri hem __get__de __set__yöntemleri uygulayan bir sınıftan başka bir şey değildir ). Varsa __get__, Veri Tanımlayıcı yöntemini (28-33. Satırlar) çağırarak öznitelik aramasını çözün .

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.