คุณสมบัติคลาส Python: คำแนะนำที่ละเอียดเกินไป

เผยแพร่แล้ว: 2022-03-11

ฉันมีสัมภาษณ์การเขียนโปรแกรมเมื่อเร็วๆ นี้ หน้าจอโทรศัพท์ที่เราใช้เครื่องมือแก้ไขข้อความร่วมกัน

ฉันถูกขอให้ใช้ API บางตัว และเลือกทำใน Python การแยกคำสั่งปัญหาออกไป สมมติว่าฉันต้องการคลาสที่อินสแตนซ์เก็บ data บางส่วนและ other_data บางส่วน

ฉันหายใจเข้าลึกๆ แล้วเริ่มพิมพ์ หลังจากสองสามบรรทัดฉันมีสิ่งนี้:

 class Service(object): data = [] def __init__(self, other_data): self.other_data = other_data ...

ผู้สัมภาษณ์หยุดฉัน:

  • ผู้สัมภาษณ์: “บรรทัดนั้น: data = [] . ฉันไม่คิดว่านั่นเป็น Python ที่ถูกต้องใช่ไหม
  • ฉัน: “ฉันค่อนข้างแน่ใจว่ามันเป็น เป็นเพียงการตั้งค่าเริ่มต้นสำหรับแอตทริบิวต์อินสแตนซ์”
  • ผู้สัมภาษณ์: “รหัสนั้นจะถูกเรียกใช้เมื่อใด”
  • ฉัน: “ฉันไม่แน่ใจจริงๆ ฉันจะแก้ไขมันเพื่อหลีกเลี่ยงความสับสน”

เพื่อเป็นข้อมูลอ้างอิง และเพื่อให้คุณได้ทราบว่าฉันกำลังทำอะไรอยู่ นี่คือวิธีที่ฉันแก้ไขโค้ด:

 class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ...

ปรากฎว่าเราผิดทั้งคู่ คำตอบที่แท้จริงคือการทำความเข้าใจความแตกต่างระหว่างแอตทริบิวต์คลาส Python และแอตทริบิวต์อินสแตนซ์ของ Python

แอตทริบิวต์คลาส Python เทียบกับแอตทริบิวต์อินสแตนซ์ของ Python

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

คุณสมบัติคลาส Python

ผู้สัมภาษณ์ของฉันผิดที่โค้ดด้านบน นี้ ถูกต้องตามหลักไวยากรณ์

ฉันเองก็ผิดที่ไม่ได้ตั้งค่า "ค่าเริ่มต้น" สำหรับแอตทริบิวต์อินสแตนซ์ แต่จะกำหนด data เป็นแอตทริบิวต์ คลาส ที่มีค่า [] แทน

จากประสบการณ์ของผม คุณลักษณะคลาส Python เป็นหัวข้อที่ หลาย คนรู้ บางอย่าง เกี่ยวกับบางอย่าง แต่มีเพียงไม่กี่คนที่เข้าใจอย่างถ่องแท้

Python Class Variable กับ Instance Variable: อะไรคือความแตกต่าง?

แอตทริบิวต์คลาส Python เป็นแอตทริบิวต์ของคลาส (ฉันรู้) แบบวงกลม แทนที่จะเป็นแอตทริบิวต์ของ อินสแตนซ์ ของคลาส

ลองใช้ตัวอย่างคลาส Python เพื่อแสดงความแตกต่าง ที่นี่ class_var เป็นแอตทริบิวต์ของคลาส และ i_var เป็นแอตทริบิวต์อินสแตนซ์:

 class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var

โปรดทราบว่าอินสแตนซ์ทั้งหมดของคลาสมีสิทธิ์เข้าถึง class_var และสามารถเข้าถึงได้เป็นคุณสมบัติของ คลาสด้วย :

 foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1

สำหรับโปรแกรมเมอร์ Java หรือ C++ แอตทริบิวต์ class จะคล้ายกัน—แต่ไม่เหมือนกัน—กับสมาชิกแบบสแตติก เราจะมาดูกันว่าพวกเขาแตกต่างกันอย่างไรในภายหลัง

คลาสเทียบกับเนมสเปซอินสแตนซ์

เพื่อให้เข้าใจถึงสิ่งที่เกิดขึ้นที่นี่ มาพูดคุยสั้น ๆ เกี่ยวกับ Python namespaces

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

ขึ้นอยู่กับบริบท คุณอาจต้องเข้าถึงเนมสเปซโดยใช้ไวยากรณ์จุด (เช่น object.name_from_objects_namespace ) หรือเป็นตัวแปรในเครื่อง (เช่น object_from_namespace ) เป็นตัวอย่างที่เป็นรูปธรรม:

 class MyClass(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 MyClass.class_var ## 1

คลาส Python และ อินสแตนซ์ของคลาสแต่ละรายการมีเนมสเปซที่แตกต่างกันซึ่งแสดงโดยแอตทริบิวต์ที่กำหนดไว้ล่วงหน้า MyClass.__dict__ และ instance_of_MyClass.__dict__ ตามลำดับ

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

 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 (MyClass.__dict__) foo.class_var ## 1

เนมสเปซอินสแตนซ์ใช้อำนาจสูงสุดเหนือเนมสเปซคลาส: หากมีแอตทริบิวต์ที่มีชื่อเดียวกันในทั้งสอง อินสแตนซ์เนมสเปซจะถูกตรวจสอบก่อนและคืนค่า นี่คือโค้ด (ซอร์ส) รุ่นที่เรียบง่ายสำหรับการค้นหาแอตทริบิวต์:

 def instlookup(inst, name): ## simplified algorithm... if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name]

และในรูปแบบภาพ:

การค้นหาแอตทริบิวต์ในรูปแบบภาพ

คุณสมบัติของคลาสจัดการกับการมอบหมายอย่างไร

เมื่อคำนึงถึงสิ่งนี้ เราสามารถทำความเข้าใจว่าแอตทริบิวต์คลาส Python จัดการกับการมอบหมายได้อย่างไร:

  • หากแอตทริบิวต์ของคลาสถูกกำหนดโดยการเข้าถึงคลาส แอตทริบิวต์นั้นจะแทนที่ค่าสำหรับอินสแตนซ์ ทั้งหมด ตัวอย่างเช่น:

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

    ที่ระดับเนมสเปซ… เรากำลังตั้งค่า MyClass.__dict__['class_var'] = 2 (หมายเหตุ: นี่ไม่ใช่รหัสที่แน่นอน (ซึ่งจะเป็น setattr(MyClass, 'class_var', 2) ) เนื่องจาก __dict__ ส่งคืน dictproxy ซึ่งเป็น wrapper ที่ไม่เปลี่ยนรูปแบบที่ป้องกันการมอบหมายโดยตรง แต่ช่วยเพื่อการสาธิต) จากนั้น เมื่อเราเข้าถึง foo.class_var class_var มีค่าใหม่ในเนมสเปซคลาส ดังนั้นจึงส่งคืน 2

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

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

    ที่ระดับเนมสเปซ… เรากำลังเพิ่มแอตทริบิวต์ class_var ให้กับ foo.__dict__ ดังนั้นเมื่อเราค้นหา foo.class_var เราคืนค่า 2 ในขณะเดียวกัน อินสแตนซ์อื่นๆ ของ MyClass จะ ไม่มี class_var ในเนมสเปซอินสแตนซ์ ดังนั้นพวกเขาจึงค้นหา class_var ต่อไป ใน MyClass.__dict__ และส่งคืน 1

การกลายพันธุ์

คำถามแบบทดสอบ: เกิดอะไรขึ้นถ้าแอตทริบิวต์ class ของคุณมี ประเภท mutable ? คุณสามารถจัดการ (ทำลาย?) แอตทริบิวต์คลาสโดยการเข้าถึงผ่านอินสแตนซ์เฉพาะและในที่สุดก็ จัดการวัตถุอ้างอิงที่อินสแตนซ์ทั้งหมดกำลังเข้าถึง (ตามที่ Timothy Wiseman ชี้ให้เห็น)

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

 class Service(object): data = [] def __init__(self, other_data): self.other_data = other_data ...

เป้าหมายของฉันคือการมีรายการว่าง ( [] ) เป็นค่าเริ่มต้นสำหรับ data และสำหรับแต่ละอินสแตนซ์ของ Service จะมี ข้อมูลของตัวเอง ที่จะมีการเปลี่ยนแปลงเมื่อเวลาผ่านไปในแต่ละอินสแตนซ์ แต่ในกรณีนี้ เราได้รับพฤติกรรมดังต่อไปนี้ (จำได้ว่า Service ใช้อาร์กิวเมนต์ other_data ซึ่งเป็นข้อโต้แย้งในตัวอย่างนี้):

 s1 = Service(['a', 'b']) s2 = Service(['c', 'd']) s1.data.append(1) s1.data ## [1] s2.data ## [1] s2.data.append(2) s1.data ## [1, 2] s2.data ## [1, 2]

สิ่งนี้ไม่ดี—การเปลี่ยนตัวแปรคลาสผ่านอินสแตนซ์หนึ่งจะทำให้ตัวแปรอื่นๆ เปลี่ยนไปทั้งหมด!

ที่ระดับเนมสเปซ… อินสแตนซ์ทั้งหมดของ Service กำลังเข้าถึงและแก้ไขรายการเดียวกันใน Service.__dict__ โดยไม่ต้องสร้างแอตทริบิวต์ data ของตนเองในเนมสเปซของอินสแตนซ์

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

 s1 = Service(['a', 'b']) s2 = Service(['c', 'd']) s1.data = [1] s2.data = [2] s1.data ## [1] s2.data ## [2]

ในกรณีนี้ เรากำลังเพิ่ม s1.__dict__['data'] = [1] ดังนั้น Service.__dict__['data'] ดั้งเดิมจึงยังคงไม่เปลี่ยนแปลง

น่าเสียดายที่สิ่งนี้ต้องการให้ผู้ใช้ Service มีความรู้อย่างใกล้ชิดเกี่ยวกับตัวแปรและมีแนวโน้มที่จะเกิดข้อผิดพลาดอย่างแน่นอน ในแง่หนึ่ง เราจะจัดการกับอาการมากกว่าที่สาเหตุ เราต้องการสิ่งที่ถูกต้องโดยการก่อสร้าง

วิธีแก้ปัญหาส่วนตัวของฉัน: หากคุณเพียงแค่ใช้ตัวแปรคลาสเพื่อกำหนดค่าเริ่มต้นให้กับตัวแปรอินสแตนซ์ Python ที่น่าจะเป็น อย่าใช้ ค่าที่ไม่แน่นอน ในกรณีนี้ ทุกอินสแตนซ์ของ Service จะแทนที่ Service.data ด้วยแอตทริบิวต์อินสแตนซ์ของตัวเองในที่สุด ดังนั้นการใช้รายการว่างเป็นค่าเริ่มต้นทำให้เกิดจุดบกพร่องเล็กๆ ที่มองข้ามได้ง่าย แทนที่จะเป็นข้างต้น เราอาจทำอย่างใดอย่างหนึ่งต่อไปนี้

  1. ติดอยู่กับแอตทริบิวต์ของอินสแตนซ์ทั้งหมด ตามที่แสดงในบทนำ
  2. หลีกเลี่ยงการใช้รายการที่ว่างเปล่า (ค่าที่ไม่แน่นอน) เป็น "ค่าเริ่มต้น" ของเรา:

     class Service(object): data = None def __init__(self, other_data): self.other_data = other_data ...

    แน่นอน เราจะต้องจัดการกับกรณี None อย่างเหมาะสม แต่นั่นเป็นราคาที่ต้องจ่ายเล็กน้อย

คุณควรใช้แอตทริบิวต์คลาส Python เมื่อใด

แอตทริบิวต์ของคลาสนั้นยุ่งยาก แต่ลองดูบางกรณีที่จะมีประโยชน์:

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

     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
  2. การกำหนดค่าเริ่มต้น ตัวอย่างเช่น เราอาจสร้างรายการที่มีขอบเขต (เช่น รายการที่มีองค์ประกอบจำนวนหนึ่งหรือน้อยกว่านั้น) และเลือกให้มีค่าเริ่มต้นสูงสุด 10 รายการ:

     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

    จากนั้น เราสามารถสร้างอินสแตนซ์ที่มีขีดจำกัดเฉพาะของตนเองได้เช่นกัน โดยกำหนดแอตทริบิวต์ limit ของอินสแตนซ์

     foo = MyClass() foo.limit = 50 ## foo can now hold 50 elements—other instances can hold 10

    วิธีนี้สมเหตุสมผลถ้าคุณต้องการให้อินสแตนซ์ทั่วไปของ MyClass มีองค์ประกอบเพียง 10 องค์ประกอบหรือน้อยกว่า—หากคุณให้ขีดจำกัดที่แตกต่างกันของอินสแตนซ์ทั้งหมด ข้อ limit ควรเป็นตัวแปรอินสแตนซ์ (อย่าลืมว่า: โปรดใช้ความระมัดระวังเมื่อใช้ค่าที่ไม่แน่นอนเป็นค่าเริ่มต้นของคุณ)

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

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

    โปรดทราบว่าในกรณีนี้ names จะเข้าถึงได้เป็นตัวแปรคลาสเท่านั้น ดังนั้นค่าดีฟอลต์ที่เปลี่ยนแปลงได้จึงเป็นที่ยอมรับ

     class Person(object): all_names = [] def __init__(self, name): self.name = name Person.all_names.append(name) joe = Person('Joe') bob = Person('Bob') print Person.all_names ## ['Joe', 'Bob']

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

     class Person(object): all_people = [] def __init__(self, name): self.name = name Person.all_people.append(self) joe = Person('Joe') bob = Person('Bob') print Person.all_people ## [<__main__.Person object at 0x10e428c50>, <__main__.Person object at 0x10e428c90>]
  4. ประสิทธิภาพ (ประเภท… ดูด้านล่าง)

ที่เกี่ยวข้อง: Python Best Practices and Tips โดย Toptal Developers

ภายใต้ประทุน

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

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

 def called_class(): print "Class assignment" return 2 class Bar(object): y = called_class() def __init__(self, x): self.x = x ## "Class assignment" def called_instance(): print "Instance assignment" return 2 class Foo(object): def __init__(self, x): self.y = called_instance() self.x = x Bar(1) Bar(2) Foo(1) ## "Instance assignment" Foo(2) ## "Instance assignment"

เรามอบหมายให้ Bar.y เพียงครั้งเดียว แต่ instance_of_Foo.y ทุกครั้งที่โทรไปที่ __init__

เพื่อเป็นหลักฐานเพิ่มเติม ลองใช้ Python disassembler:

 import dis class Bar(object): y = 2 def __init__(self, x): self.x = x class Foo(object): def __init__(self, x): self.y = 2 self.x = x dis.dis(Bar) ## Disassembly of __init__: ## 7 0 LOAD_FAST 1 (x) ## 3 LOAD_FAST 0 (self) ## 6 STORE_ATTR 0 (x) ## 9 LOAD_CONST 0 (None) ## 12 RETURN_VALUE dis.dis(Foo) ## Disassembly of __init__: ## 11 0 LOAD_CONST 1 (2) ## 3 LOAD_FAST 0 (self) ## 6 STORE_ATTR 0 (y) ## 12 9 LOAD_FAST 1 (x) ## 12 LOAD_FAST 0 (self) ## 15 STORE_ATTR 1 (x) ## 18 LOAD_CONST 0 (None) ## 21 RETURN_VALUE

เมื่อเราดูไบต์โค้ด จะเห็นได้ชัดเจนว่า Foo.__init__ ต้องทำสองงาน ขณะที่ Bar.__init__ เพียงงานเดียว

ในทางปฏิบัติ กำไรนี้มีหน้าตาเป็นอย่างไร? ฉันจะเป็นคนแรกที่ยอมรับว่าการทดสอบเวลาขึ้นอยู่กับปัจจัยที่ควบคุมไม่ได้บ่อยครั้ง และความแตกต่างระหว่างสิ่งเหล่านี้มักจะอธิบายได้ยากอย่างแม่นยำ

อย่างไรก็ตาม ฉันคิดว่าข้อมูลโค้ดเล็กๆ เหล่านี้ (ทำงานด้วยโมดูล Python timeit) ช่วยแสดงให้เห็นความแตกต่างระหว่างตัวแปรคลาสและอินสแตนซ์ ดังนั้นฉันจึงรวมไว้ด้วย

หมายเหตุ: ฉันใช้ MacBook Pro ที่มี OS X 10.8.5 และ Python 2.7.2

การเริ่มต้น

 10000000 calls to `Bar(2)`: 4.940s 10000000 calls to `Foo(2)`: 6.043s

การเริ่มต้นของ Bar เร็วขึ้นกว่าหนึ่งวินาที ดังนั้นความแตกต่างที่นี่จึงปรากฏว่ามีนัยสำคัญทางสถิติ

เหตุใดจึงเป็นเช่นนี้ คำอธิบาย เก็งกำไร หนึ่งรายการ: เราทำสองงานใน Foo.__init__ แต่มีเพียงหนึ่งงานใน Bar.__init__

งานที่มอบหมาย

 10000000 calls to `Bar(2).y = 15`: 6.232s 10000000 calls to `Foo(2).y = 15`: 6.855s 10000000 `Bar` assignments: 6.232s - 4.940s = 1.292s 10000000 `Foo` assignments: 6.855s - 6.043s = 0.812s

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

จากด้านบน ดูเหมือนว่า Foo จะใช้เวลาประมาณ 60% ตราบใดที่ Bar จัดการกับงานที่ได้รับมอบหมาย

ทำไมถึงเป็นเช่นนี้? คำอธิบาย เก็งกำไร อย่างหนึ่ง: เมื่อเรากำหนดให้ Bar(2).y อันดับแรก เราจะดูในเนมสเปซอินสแตนซ์ ( Bar(2).__dict__[y] ) ไม่พบ y แล้วดูในเนมสเปซคลาส ( Bar.__dict__[y] ) จากนั้นทำการมอบหมายที่เหมาะสม เมื่อเรากำหนดให้กับ Foo(2).y เราจะทำการค้นหาเพียงครึ่งเดียว เนื่องจากเรากำหนดให้กับเนมสเปซอินสแตนซ์ทันที ( Foo(2).__dict__[y] )

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

สรุปแล้ว

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

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

ภาคผนวก : ตัวแปรอินสแตนซ์ส่วนตัว

สิ่งหนึ่งที่ฉันต้องการจะรวมไว้ แต่ไม่มีทางเข้าที่เป็นธรรมชาติ...

Python ไม่มีตัวแปร ส่วนตัว ที่สามารถพูดได้ แต่ความสัมพันธ์ที่น่าสนใจอีกอย่างหนึ่งระหว่างการตั้งชื่อคลาสและอินสแตนซ์มาพร้อมกับการเปลี่ยนชื่อ

ในคู่มือสไตล์ Python นั้นกล่าวว่าตัวแปร pseudo-private ควรนำหน้าด้วยเครื่องหมายขีดล่างคู่: '__' นี่ไม่ได้เป็นเพียงสัญญาณสำหรับผู้อื่นว่าตัวแปรของคุณมีไว้เพื่อให้ได้รับการปฏิบัติอย่างเป็นส่วนตัว แต่ยังเป็นวิธีการป้องกันการเข้าถึงตัวแปรอีกด้วย นี่คือสิ่งที่ฉันหมายถึง:

 class Bar(object): def __init__(self): self.__zap = 1 a = Bar() a.__zap ## Traceback (most recent call last): ## File "<stdin>", line 1, in <module> ## AttributeError: 'Bar' object has no attribute '__baz' ## Hmm. So what's in the namespace? a.__dict__ {'_Bar__zap': 1} a._Bar__zap ## 1

ดูสิ: แอตทริบิวต์อินสแตนซ์ __zap ถูกนำหน้าโดยอัตโนมัติด้วยชื่อคลาสที่จะให้ผล _Bar__zap

ในขณะที่ยังคง settable และ gettable โดยใช้ a._Bar__zap ชื่อนี้ mangling เป็นวิธีการสร้างตัวแปร 'ส่วนตัว' เนื่องจากจะป้องกันไม่ให้คุณ และ คนอื่น ๆ เข้าถึงได้โดยบังเอิญหรือด้วยความไม่รู้

แก้ไข: ตามที่ Pedro Werneck ได้กล่าวไว้อย่างกรุณา พฤติกรรมนี้มีจุดมุ่งหมายเพื่อช่วยในการจัดคลาสย่อยเป็นส่วนใหญ่ ในคู่มือรูปแบบ PEP 8 พวกเขาเห็นว่ามีจุดประสงค์สองประการ: (1) การป้องกันคลาสย่อยจากการเข้าถึงแอตทริบิวต์บางอย่าง และ (2) การป้องกันการชนกันของเนมสเปซในคลาสย่อยเหล่านี้ แม้ว่าจะมีประโยชน์ แต่การแปรผันไม่ควรถูกมองว่าเป็นการเชิญชวนให้เขียนโค้ดโดยสันนิษฐานว่ามีความแตกต่างระหว่างภาครัฐและเอกชน เช่น มีอยู่ใน Java

ที่เกี่ยวข้อง: เป็นขั้นสูงมากขึ้น: หลีกเลี่ยง 10 ข้อผิดพลาดที่พบบ่อยที่สุดที่โปรแกรมเมอร์ Python ทำ