คุณสมบัติคลาส 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
ผู้สัมภาษณ์ของฉันผิดที่โค้ดด้านบน นี้ ถูกต้องตามหลักไวยากรณ์
ฉันเองก็ผิดที่ไม่ได้ตั้งค่า "ค่าเริ่มต้น" สำหรับแอตทริบิวต์อินสแตนซ์ แต่จะกำหนด 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
ด้วยแอตทริบิวต์อินสแตนซ์ของตัวเองในที่สุด ดังนั้นการใช้รายการว่างเป็นค่าเริ่มต้นทำให้เกิดจุดบกพร่องเล็กๆ ที่มองข้ามได้ง่าย แทนที่จะเป็นข้างต้น เราอาจทำอย่างใดอย่างหนึ่งต่อไปนี้
- ติดอยู่กับแอตทริบิวต์ของอินสแตนซ์ทั้งหมด ตามที่แสดงในบทนำ
หลีกเลี่ยงการใช้รายการที่ว่างเปล่า (ค่าที่ไม่แน่นอน) เป็น "ค่าเริ่มต้น" ของเรา:
class Service(object): data = None def __init__(self, other_data): self.other_data = other_data ...
แน่นอน เราจะต้องจัดการกับกรณี
None
อย่างเหมาะสม แต่นั่นเป็นราคาที่ต้องจ่ายเล็กน้อย
คุณควรใช้แอตทริบิวต์คลาส Python เมื่อใด
แอตทริบิวต์ของคลาสนั้นยุ่งยาก แต่ลองดูบางกรณีที่จะมีประโยชน์:
การจัดเก็บค่าคงที่ เนื่องจากสามารถเข้าถึงแอตทริบิวต์ของคลาสเป็นแอตทริบิวต์ของคลาสได้ จึงมักจะดีที่จะใช้เพื่อเก็บค่าคงที่เฉพาะคลาสสำหรับทั้งคลาส ตัวอย่างเช่น:
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
การกำหนดค่าเริ่มต้น ตัวอย่างเช่น เราอาจสร้างรายการที่มีขอบเขต (เช่น รายการที่มีองค์ประกอบจำนวนหนึ่งหรือน้อยกว่านั้น) และเลือกให้มีค่าเริ่มต้นสูงสุด 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
ควรเป็นตัวแปรอินสแตนซ์ (อย่าลืมว่า: โปรดใช้ความระมัดระวังเมื่อใช้ค่าที่ไม่แน่นอนเป็นค่าเริ่มต้นของคุณ)ติดตามข้อมูลทั้งหมดในทุกอินสแตนซ์ของคลาสที่กำหนด นี่เป็นเรื่องเฉพาะเจาะจง แต่ฉันสามารถดูสถานการณ์ที่คุณอาจต้องการเข้าถึงข้อมูลที่เกี่ยวข้องกับทุกอินสแตนซ์ที่มีอยู่ของคลาสที่กำหนด
เพื่อให้สถานการณ์มีความชัดเจนมากขึ้น สมมติว่าเรามีคลาส
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>]
ประสิทธิภาพ (ประเภท… ดูด้านล่าง)
ภายใต้ประทุน
หมายเหตุ: หากคุณกังวลเกี่ยวกับประสิทธิภาพในระดับนี้ คุณอาจไม่ต้องการใช้ 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