Sınıf verilerinin örnekler arasında paylaşılmasını nasıl önleyebilirim?


146

Ne istiyorum bu davranış:

class a:
    list = []

x = a()
y = a()

x.list.append(1)
y.list.append(2)
x.list.append(3)
y.list.append(4)

print(x.list) # prints [1, 3]
print(y.list) # prints [2, 4]

Tabii ki, yazdırdığımda gerçekten olan şey:

print(x.list) # prints [1, 2, 3, 4]
print(y.list) # prints [1, 2, 3, 4]

Açıkça sınıfta veri paylaşıyorlar a. Arzu ettiğim davranışı elde etmek için nasıl ayrı örnekler alabilirim?


14
Lütfen listözellik adı olarak kullanmayın . listyeni bir liste oluşturmak için bir inşa işlevidir. İsim sınıflarını büyük harfle yazmalısınız.
Marc Tudurí

Yanıtlar:


147

Bunu istiyorsun:

class a:
    def __init__(self):
        self.list = []

Sınıf bildirimi içindeki değişkenlerin bildirilmesi, onları örnek üyeleri değil, "sınıf" üyeleri yapar. Onları __init__yöntemin içinde bildirmek , aradığınız davranış olan nesnenin her yeni örneğiyle birlikte üyelerin yeni bir örneğinin oluşturulmasını sağlar.


5
Ek bir açıklama: list özelliğini örneklerden birinde yeniden atayacak olsaydınız, diğerlerini etkilemezdi. Yani böyle bir şey yaptıysanız x.list = [], onu değiştirebilir ve başkalarını etkilemezsiniz. Karşılaştığınız sorun şudur x.listve y.listaynı listedir, bu yüzden birine append çağırdığınızda diğerini etkiler.
Matt Moriarity

Peki bu neden sadece liste için oluyor? Ben init dışında bir tamsayı veya dize ilan , nesneler arasında paylaşılmadı? Herkes bu kavram ile herhangi bir doc bağlantısını paylaşabilir miyim?
Amal Ts

1
@AmalTs Python'daki atamanın nasıl çalıştığını anlamadığınız anlaşılıyor. Bkz Bu videoyu veya bu SO yazı . Gördüğünüz davranış, listeleri mutasyona uğratmanız, ancak ints ve dizelere referansları yeniden hatırlamanızdan kaynaklanır.
Андрей Беньковский

4
@AmalTs Not: sınıf niteliklerini örnek nitelikleri için "tembel" varsayılan değerler olarak kullanmak kötü bir uygulama olarak kabul edilir. Nitelikler değişmez bir tipte olsa bile, bunları içerisine atamak daha iyidir __init__.
Андрей Беньковский

21

Kabul edilen cevap işe yarıyor ama biraz daha fazla açıklama zarar vermiyor.

Bir örnek oluşturulduğunda sınıf nitelikleri örnek niteliklerine dönüşmez. Kendilerine bir değer atandığında örnek nitelikleri haline gelirler.

Orijinal kodda, listsomutlaştırmadan sonra niteliğe hiçbir değer atanmaz ; bu yüzden bir sınıf niteliği olarak kalır. __init__İşlerin içindeki listenin tanımlanması __init__, örneklemeden sonra çağrılır. Alternatif olarak, bu kod da istenen çıktıyı üretecektir:

>>> class a:
    list = []

>>> y = a()
>>> x = a()
>>> x.list = []
>>> y.list = []
>>> x.list.append(1)
>>> y.list.append(2)
>>> x.list.append(3)
>>> y.list.append(4)
>>> print(x.list)
[1, 3]
>>> print(y.list)
[2, 4]

Bununla birlikte, sorudaki kafa karıştırıcı senaryo asla sayılar ve dizeler gibi değişmez nesnelere gerçekleşmeyecektir, çünkü değerleri atama olmadan değiştirilemez. Örneğin, string öznitelik türüne sahip orijinaline benzer bir kod sorunsuz çalışır:

>>> class a:
    string = ''


>>> x = a()
>>> y = a()
>>> x.string += 'x'
>>> y.string += 'y'
>>> x.string
'x'
>>> y.string
'y'

Özetlemek gerekirse: sınıf öznitelikleri, yalnızca, örneklemeden sonra, __init__yöntemde olup olmadığına kendilerine bir değer atandığında örnek öznitelikleri haline gelir . Bu iyi bir şey çünkü örneklemeden sonra bir özniteliğe asla değer atamazsanız bu şekilde statik özniteliklere sahip olabilirsiniz.


11

"Liste" yi "örnek düzeyinde özellik" olarak değil "sınıf düzeyinde özellik" olarak bildirdiniz. Özelliklerin örnek düzeyinde kapsamlandırılması için, __init__yöntemdeki (veya duruma bağlı başka bir yerde) "self" parametresine başvurarak bunları başlatmanız gerekir .

__init__Yöntemdeki örnek özelliklerini kesinlikle başlatmanız gerekmez, ancak bu daha kolay anlaşılmasını sağlar.


11

Kabul edilen anwer açık olmasına rağmen, biraz açıklama eklemek istiyorum.

Küçük bir egzersiz yapalım

her şeyden önce bir sınıfı aşağıdaki gibi tanımlayın:

class A:
    temp = 'Skyharbor'

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

    def change(self, y):
        self.temp = y

Peki burada neyimiz var?

  • tempBir dize niteliği olan çok basit bir sınıfımız var
  • Ayarlayan bir __init__yöntemself.x
  • Bir değişiklik yöntemi kümeleri self.temp

Şimdiye kadar oldukça düz evet? Şimdi bu sınıfla oynamaya başlayalım. Önce bu sınıfı başlatalım:

a = A('Tesseract')

Şimdi aşağıdakileri yapın:

>>> print(a.temp)
Skyharbor
>>> print(A.temp)
Skyharbor

Eh, a.tempbeklendiği gibi çalıştı ama cehennem nasıl yaptığını A.tempişi? İyi çalıştı çünkü temp bir sınıf niteliğidir. Python'daki her şey bir nesnedir. Burada A aynı zamanda bir sınıf nesnesidir type. Bu nedenle temp özniteliği Asınıf tarafından tutulan bir özniteliktir ve temp değerini A(aracılığıyla bir örnekle değil a) değiştirirseniz, değiştirilen değer tüm Asınıf örneğine yansıtılır . Devam edelim ve bunu yapalım:

>>> A.temp = 'Monuments'
>>> print(A.temp)
Monuments
>>> print(a.temp)
Monuments

İlginç değil mi? Ve unutmayın id(a.temp)ve id(A.temp)hala aynı .

Herhangi bir Python nesnesine, __dict__öznitelik listesini içeren bir öznitelik otomatik olarak verilir . Bu sözlüğün örnek nesnelerimiz için neler içerdiğini araştıralım:

>>> print(A.__dict__)
{
    'change': <function change at 0x7f5e26fee6e0>,
    '__module__': '__main__',
    '__init__': <function __init__ at 0x7f5e26fee668>,
    'temp': 'Monuments',
    '__doc__': None
}
>>> print(a.__dict__)
{x: 'Tesseract'}

O Not tempniteliği arasında yer almaktadır Aiken sınıfın niteliklerini xörneğin listelenir.

Öyleyse a.temp, örnek için listelenmemiş olsa bile, tanımlı bir değer elde edelim a. Bu __getattribute__()yöntemin büyüsü . Python'da noktalı sözdizimi otomatik olarak bu yöntemi çağırır a.temp, böylece yazdığımızda Python yürütülür a.__getattribute__('temp'). Bu yöntem, özellik arama eylemini gerçekleştirir, yani farklı yerlere bakarak özelliğin değerini bulur.

Standart uygulama __getattribute__()ilk önce bir nesnenin dahili sözlüğünü ( dict ), sonra da nesnenin türünü arar . Bu durumda a.__getattribute__('temp')önce a.__dict__['temp']ve sonraa.__class__.__dict__['temp']

Tamam şimdi changeyöntemimizi kullanalım :

>>> a.change('Intervals')
>>> print(a.temp)
Intervals
>>> print(A.temp)
Monuments

Şimdi kullandık self, print(a.temp)bize farklı bir değer veriyor print(A.temp).

Şimdi karşılaştırırsak id(a.temp)ve id(A.temp)farklı olurlar.


3

Evet, listenin bir class özelliği değil, bir object özelliği olmasını istiyorsanız "yapıcı" içinde bildirmeniz gerekir.


3

Yani hemen hemen her yanıt belirli bir noktayı kaçırıyor gibi görünüyor. Sınıf değişkenleri hiçbir zaman aşağıdaki kodda gösterildiği gibi örnek değişkenleri haline gelmez . Sınıf düzeyinde değişken atamayı durdurmak için bir metasınıf kullanarak, a.myattr yeniden atandığında, sınıftaki alan atama sihirli yönteminin çağrılmadığını görebiliriz. Bunun nedeni atamanın yeni bir örnek değişkeni oluşturmasıdır . Bu davranışın, sınıf değişkenleri olmayan ve yine de alan atamasına izin veren ikinci sınıf tarafından gösterildiği gibi, sınıf değişkeni ile hiçbir ilgisi yoktur.

class mymeta(type):
    def __init__(cls, name, bases, d):
        pass

    def __setattr__(cls, attr, value):
        print("setting " + attr)
        super(mymeta, cls).__setattr__(attr, value)

class myclass(object):
    __metaclass__ = mymeta
    myattr = []

a = myclass()
a.myattr = []           #NOTHING IS PRINTED
myclass.myattr = [5]    #change is printed here
b = myclass()
print(b.myattr)         #pass through lookup on the base class

class expando(object):
    pass

a = expando()
a.random = 5            #no class variable required
print(a.random)         #but it still works

IN SHORT Sınıf değişkenlerinin örnek değişkenlerle ilgisi yoktur.

Daha açık bir ifadeyle, sadece örneklerin aranması kapsamındadırlar. Sınıf değişkenleri aslında sınıf nesnesinin kendisindeki örnek değişkenlerdir . İsterseniz metasınıf değişkenleri de olabilir, çünkü metasınıfların kendileri de nesnedir. Her şey başka nesneler oluşturmak için kullanılsın ya da kullanılmasın bir nesnedir, bu nedenle sınıf kelimesinin diğer dillerin kullanımının anlambilimine bağlı kalmayın. Python'da, sınıf gerçekten sadece diğer nesnelerin nasıl oluşturulacağını ve davranışlarının ne olacağını belirlemek için kullanılan bir nesnedir. Metasınıflar sadece bu noktayı daha da açıklamak için sınıflar yaratan sınıflardır.


0

Başka bir örnek tarafından paylaşılan değişkeninizi korumak için, her örnek oluşturduğunuzda yeni örnek değişkeni oluşturmanız gerekir. Bir sınıf içindeki bir değişkeni bildirirken, sınıf değişkeni olur ve tüm örnekler tarafından paylaşılır. Örneğin akıllıca yapmak istiyorsanız , değişkeni örneğe başvururken yeniden başlatmak için init yöntemini kullanmanız gerekir

Gönderen Python Nesneleri ve Sınıf Programiz.com tarafından :

__init__()işlevi. Bu özel işlev, söz konusu sınıfın yeni bir nesnesi her başlatıldığında çağrılır.

Bu tür bir işleve Nesne Tabanlı Programlama (OOP) içinde yapıcılar da denir. Normalde tüm değişkenleri başlatmak için kullanırız.

Örneğin:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance
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.