จะหลีกเลี่ยงการแบ่งปันข้อมูลคลาสได้อย่างไร?


145

สิ่งที่ฉันต้องการคือพฤติกรรมนี้:

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]

แน่นอนสิ่งที่เกิดขึ้นจริงเมื่อฉันพิมพ์คือ:

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

aเห็นได้ชัดว่าพวกเขาจะใช้ข้อมูลร่วมกันในชั้นเรียน ฉันจะรับอินสแตนซ์ที่แยกจากกันเพื่อให้บรรลุพฤติกรรมที่ฉันต้องการได้อย่างไร


14
กรุณาอย่าใช้listเป็นชื่อแอตทริบิวต์ listเป็นฟังก์ชั่นในการสร้างรายการใหม่ คุณควรเขียนคลาสชื่อด้วยอักษรตัวใหญ่
Marc Tudurí

คำตอบ:


147

คุณต้องการสิ่งนี้:

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

การประกาศตัวแปรภายในการประกาศคลาสทำให้สมาชิก "คลาส" ไม่ใช่สมาชิกอินสแตนซ์ การประกาศภายใน__init__เมธอดทำให้แน่ใจว่ามีการสร้างอินสแตนซ์ใหม่ของสมาชิกข้างๆอินสแตนซ์ใหม่ของวัตถุซึ่งเป็นพฤติกรรมที่คุณกำลังมองหา


5
ชี้แจงเพิ่มเติม: ถ้าคุณจะกำหนดคุณสมบัติรายการในหนึ่งในกรณีนั้นจะไม่ส่งผลกระทบต่อคนอื่น ๆ ดังนั้นหากคุณทำสิ่งที่ชอบx.list = []คุณสามารถเปลี่ยนแปลงได้และไม่ส่งผลกระทบต่อผู้อื่น ปัญหาที่คุณเผชิญคือx.listและy.listเป็นรายการเดียวกันดังนั้นเมื่อคุณโทรต่อท้ายหนึ่งปัญหาจะมีผลต่ออีกปัญหาหนึ่ง
Matt Moriarity

แต่ทำไมถึงเกิดขึ้นกับรายการเท่านั้น? เมื่อฉันประกาศจำนวนเต็มหรือสตริงนอกinitมันไม่ได้ใช้ร่วมกันระหว่างวัตถุ? ทุกคนสามารถแบ่งปันลิงก์เอกสารกับแนวคิดนี้ได้หรือไม่
Amal Ts

1
@AmalTs ดูเหมือนว่าคุณไม่เข้าใจวิธีการทำงานของ Python ดูวิดีโอนี้หรือโพสต์ SOนี้ พฤติกรรมที่คุณเห็นเกิดจากข้อเท็จจริงที่ว่าคุณกำลังกลายพันธุ์รายการ แต่การปฏิเสธการอ้างอิงไปยัง ints และสตริง
АндрейБеньковский

4
@AmalTs หมายเหตุ: ถือว่าเป็นการปฏิบัติที่ไม่ดีในการใช้แอตทริบิวต์ class เป็นค่าเริ่มต้น "สันหลังยาว" สำหรับแอตทริบิวต์ของอินสแตนซ์ __init__แม้ว่าคุณลักษณะที่มีชนิดไม่เปลี่ยนรูปมันจะดีกว่าที่จะกำหนดไว้ภายใน
АндрейБеньковский

21

คำตอบที่ได้รับการยอมรับใช้งานได้ แต่คำอธิบายเพิ่มเติมเล็กน้อยไม่ได้ทำให้เจ็บ

แอ็ตทริบิวต์คลาสจะไม่กลายเป็นแอ็ตทริบิวต์อินสแตนซ์เมื่อสร้างอินสแตนซ์ พวกเขากลายเป็นคุณลักษณะของอินสแตนซ์เมื่อมีการกำหนดค่าให้กับพวกเขา

ในรหัสเดิมไม่มีการกำหนดค่าให้กับlistแอตทริบิวต์หลังจากการเริ่มต้น ดังนั้นจึงยังคงเป็นแอตทริบิวต์ class การกำหนดรายการภายใน__init__งานเพราะ__init__เรียกว่าหลังจากการสร้างอินสแตนซ์ อีกทางหนึ่งรหัสนี้จะผลิตผลลัพธ์ที่ต้องการ:

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

อย่างไรก็ตามสถานการณ์ที่สับสนในคำถามจะไม่เกิดขึ้นกับวัตถุที่ไม่เปลี่ยนรูปเช่นตัวเลขและสตริงเนื่องจากค่าของมันไม่สามารถเปลี่ยนแปลงได้หากไม่ได้รับมอบหมาย ตัวอย่างเช่นรหัสที่คล้ายกับต้นฉบับที่มีประเภทแอตทริบิวต์ของสตริงทำงานได้โดยไม่มีปัญหา:

>>> class a:
    string = ''


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

ดังนั้นเพื่อสรุป: คุณลักษณะระดับกลายเป็นคุณลักษณะเช่นถ้าหากค่าถูกกำหนดให้กับพวกเขาหลังจากที่ instantiation, อยู่ใน__init__วิธีการหรือไม่ นี่เป็นสิ่งที่ดีเพราะวิธีนี้คุณสามารถมีแอตทริบิวต์แบบคงที่หากคุณไม่เคยกำหนดค่าให้กับแอตทริบิวต์หลังจากการเริ่มต้น


11

คุณประกาศ "รายการ" เป็น "คุณสมบัติระดับคลาส" และไม่ใช่ "คุณสมบัติระดับอินสแตนซ์" เพื่อให้มีการกำหนดขอบเขตคุณสมบัติในระดับอินสแตนซ์คุณต้องเริ่มต้นคุณสมบัติเหล่านั้นผ่านการอ้างอิงด้วยพารามิเตอร์ "ตัวเอง" ใน__init__วิธีการ (หรือที่อื่น ๆ ขึ้นอยู่กับสถานการณ์)

คุณไม่จำเป็นต้องเริ่มต้นคุณสมบัติอินสแตนซ์ใน__init__วิธีการอย่างเคร่งครัดแต่จะทำให้เข้าใจง่ายขึ้น


11

แม้ว่าแอนเวอร์ที่ยอมรับนั้นจะเป็นจุดสนใจ แต่ฉันต้องการเพิ่มคำอธิบายเล็กน้อย

มาออกกำลังกายกันเถอะ

ก่อนอื่นให้นิยามคลาสดังนี้:

class A:
    temp = 'Skyharbor'

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

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

แล้วเรามีอะไรอยู่ที่นี่?

  • เรามีคลาสที่ง่ายมากซึ่งมีคุณสมบัติ tempซึ่งเป็นสตริง
  • __init__วิธีการที่ชุดself.x
  • ชุดวิธีการเปลี่ยนแปลง self.temp

ตรงไปตรงมาสวยใช่มั้ย ตอนนี้เรามาเริ่มเล่นกับคลาสนี้กัน มาเริ่มคลาสนี้กันก่อน:

a = A('Tesseract')

ตอนนี้ทำต่อไปนี้:

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

ดีa.tempทำงานตามที่คาดไว้ แต่วิธีนรกไม่A.tempทำงานหรือไม่ มันทำงานได้ดีเพราะ temp เป็นคุณสมบัติคลาส ทุกอย่างในหลามเป็นวัตถุ นี่คือวัตถุของคลาสtypeด้วย ดังนั้น attribute temp จึงเป็น attribute ที่เก็บไว้ในAclass และหากคุณเปลี่ยนค่าของ temp through A(และไม่ผ่านอินสแตนซ์ของa) ค่าที่เปลี่ยนแปลงจะถูกแสดงในอินสแตนซ์ทั้งหมดของAคลาส ไปข้างหน้าและทำสิ่งนั้น:

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

น่าสนใจใช่ไหม และทราบว่าid(a.temp)และid(A.temp)ยังคงเหมือนเดิมยังคงเดิม

วัตถุ Python ใด ๆ จะได้รับ__dict__แอตทริบิวต์โดยอัตโนมัติซึ่งมีรายการแอตทริบิวต์ มาตรวจสอบว่าพจนานุกรมนี้มีอะไรสำหรับวัตถุตัวอย่างของเรา:

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

โปรดทราบว่าtempคุณลักษณะนั้นจะแสดงอยู่ในหมู่Aแอตทริบิวต์ของคลาสขณะที่xมีการระบุไว้สำหรับอินสแตนซ์

แล้วทำไมเราถึงได้ค่าที่กำหนดa.tempว่าถ้ามันไม่อยู่ในรายการสำหรับอินสแตนซ์นั้นaนั้น นั่นคือความมหัศจรรย์ของ__getattribute__()วิธีการ ใน Python ไวยากรณ์ของจุดประเรียกวิธีนี้โดยอัตโนมัติดังนั้นเมื่อเราเขียนa.tempPython จะดำเนินการa.__getattribute__('temp')รันหลาม วิธีการดังกล่าวดำเนินการค้นหาแอ็ตทริบิวต์เช่นค้นหาค่าของแอททริบิวต์โดยการค้นหาในที่ต่างๆ

การดำเนินการตามมาตรฐานของ__getattribute__()การค้นหาอันดับแรกของพจนานุกรมภายใน ( dict ) ของวัตถุจากนั้นชนิดของวัตถุนั้น ในกรณีนี้a.__getattribute__('temp')ดำเนินการครั้งแรกa.__dict__['temp']แล้วa.__class__.__dict__['temp']

ตกลงตอนนี้มาใช้ของเรา changeวิธีการ :

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

ตอนนี้ที่เราได้ใช้ selfแล้วprint(a.temp)ให้คุณค่าที่แตกต่างจากprint(A.temp)เรา

ตอนนี้ถ้าเราเปรียบเทียบid(a.temp)และid(A.temp)พวกเขาจะแตกต่างกัน


3

ใช่คุณต้องประกาศใน "ตัวสร้าง" ถ้าคุณต้องการให้รายการกลายเป็นคุณสมบัติวัตถุและไม่ใช่คุณสมบัติคลาส


3

ดังนั้นเกือบทุกคำตอบที่นี่ดูเหมือนจะพลาดจุดใดจุดหนึ่ง ตัวแปรคลาสไม่เคยเป็นตัวแปรอินสแตนซ์ดังที่แสดงในโค้ดด้านล่าง โดยการใช้ metaclass เพื่อสกัดกั้นการมอบหมายตัวแปรในระดับคลาสเราจะเห็นว่าเมื่อ a.myattr ถูกกำหนดใหม่วิธีการมอบหมายวิเศษในฟิลด์จะไม่ถูกเรียก เพราะนี่คือการกำหนดสร้างตัวแปรตัวอย่างใหม่ พฤติกรรมนี้ไม่มีอะไรเกี่ยวข้องกับตัวแปรคลาสอย่างที่แสดงโดยคลาสที่สองซึ่งไม่มีตัวแปรคลาสและยังอนุญาตการมอบหมายฟิลด์ได้

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

ตัวแปรสั้นระดับไม่มีอะไรเกี่ยวข้องกับตัวแปรอินสแตนซ์

ชัดเจนยิ่งขึ้นพวกเขาเพิ่งอยู่ในขอบเขตการค้นหาในอินสแตนซ์ ตัวแปรคลาสเป็นตัวแปรอินสแตนซ์จริงในอ็อบเจกต์คลาสเอง คุณยังสามารถมีตัวแปร metaclassได้หากคุณต้องการเช่นกันเพราะ metaclasses เองก็เป็นวัตถุเช่นกัน ทุกอย่างเป็นวัตถุไม่ว่าจะใช้เพื่อสร้างวัตถุอื่นหรือไม่ดังนั้นอย่าผูกมัดในความหมายของการใช้ภาษาอื่นของคลาสคำ ในไพ ธ อนคลาสเป็นเพียงวัตถุที่ใช้ในการกำหนดวิธีการสร้างวัตถุอื่นและพฤติกรรมของพวกเขาจะเป็นอย่างไร Metaclasses เป็นคลาสที่สร้างคลาสเพื่อแสดงจุดนี้เพิ่มเติม


0

เพื่อปกป้องตัวแปรของคุณที่ใช้ร่วมกันโดยอินสแตนซ์อื่นคุณต้องสร้างตัวแปรอินสแตนซ์ใหม่ทุกครั้งที่คุณสร้างอินสแตนซ์ เมื่อคุณประกาศตัวแปรภายในคลาสมันเป็นตัวแปรคลาสและแบ่งใช้โดยอินสแตนซ์ทั้งหมด หากคุณต้องการให้อินสแตนซ์อย่างชาญฉลาดจำเป็นต้องใช้เมธอดinitเพื่อกำหนดค่าเริ่มต้นให้กับตัวแปรตามที่อ้างถึงอินสแตนซ์

จากPython Objects และ Classโดย Programiz.com :

__init__()ฟังก์ชัน ฟังก์ชั่นพิเศษนี้จะถูกเรียกเมื่อใดก็ตามที่วัตถุใหม่ของคลาสนั้นถูกสร้างขึ้น

ฟังก์ชันประเภทนี้เรียกว่า constructors ใน Object Oriented Programming (OOP) ปกติแล้วเราจะใช้มันเพื่อเริ่มต้นตัวแปรทั้งหมด

ตัวอย่างเช่น:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.