ชื่องูหลาม


110

ในภาษาอื่น ๆ แนวทางทั่วไปที่ช่วยในการสร้างโค้ดที่ดีขึ้นคือทำให้ทุกอย่างถูกซ่อนไว้ให้มากที่สุด หากมีข้อสงสัยว่าตัวแปรควรเป็นแบบส่วนตัวหรือได้รับการปกป้องควรเลือกใช้แบบส่วนตัว

สิ่งเดียวกันนี้ถือเป็นจริงสำหรับ Python หรือไม่? ในตอนแรกฉันควรใช้เครื่องหมายขีดล่างสองอันกับทุกอย่างในตอนแรกและทำให้ซ่อนน้อยลง (ขีดล่างเดียวเท่านั้น) ตามที่ฉันต้องการ

ถ้าการประชุมต้องใช้ขีดล่างเพียงอันเดียวฉันก็อยากทราบเหตุผลเช่นกัน

นี่เป็นความคิดเห็นของผมที่เหลืออยู่บนคำตอบ JBernardo ของ อธิบายว่าทำไมฉันถึงถามคำถามนี้และทำไมฉันถึงอยากรู้ว่าทำไม Python ถึงแตกต่างจากภาษาอื่น:

ฉันมาจากภาษาที่ฝึกให้คุณคิดว่าทุกอย่างควรเป็นแบบสาธารณะเท่าที่จำเป็นเท่านั้นและไม่มีอีกแล้ว เหตุผลก็คือสิ่งนี้จะลดการอ้างอิงและทำให้รหัสปลอดภัยในการแก้ไขมากขึ้น วิธีที่ Python ทำในสิ่งที่ตรงกันข้าม - เริ่มจากที่สาธารณะและไปที่ซ่อน - เป็นเรื่องแปลกสำหรับฉัน

คำตอบ:


183

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

class Stack(object):

    def __init__(self):
        self.__storage = [] # Too uptight

    def push(self, value):
        self.__storage.append(value)

เขียนสิ่งนี้โดยค่าเริ่มต้น:

class Stack(object):

    def __init__(self):
        self.storage = [] # No mangling

    def push(self, value):
        self.storage.append(value)

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

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

class Stack(object):

    def __init__(self):
        self._storage = [] # This is ok but pythonistas use it to be relaxed about it

    def push(self, value):
        self._storage.append(value)

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

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

แล้วขีดล่างคู่ล่ะ? ดีขีดมายากลคู่ส่วนใหญ่จะใช้เพื่อหลีกเลี่ยงการบรรทุกเกินพิกัดอุบัติเหตุของวิธีการและความขัดแย้งที่มีคุณลักษณะที่ชื่อ superclasses' จะมีประโยชน์มากหากคุณเขียนคลาสที่คาดว่าจะขยายออกไปหลาย ๆ ครั้ง

หากคุณต้องการใช้เพื่อวัตถุประสงค์อื่นคุณสามารถทำได้ แต่ไม่เป็นปกติหรือไม่แนะนำ

แก้ไข : ทำไมถึงเป็นเช่นนั้น? สไตล์ Python ตามปกติไม่ได้เน้นการทำให้สิ่งต่างๆเป็นส่วนตัวในทางตรงกันข้าม! มีสาเหตุหลายประการ - ส่วนใหญ่ขัดแย้งกัน ... ให้เราดูบางส่วน

Python มีคุณสมบัติ

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

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

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

person.age = age;

ในรหัสของคุณเพื่อให้เราพูดว่า

person.setAge(age);

setAge() ความเป็น:

public void setAge(int age) {
    if (age >= 0) {
        this.age = age;
    } else {
        this.age = 0;
    }
}

ดังนั้นใน Java (และภาษาอื่น ๆ ) ค่าเริ่มต้นคือการใช้ getters และ setters ต่อไปเพราะอาจเป็นเรื่องน่ารำคาญในการเขียน แต่อาจทำให้คุณเสียเวลาได้มากหากคุณพบว่าตัวเองอยู่ในสถานการณ์ที่ฉันได้อธิบายไว้

อย่างไรก็ตามคุณไม่จำเป็นต้องทำใน Python เนื่องจาก Python มีคุณสมบัติ หากคุณมีคลาสนี้:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self.age = age

จากนั้นคุณจึงตัดสินใจที่จะตรวจสอบอายุคุณไม่จำเป็นต้องเปลี่ยนperson.age = ageส่วนของรหัสของคุณ เพียงแค่เพิ่มคุณสมบัติ (ตามที่แสดงด้านล่าง)

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

ถ้าคุณทำได้และยังใช้ได้อยู่ person.age = ageทำไมคุณถึงเพิ่มฟิลด์ส่วนตัวและ getters และ setters?

(โปรดดูที่Python ไม่ใช่ Javaและบทความนี้เกี่ยวกับอันตรายของการใช้ getters และ setters )

ทุกอย่างสามารถมองเห็นได้ - และการพยายามซ่อนก็ทำให้งานของคุณซับซ้อนขึ้น

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

เนื่องจาก Python เป็นภาษาที่มีไดนามิกมากจึงเป็นเพียงการต่อต้านการเพิ่มภาระนี้ให้กับชั้นเรียนของคุณ

ปัญหาคือไม่สามารถมองเห็นได้ - จำเป็นต้องดู

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

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

กุยโดกล่าวเช่นนั้น

อย่างนี้ไม่ได้เป็นความขัดแย้ง: เขากล่าวว่าเป็นเช่นนั้นจริง (มองหา "ชุดกิโมโนแบบเปิด")

นี่คือวัฒนธรรม

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

เนื่องจากมีวัฒนธรรมนี้อยู่แล้วจึงขอแนะนำให้ปฏิบัติตาม มิฉะนั้นคุณจะรำคาญโดยโปรแกรมเมอร์ Python ที่บอกให้คุณลบออก__จากโค้ดของคุณเมื่อคุณถามคำถามใน Stack Overflow :)


1. Encapsulation มีไว้เพื่อป้องกันคลาสที่ไม่เปลี่ยนแปลง ไม่ซ่อนรายละเอียดที่ไม่จำเป็นจากโลกภายนอกเพราะจะเป็นการรบกวน 2. "ประเด็นคือ API ของคุณควรจะดีและส่วนที่เหลือคือรายละเอียด" นี่คือเรื่องจริง และแอตทริบิวต์สาธารณะเป็นส่วนหนึ่งของ API ของคุณ นอกจากนี้บางครั้งตัวตั้งค่าสาธารณะก็เหมาะสม (เกี่ยวกับค่าคงที่ของคลาสของคุณ) และบางครั้งก็ไม่เหมาะสม API ที่มีตัวตั้งค่าสาธารณะที่ไม่ควรเปิดเผยต่อสาธารณะ (เสี่ยงต่อการละเมิดค่าคงที่) เป็น API ที่ไม่ดี ซึ่งหมายความว่าคุณต้องคิดถึงการมองเห็นของตัวตั้งค่าแต่ละตัวอยู่ดีและการมี 'ค่าเริ่มต้น' นั้นมีความหมายน้อยกว่า
พฤหัสบดี

21

แรก - ชื่อ mangling คืออะไร?

ชื่อ mangling จะถูกเรียกใช้เมื่อคุณอยู่ในนิยามคลาสและใช้__any_nameหรือ__any_name_นั่นคือขีดล่างสองตัว (หรือมากกว่า) ที่นำหน้าและขีดล่างสุด

class Demo:
    __any_name = "__any_name"
    __any_other_name_ = "__any_other_name_"

และตอนนี้:

>>> [n for n in dir(Demo) if 'any' in n]
['_Demo__any_name', '_Demo__any_other_name_']
>>> Demo._Demo__any_name
'__any_name'
>>> Demo._Demo__any_other_name_
'__any_other_name_'

เมื่อมีข้อสงสัยให้ทำอย่างไร

การใช้อย่างชัดเจนคือการป้องกันไม่ให้คลาสย่อยใช้แอตทริบิวต์ที่คลาสใช้

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

ข้อเสียคือมันเพิ่มภาระการรับรู้สำหรับการอ่านและทำความเข้าใจฐานของโค้ดและโดยเฉพาะอย่างยิ่งเมื่อทำการดีบั๊กที่คุณเห็นชื่อขีดล่างคู่ในซอร์สและชื่อที่ยุ่งเหยิงในดีบักเกอร์

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

คุณจำเป็นต้องตระหนักถึงมันเพื่อที่คุณจะได้รู้เมื่อคุณเห็นมัน

PEP 8

PEP 8ซึ่งเป็นคู่มือรูปแบบไลบรารีมาตรฐาน Python ในปัจจุบันกล่าวว่า (ย่อ):

มีความขัดแย้งบางอย่างเกี่ยวกับการใช้__names.

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

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

  2. การโกงชื่อสามารถใช้ประโยชน์บางอย่างเช่นการดีบักและ__getattr__()สะดวกน้อยกว่า อย่างไรก็ตามอัลกอริทึมการโกงชื่อได้รับการบันทึกไว้อย่างดีและใช้งานง่ายด้วยตนเอง

  3. ทุกคนไม่ชอบชื่อที่สับสน พยายามสร้างสมดุลให้กับความต้องการเพื่อหลีกเลี่ยงการปะทะกันของชื่อโดยไม่ได้ตั้งใจกับการใช้งานของผู้โทรขั้นสูง

มันทำงานอย่างไร?

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

>>> class Foo(object):
...     __foobar = None
...     _foobaz = None
...     __fooquux__ = None
... 
>>> [name for name in dir(Foo) if 'foo' in name]
['_Foo__foobar', '__fooquux__', '_foobaz']

โปรดทราบว่าชื่อจะแหลกเหลวก็ต่อเมื่อมีการแยกวิเคราะห์นิยามคลาส:

>>> Foo.__test = None
>>> Foo.__test
>>> Foo._Foo__test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '_Foo__test'

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

ขีดเดียว?

ถ้าการประชุมต้องใช้ขีดล่างเพียงอันเดียวฉันก็อยากทราบเหตุผลเช่นกัน

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

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


16

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

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

กลยุทธ์ของฉันใน Python คือ:

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

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

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


9

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

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

ความแตกต่างระหว่าง _ และ __ คือ python พยายามบังคับใช้คำหลัง แน่นอนว่ามันไม่ได้พยายามอย่างหนัก แต่มันทำให้ยาก การที่ _ เพียงบอกโปรแกรมเมอร์คนอื่น ๆ ว่าเจตนาคืออะไรพวกเขามีอิสระที่จะเพิกเฉยต่ออันตราย แต่การเพิกเฉยต่อกฎนั้นบางครั้งก็เป็นประโยชน์ ตัวอย่างเช่นการดีบักการแฮ็กชั่วคราวและการทำงานกับรหัสของบุคคลที่สามที่ไม่ได้ตั้งใจให้ใช้ในลักษณะที่คุณใช้


6

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

หากคุณดูที่ Java / C # ทั้งคู่มี private / protected / public ทั้งหมดนี้เป็นโครงสร้างเวลาคอมไพล์โครงสร้างเวลารวบรวมมีการบังคับใช้ในช่วงเวลาของการรวบรวมเท่านั้น หากคุณจะใช้การสะท้อนใน Java / C # คุณสามารถเข้าถึงวิธีส่วนตัวได้อย่างง่ายดาย

ทุกครั้งที่คุณเรียกใช้ฟังก์ชันใน Python คุณจะใช้การสะท้อนกลับโดยเนื้อแท้ โค้ดเหล่านี้เหมือนกันใน Python

lst = []
lst.append(1)
getattr(lst, 'append')(1)

ไวยากรณ์ "dot" เป็นเพียงน้ำตาลในเชิงไวยากรณ์สำหรับโค้ดส่วนหลังเท่านั้น ส่วนใหญ่เป็นเพราะการใช้ getattr นั้นน่าเกลียดอยู่แล้วด้วยการเรียกใช้ฟังก์ชันเพียงครั้งเดียว มันแย่ลงจากที่นั่น

ด้วยเหตุนี้จึงไม่มีไพรเวตเวอร์ชัน Java / C # เนื่องจาก Python ไม่รวบรวมโค้ด Java และ C # ไม่สามารถตรวจสอบได้ว่าฟังก์ชันนั้นเป็นแบบส่วนตัวหรือแบบสาธารณะที่รันไทม์เนื่องจากข้อมูลนั้นหายไป (และไม่มีความรู้ว่าฟังก์ชันถูกเรียกใช้จากที่ใด)

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

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


4

อันดับแรก: ทำไมคุณถึงต้องการซ่อนข้อมูลของคุณ? ทำไมสิ่งนั้นจึงสำคัญ?

เวลาส่วนใหญ่คุณไม่อยากทำ แต่คุณทำเพราะคนอื่นกำลังทำ

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

นั่นคือวิถีชีวิตของเราและเราก็โอเคกับสิ่งนั้น

การใช้ขีดล่างสองอันจะทำให้คลาสของคุณแย่มากกับคลาสย่อยจนคุณไม่อยากทำงานแบบนั้น


2
คุณไม่ระบุเหตุผลที่ขีดล่างคู่ไม่ดีสำหรับคลาสย่อย ... สิ่งนี้จะช่วยปรับปรุงคำตอบของคุณ
Matt Joiner

2
เนื่องจากการขีดล่างสองครั้งเป็นเพียงการป้องกันการชนชื่อกับคลาสย่อยเท่านั้น (เช่นเดียวกับการพูดว่า "ส่งต่อ" ไปยังคลาสย่อย) ฉันไม่เห็นว่าการโกงชื่อสร้างปัญหาอย่างไร
Aaron Hall

4

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

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


3

ข้อมูลโค้ดต่อไปนี้จะอธิบายกรณีต่างๆทั้งหมด:

  • ขีดล่างสองตัว (__a)
  • ขีดล่างเดียว (_a)
  • ไม่มีขีดล่าง (a)

    class Test:
    
    def __init__(self):
        self.__a = 'test1'
        self._a = 'test2'
        self.a = 'test3'
    
    def change_value(self,value):
        self.__a = value
        return self.__a

พิมพ์แอตทริบิวต์ที่ถูกต้องทั้งหมดของ Test Object

testObj1 = Test()
valid_attributes = dir(testObj1)
print valid_attributes

['_Test__a', '__doc__', '__init__', '__module__', '_a', 'a', 
'change_value']

ที่นี่คุณจะเห็นว่าชื่อของ __a ถูกเปลี่ยนเป็น _Test__a เพื่อป้องกันไม่ให้ตัวแปรนี้ถูกแทนที่โดยคลาสย่อย แนวคิดนี้เรียกว่า "ชื่อ Mangling" ใน python คุณสามารถเข้าถึงสิ่งนี้ได้ดังนี้:

testObj2 = Test()
print testObj2._Test__a

test1

ในทำนองเดียวกันในกรณีของ _a ตัวแปรเป็นเพียงการแจ้งให้นักพัฒนาทราบว่าควรใช้เป็นตัวแปรภายในของคลาสนั้น ๆ ตัวแปล python จะไม่ทำอะไรแม้ว่าคุณจะเข้าถึง แต่ก็ไม่ใช่แนวทางปฏิบัติที่ดี

testObj3 = Test()
print testObj3._a

test2

ตัวแปรสามารถเข้าถึงได้จากทุกที่เหมือนกับตัวแปรคลาสสาธารณะ

testObj4 = Test()
print testObj4.a

test3

หวังว่าคำตอบจะช่วยคุณได้ :)


2

ในตอนแรกมันควรจะเหมือนกับภาษาอื่น ๆ (ภายใต้ "อื่น ๆ " ฉันหมายถึง Java หรือ C ++) แต่มันไม่ใช่

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

ดังนั้นฉันขอแนะนำให้คุณใช้ขีดล่างเดียวโดยค่าเริ่มต้นสำหรับสมาชิก "ส่วนตัว"


ใช้ขีดล่างคู่สำหรับ "ส่วนตัว" และขีดล่างเดียวสำหรับ "ป้องกัน" โดยปกติแล้วผู้คนจะใช้ขีดล่างเดียวสำหรับทุกสิ่ง (ขีดล่างคู่จะช่วยบังคับใช้ความเป็นส่วนตัวซึ่งมักจะขัดกับรูปแบบ Python)
Jonathan Sternberg

1
แต่นั่นไม่ทำให้ขีดล่างสองอันคล้ายกับส่วนตัวและอีกอันหนึ่งที่เหมือนกันกับการป้องกัน ทำไมไม่เริ่มจาก "ส่วนตัว" ล่ะ?
Paul Manta

@ พอลไม่มันไม่ ไม่มีไพรเวตใน Python และคุณไม่ควรพยายามทำให้สำเร็จ
Roman Bodnarchuk

@ โรมันพูดตามแนวคิด ... สังเกตคำพูดรอบ ๆ 'ส่วนตัว'
Paul Manta

1

"หากมีข้อสงสัยว่าตัวแปรควรเป็นแบบส่วนตัวหรือได้รับการปกป้องควรเลือกใช้แบบส่วนตัว" - ใช่การถือครองแบบเดียวกันใน Python

คำตอบบางคำกล่าวเกี่ยวกับ "อนุสัญญา" แต่อย่าให้ลิงก์ไปยังอนุสัญญาเหล่านั้น คู่มือที่เชื่อถือได้สำหรับ Python, PEP 8ระบุอย่างชัดเจน:

หากมีข้อสงสัยให้เลือกแบบไม่เปิดเผยต่อสาธารณะ การทำให้เป็นสาธารณะในภายหลังง่ายกว่าการทำให้แอตทริบิวต์สาธารณะไม่ใช่สาธารณะ

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

เราไม่ใช้คำว่า "ส่วนตัว" ที่นี่เนื่องจากไม่มีแอตทริบิวต์ใดเป็นส่วนตัวใน Python (โดยทั่วไปไม่จำเป็นต้องทำงานเป็นจำนวนมาก)


-3

# โปรแกรมตัวอย่างสำหรับการโกงชื่อ Python

class Demo:
    __any_name = "__any_name"
    __any_other_name_ = "__any_other_name_"


[n for n in dir(Demo) if 'any' in n]   # GIVES OUTPUT AS ['_Demo__any_name', 
                                       #    '_Demo__any_other_name_']

1
สิ่งนี้ไม่ได้ตอบคำถามเลย - แสดงตัวอย่าง แต่ไม่ได้ตรงใจของคำถามจริง นั่นและคำถามนี้เกือบ 9 ปีพร้อมคำตอบที่เป็นที่ยอมรับ สิ่งนี้เพิ่มอะไรในคำตอบที่ให้ไว้ที่นี่หรือไม่?
rayryeng
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.