ฉันมี สัมภาษณ์การเขียนโปรแกรม เมื่อเร็ว ๆ นี้หน้าจอโทรศัพท์ที่เราใช้ไฟล์ โปรแกรมแก้ไขข้อความที่ทำงานร่วมกัน .
ฉันถูกขอให้ใช้ 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
หมายเหตุ: หากคุณมีผู้เชี่ยวชาญในการจัดการแอตทริบิวต์คลาสคุณสามารถข้ามไปที่ กรณีการใช้งาน .
ผู้สัมภาษณ์ของฉันผิดรหัสข้างต้น คือ ถูกต้องตามหลักไวยากรณ์
ฉันคิดผิดเช่นกันที่ไม่ได้ตั้งค่า 'ค่าเริ่มต้น' สำหรับแอตทริบิวต์อินสแตนซ์ แต่เป็นการกำหนด data
เป็น ชั้นเรียน แอตทริบิวต์ที่มีค่า []
.
จากประสบการณ์ของฉันแอตทริบิวต์คลาส Python เป็นหัวข้อที่ มากมาย คนรู้ บางอย่าง เกี่ยวกับ แต่มีเพียงไม่กี่คนที่เข้าใจอย่างถ่องแท้
แอตทริบิวต์คลาส 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 ++ แอตทริบิวต์คลาสจะคล้ายกัน แต่ไม่เหมือนกันกับสมาชิกแบบคงที่ เราจะมาดูความแตกต่างในภายหลัง
เพื่อทำความเข้าใจว่าเกิดอะไรขึ้นที่นี่เรามาพูดคุยสั้น ๆ เกี่ยวกับ เนมสเปซ Python .
ถึง เนมสเปซ คือการแม็พจากชื่อไปยังอ็อบเจ็กต์โดยมีคุณสมบัติที่มีความสัมพันธ์เป็นศูนย์ระหว่างชื่อในเนมสเปซที่ต่างกัน โดยปกติจะใช้เป็นพจนานุกรม Python แม้ว่าสิ่งนี้จะถูกแยกออกไป
ขึ้นอยู่กับบริบทคุณอาจต้องเข้าถึงเนมสเปซโดยใช้ dot syntax (เช่น 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__
ตามลำดับ
node.js ใช้ทำอะไร
เมื่อคุณพยายามเข้าถึงแอตทริบิวต์จากอินสแตนซ์ของคลาสอันดับแรกจะดูที่ ตัวอย่าง เนมสเปซ หากพบแอตทริบิวต์จะส่งคืนค่าที่เกี่ยวข้อง ถ้าไม่เป็นเช่นนั้น แล้ว ดูในไฟล์ ชั้นเรียน เนมสเปซและส่งคืนแอตทริบิวต์ (หากมีอยู่อาจทำให้เกิดข้อผิดพลาด) ตัวอย่างเช่น:
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__
ส่งคืน a dictproxy ซึ่งเป็นกระดาษห่อหุ้มที่ไม่เปลี่ยนรูปซึ่งป้องกันการมอบหมายงานโดยตรง แต่ช่วยในการสาธิต) จากนั้นเมื่อเราเข้าถึง 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.
คำถามแบบทดสอบ: จะเกิดอะไรขึ้นถ้าแอตทริบิวต์คลาสของคุณมี ประเภทที่ไม่แน่นอน เหรอ? คุณสามารถจัดการ (mutilate?) แอตทริบิวต์คลาสได้โดยการเข้าถึงผ่านอินสแตนซ์เฉพาะและในที่สุดก็จบลง การจัดการวัตถุอ้างอิงที่อินสแตนซ์ทั้งหมดกำลังเข้าถึง (ตามที่ระบุโดย ทิโมธีวิสแมน ).
ตัวอย่างนี้แสดงให้เห็นได้ดีที่สุด กลับไปที่ 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
อย่างเหมาะสม แต่นั่นเป็นราคาที่ต้องจ่ายเล็กน้อย
แอตทริบิวต์ของคลาสเป็นเรื่องยุ่งยาก แต่มาดูบางกรณีที่จะมีประโยชน์:
การจัดเก็บค่าคงที่ . เนื่องจากแอตทริบิวต์ของคลาสสามารถเข้าถึงได้เป็นแอตทริบิวต์ของคลาสนั้น ๆ จึงมักเป็นเรื่องดีที่จะใช้เพื่อจัดเก็บค่าคงที่เฉพาะคลาสสำหรับทั้งคลาส ตัวอย่างเช่น:
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
. เราต้องการติดตามรายชื่อทั้งหมดที่ถูกใช้ แนวทางหนึ่งอาจเป็นไปได้ วนซ้ำรายการวัตถุของคนเก็บขยะ แต่ใช้ตัวแปรคลาสได้ง่ายกว่า
วิธีการปรับแต่งประสิทธิภาพใน sql
โปรดทราบว่าในกรณีนี้ 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 ## [, ]
ประสิทธิภาพ (เรียงลำดับ…ดูด้านล่าง)
บันทึก: หากคุณกังวลเกี่ยวกับประสิทธิภาพในระดับนี้คุณอาจไม่ต้องการใช้ 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 เวลา module) ช่วยแสดงความแตกต่างระหว่างตัวแปรคลาสและอินสแตนซ์ดังนั้นฉันจึงรวมไว้ด้วย
วิธีเข้าถึงสถานีบลูมเบิร์ก
หมายเหตุ: ฉันใช้ 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
หมายเหตุ: ไม่มีวิธีเรียกใช้รหัสการตั้งค่าของคุณซ้ำในการทดลองใช้งานแต่ละครั้งด้วย เวลา ดังนั้นเราจึงต้องเริ่มต้นตัวแปรของเราใหม่ในการทดลองของเรา บรรทัดที่สองแสดงเวลาข้างต้นโดยหักเวลาเริ่มต้นที่คำนวณไว้ก่อนหน้านี้
จากข้างบนดูเหมือน 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 '', line 1, in ## 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
ในขณะที่ยังคงสามารถตั้งค่าและใช้งานได้โดยใช้ a._Bar__zap
ชื่อนี้ mangling เป็นวิธีการสร้างตัวแปร 'ส่วนตัว' เนื่องจากป้องกันไม่ให้คุณ และ ไม่ให้ผู้อื่นเข้าถึงโดยบังเอิญหรือโดยไม่รู้ตัว
แก้ไข: ดังที่ Pedro Werneck ชี้ให้เห็นอย่างชัดเจนพฤติกรรมนี้ส่วนใหญ่มีไว้เพื่อช่วยในการจัดคลาสย่อย ใน คู่มือสไตล์ PEP 8 พวกเขามองว่ามันตอบสนองวัตถุประสงค์สองประการ: (1) การป้องกันคลาสย่อยไม่ให้เข้าถึงคุณลักษณะบางอย่างและ (2) การป้องกันการปะทะกันของเนมสเปซในคลาสย่อยเหล่านี้ แม้ว่าจะมีประโยชน์ แต่ไม่ควรมองว่าการเปลี่ยนแปลงตัวแปรเป็นคำเชิญให้เขียนโค้ดโดยมีความแตกต่างระหว่างสาธารณะกับส่วนตัวเช่นมีอยู่ใน Java
ที่เกี่ยวข้อง: ก้าวสู่ขั้นสูงมากขึ้น: หลีกเลี่ยงข้อผิดพลาดทั่วไป 10 ประการที่โปรแกรมเมอร์ Python ทำตามชื่อที่แนะนำเนมสเปซ Python คือการแมปจากชื่อไปยังอ็อบเจ็กต์โดยมีคุณสมบัติที่ไม่มีความสัมพันธ์เป็นศูนย์ระหว่างชื่อในเนมสเปซที่ต่างกัน โดยปกติเนมสเปซจะถูกนำไปใช้เป็นพจนานุกรม Python แม้ว่าสิ่งนี้จะถูกแยกออกไป
ใน Python เมธอดคลาสคือเมธอดที่เรียกใช้โดยคลาสเป็นบริบท สิ่งนี้มักเรียกว่าวิธีการแบบคงที่ในภาษาโปรแกรมอื่น ๆ ในทางกลับกันวิธีการของอินสแตนซ์จะถูกเรียกใช้โดยมีอินสแตนซ์เป็นบริบท
ในกรณีนี้เนมสเปซอินสแตนซ์จะมีอำนาจสูงสุดเหนือเนมสเปซคลาส หากมีแอตทริบิวต์ที่มีชื่อเดียวกันในทั้งสองอินสแตนซ์เนมสเปซจะถูกตรวจสอบก่อนและส่งคืนค่า