Làm thế nào để tránh việc dữ liệu lớp được chia sẻ giữa các trường hợp?


145

Điều tôi muốn là hành vi này:

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]

Tất nhiên, những gì thực sự xảy ra khi tôi in là:

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

Rõ ràng họ đang chia sẻ dữ liệu trong lớp a. Làm thế nào để tôi có được các trường hợp riêng biệt để đạt được hành vi mà tôi mong muốn?


14
Xin vui lòng, không sử dụng listnhư một tên thuộc tính. listlà một chức năng buil-in để xây dựng một danh sách mới. Bạn nên viết các lớp tên với chữ in hoa.
Marc Tudurí

Câu trả lời:


147

Bạn muốn điều này:

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

Khai báo các biến bên trong khai báo lớp làm cho chúng thành viên "lớp" chứ không phải thành viên thể hiện. Khai báo chúng bên trong __init__phương thức đảm bảo rằng một thể hiện mới của các thành viên được tạo cùng với mọi thể hiện mới của đối tượng, đó là hành vi bạn đang tìm kiếm.


5
Một làm rõ thêm: nếu bạn đã gán lại thuộc tính danh sách trong một trong các trường hợp, nó sẽ không ảnh hưởng đến những người khác. Vì vậy, nếu bạn đã làm một cái gì đó như x.list = [], bạn có thể thay đổi nó và không ảnh hưởng đến bất kỳ người khác. Vấn đề bạn gặp phải là x.listy.listcùng một danh sách, vì vậy khi bạn gọi thêm vào một, nó sẽ ảnh hưởng đến cái khác.
Matt Moriarity

Nhưng tại sao điều này chỉ xảy ra cho danh sách? Khi tôi khai báo một số nguyên hoặc chuỗi bên ngoài init , nó không được chia sẻ giữa các đối tượng? Bất cứ ai có thể chia sẻ bất kỳ liên kết doc đến khái niệm này?
Amal Ts

1
@AmalTs Có vẻ như bạn không hiểu cách gán trong python hoạt động. Xem video này hoặc bài SO này . Hành vi bạn nhìn thấy được gây ra bởi thực tế là bạn đang thay đổi danh sách nhưng đảo ngược các tham chiếu đến ints và chuỗi.
Tiếng Việt là

4
@AmalTs Lưu ý: nó được coi là một thực tiễn xấu khi sử dụng các thuộc tính lớp làm giá trị mặc định "lười biếng" cho các thuộc tính thể hiện. Ngay cả khi các thuộc tính thuộc loại không thay đổi, tốt hơn là gán chúng bên trong __init__.
Tiếng Pháp là

21

Câu trả lời được chấp nhận hoạt động nhưng một chút giải thích không làm tổn thương.

Các thuộc tính lớp không trở thành thuộc tính thể hiện khi một thể hiện được tạo. Chúng trở thành các thuộc tính thể hiện khi một giá trị được gán cho chúng.

Trong mã gốc, không có giá trị nào được gán cho listthuộc tính sau khi khởi tạo; vì vậy nó vẫn là một thuộc tính lớp. Xác định danh sách bên trong __init__các công trình vì __init__được gọi sau khi khởi tạo. Ngoài ra, mã này cũng sẽ tạo ra đầu ra mong muốn:

>>> 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]

Tuy nhiên, kịch bản khó hiểu trong câu hỏi sẽ không bao giờ xảy ra với các đối tượng bất biến như số và chuỗi, vì giá trị của chúng không thể thay đổi nếu không có sự gán. Ví dụ, một mã tương tự như bản gốc với loại thuộc tính chuỗi hoạt động mà không gặp vấn đề gì:

>>> class a:
    string = ''


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

Vì vậy, để tóm tắt: các thuộc tính lớp trở thành thuộc tính thể hiện khi và chỉ khi một giá trị được gán cho chúng sau khi khởi tạo, có trong __init__phương thức hay không . Đây là một điều tốt vì theo cách này bạn có thể có các thuộc tính tĩnh nếu bạn không bao giờ gán giá trị cho một thuộc tính sau khi khởi tạo.


11

Bạn đã khai báo "danh sách" là "thuộc tính cấp lớp" chứ không phải "thuộc tính cấp thể hiện". Để có các thuộc tính nằm trong phạm vi mức, bạn cần khởi tạo chúng thông qua tham chiếu với tham số "tự" trong __init__phương thức (hoặc ở nơi khác tùy thuộc vào tình huống).

Bạn không nhất thiết phải khởi tạo các thuộc tính cá thể trong __init__phương thức nhưng nó giúp dễ hiểu hơn.


11

Mặc dù anwer được chấp nhận là tại chỗ, tôi muốn thêm một chút mô tả.

Hãy làm một bài tập nhỏ

trước hết định nghĩa một lớp như sau:

class A:
    temp = 'Skyharbor'

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

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

vậy chúng ta có gì ở đây nào?

  • Chúng ta có một lớp rất đơn giản có một thuộc tính templà một chuỗi
  • Một __init__phương thức thiết lậpself.x
  • Một bộ phương thức thay đổi self.temp

Khá thẳng về phía trước cho đến nay yeah? Bây giờ hãy bắt đầu chơi xung quanh với lớp này. Trước tiên hãy khởi tạo lớp này:

a = A('Tesseract')

Bây giờ làm như sau:

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

Vâng, a.templàm việc như mong đợi nhưng làm thế quái nào A.templàm việc? Vâng, nó hoạt động vì temp là một thuộc tính lớp. Tất cả mọi thứ trong python là một đối tượng. Ở đây A cũng là một đối tượng của lớp type. Do đó, temp thuộc tính là một thuộc tính được giữ bởi Alớp và nếu bạn thay đổi giá trị của temp thông qua A(và không thông qua một thể hiện a), giá trị thay đổi sẽ được phản ánh trong tất cả các thể hiện của Alớp. Hãy tiếp tục và làm điều đó:

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

Thật thú vị phải không? Và lưu ý rằng id(a.temp)id(A.temp)vẫn giống nhau .

Bất kỳ đối tượng Python nào cũng tự động được cung cấp một __dict__thuộc tính, chứa danh sách các thuộc tính của nó. Chúng ta hãy điều tra những gì từ điển này chứa cho các đối tượng ví dụ của chúng tôi:

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

Lưu ý rằng tempthuộc tính được liệt kê trong số các Athuộc tính của lớp trong khi xđược liệt kê ví dụ.

Vì vậy, tại sao chúng ta có được một giá trị được xác định a.tempnếu nó thậm chí không được liệt kê cho ví dụ a. Vâng, đó là sự kỳ diệu của __getattribute__()phương pháp. Trong Python, cú pháp chấm tự động gọi phương thức này để khi chúng ta viết a.temp, Python thực thi a.__getattribute__('temp'). Phương thức đó thực hiện hành động tra cứu thuộc tính, tức là tìm giá trị của thuộc tính bằng cách tìm ở những nơi khác nhau.

Việc thực hiện tiêu chuẩn __getattribute__()tìm kiếm trước tiên là từ điển nội bộ ( dict ) của một đối tượng, sau đó là loại đối tượng. Trong trường hợp này a.__getattribute__('temp')thực thi đầu tiên a.__dict__['temp']và sau đóa.__class__.__dict__['temp']

Được rồi bây giờ hãy sử dụng changephương pháp của chúng tôi :

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

Bây giờ chúng tôi đã sử dụng self, print(a.temp)cung cấp cho chúng tôi một giá trị khác print(A.temp).

Bây giờ nếu chúng ta so sánh id(a.temp)id(A.temp), chúng sẽ khác nhau.


3

Có, bạn phải khai báo trong "hàm tạo" nếu bạn muốn danh sách đó trở thành thuộc tính đối tượng và không phải là thuộc tính lớp.


3

Vì vậy, gần như mọi phản ứng ở đây dường như bỏ lỡ một điểm cụ thể. Các biến lớp không bao giờ trở thành biến thể hiện như được trình bày bởi đoạn mã dưới đây. Bằng cách sử dụng siêu dữ liệu để chặn việc gán biến ở cấp lớp, chúng ta có thể thấy rằng khi a.myattr được gán lại, phương thức ma thuật gán trường trên lớp không được gọi. Điều này là do sự phân công tạo ra một biến thể hiện mới . Hành vi này hoàn toàn không liên quan gì đến biến lớp như được thể hiện bởi lớp thứ hai không có biến lớp và vẫn cho phép gán trường.

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 NGẮN Các biến lớp không có gì để làm với các biến thể hiện.

Rõ ràng hơn Họ chỉ tình cờ ở trong phạm vi tìm kiếm trên các trường hợp. Các biến lớp trong thực tế là các biến đối tượng trên chính đối tượng lớp. Bạn cũng có thể có các biến siêu dữ liệu nếu bạn muốn vì bản thân siêu dữ liệu cũng là đối tượng. Mọi thứ đều là một đối tượng cho dù nó được sử dụng để tạo các đối tượng khác hay không, vì vậy đừng bị ràng buộc trong ngữ nghĩa của việc sử dụng ngôn ngữ khác của lớp từ. Trong python, một lớp thực sự chỉ là một đối tượng được sử dụng để xác định cách tạo các đối tượng khác và hành vi của chúng sẽ là gì. Metaclass là các lớp tạo ra các lớp, chỉ để minh họa thêm cho điểm này.


0

Để bảo vệ biến của bạn được chia sẻ bởi thể hiện khác, bạn cần tạo biến thể hiện mới mỗi lần bạn tạo một thể hiện. Khi bạn khai báo một biến trong một lớp, đó là biến lớp và được chia sẻ bởi tất cả các thể hiện. Nếu bạn muốn làm cho nó chẳng hạn, cần sử dụng phương thức init để khởi tạo lại biến như tham chiếu đến thể hiện

Từ các đối tượng và lớp Python của Programiz.com :

__init__()chức năng. Hàm đặc biệt này được gọi bất cứ khi nào một đối tượng mới của lớp đó được khởi tạo.

Loại hàm này còn được gọi là các hàm tạo trong Lập trình hướng đối tượng (OOP). Chúng tôi thường sử dụng nó để khởi tạo tất cả các biến.

Ví dụ:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.