ฉันพยายามเข้าใจว่า Python descriptors คืออะไรและมีประโยชน์อย่างไร
Descriptors เป็นคุณสมบัติคลาส (เช่นคุณสมบัติหรือเมธอด) ด้วยวิธีพิเศษต่อไปนี้:
__get__ (เมธอด descriptor ที่ไม่ใช่ข้อมูลตัวอย่างเช่นบนเมธอด / ฟังก์ชัน)
__set__ (เมธอดตัวอธิบายข้อมูลตัวอย่างเช่นบนอินสแตนซ์ของคุณสมบัติ)
__delete__ (วิธีอธิบายข้อมูล)
อ็อบเจ็กต์ descriptor เหล่านี้สามารถใช้เป็นแอ็ตทริบิวต์บนนิยามคลาสอ็อบเจ็กต์อื่น (นั่นคือพวกเขาอาศัยอยู่ใน__dict__วัตถุคลาส)
วัตถุ Descriptor สามารถใช้ในการจัดการผลลัพธ์ของการค้นหาแบบประ (เช่นfoo.descriptor) ในทางโปรแกรมในนิพจน์ปกติการกำหนดและแม้แต่การลบ
ฟังก์ชั่น / วิธีการวิธีการที่ถูกผูกไว้property, classmethodและstaticmethodใช้ทุกวิธีการพิเศษเหล่านี้เพื่อควบคุมวิธีการที่พวกเขาจะเข้าถึงได้ผ่านการค้นหาประ
ตัวบ่งชี้ข้อมูลเช่นpropertyสามารถอนุญาตให้ประเมินคุณลักษณะที่ขี้เกียจตามสถานะที่เรียบง่ายของวัตถุทำให้อินสแตนซ์ใช้หน่วยความจำน้อยกว่าถ้าคุณคำนวณค่าแอตทริบิวต์ที่เป็นไปได้แต่ละอย่างล่วงหน้า
บ่งข้อมูลอื่นmember_descriptorที่สร้างขึ้นโดย__slots__ให้เงินฝากออมทรัพย์หน่วยความจำโดยให้ชั้นเรียนเพื่อเก็บข้อมูลในที่ไม่แน่นอน tuple เหมือน datastructure แทนของความยืดหยุ่นมากขึ้น __dict__แต่พื้นที่นาน
อธิบายที่ไม่ใช่ข้อมูลเช่นปกติชั้นเรียนและวิธีการคงได้รับข้อโต้แย้งแรกของพวกเขาโดยปริยาย (มักจะตั้งชื่อclsและselfตามลำดับ) __get__จากวิธีบ่งไม่ใช่ข้อมูลของพวกเขา
ผู้ใช้ Python ส่วนใหญ่จำเป็นต้องเรียนรู้การใช้งานที่เรียบง่ายและไม่จำเป็นต้องเรียนรู้หรือเข้าใจการใช้ descriptor เพิ่มเติม
ในเชิงลึก: คำอธิบายคืออะไร
descriptor เป็นวัตถุที่มีวิธีการอย่างใดอย่างหนึ่งต่อไปนี้ ( __get__, __set__หรือ__delete__) ตั้งใจที่จะใช้ผ่านการค้นหาประราวกับว่ามันเป็นคุณลักษณะทั่วไปของอินสแตนซ์ สำหรับเจ้าของวัตถุobj_instanceกับdescriptorวัตถุ:
obj_instance.descriptorจะเรียก
descriptor.__get__(self, obj_instance, owner_class)กลับvalue
นี้เป็นวิธีการวิธีการทั้งหมดและgetในการทำงานของสถานที่ให้บริการ
obj_instance.descriptor = valueจะเรียก
descriptor.__set__(self, obj_instance, value)กลับมาNone
นี้เป็นวิธีการsetterในการทำงานของสถานที่ให้บริการ
del obj_instance.descriptorจะเรียก
descriptor.__delete__(self, obj_instance)กลับมาNone
นี้เป็นวิธีการdeleterในการทำงานของสถานที่ให้บริการ
obj_instanceเป็นอินสแตนซ์ที่มีคลาสประกอบด้วยอินสแตนซ์ของวัตถุ descriptor selfเป็นตัวอย่างของdescriptor (อาจเป็นเพียงหนึ่งสำหรับคลาสของobj_instance)
ในการกำหนดสิ่งนี้ด้วยรหัสวัตถุจะเป็นตัวบ่งชี้ถ้าชุดของคุณสมบัตินั้นตัดกับแอตทริบิวต์ที่ต้องการ:
def has_descriptor_attrs(obj):
return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
def is_descriptor(obj):
"""obj can be instance of descriptor or the descriptor class"""
return bool(has_descriptor_attrs(obj))
ข้อมูลอธิบายมีและ__set__ / หรือ ไม่อธิบายข้อมูลมีค่ามิได้__delete__
__set____delete__
def has_data_descriptor_attrs(obj):
return set(['__set__', '__delete__']) & set(dir(obj))
def is_data_descriptor(obj):
return bool(has_data_descriptor_attrs(obj))
Builtin Descriptor Object ตัวอย่าง:
classmethod
staticmethod
property
- ฟังก์ชั่นทั่วไป
ตัวอธิบายที่ไม่ใช่ข้อมูล
เราสามารถเห็นได้classmethodและstaticmethodไม่ใช่ผู้ให้ข้อมูล:
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
ทั้งสองมี__get__วิธีการเท่านั้น:
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
โปรดทราบว่าฟังก์ชั่นทั้งหมดยังไม่ใช่ตัวอธิบายข้อมูล:
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
Data Descriptor property
อย่างไรก็ตามpropertyเป็น Data-Descriptor:
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
คำสั่งค้นหาแบบประ
สิ่งเหล่านี้มีความแตกต่างที่สำคัญเนื่องจากจะมีผลกับลำดับการค้นหาสำหรับการค้นหาจุด
obj_instance.attribute
- ก่อนอื่นข้างต้นจะมีลักษณะเพื่อดูว่าแอตทริบิวต์เป็น Data-Descriptor ในคลาสของอินสแตนซ์หรือไม่
- หากไม่เป็นเช่นนั้นจะดูว่าแอตทริบิวต์นั้นเป็น
obj_instanceของหรือ__dict__ไม่
- ในที่สุดก็กลับไปที่ Non-Data-Descriptor
ผลที่ตามมาของการสั่งซื้อการค้นหานี้คือการที่ไม่เป็นไปตามข้อมูลอธิบายเช่นฟังก์ชั่น / วิธีการสามารถแทนที่โดยอินสแตนซ์
สรุปและขั้นตอนต่อไป
เราได้เรียนรู้ว่าการอธิบายเป็นวัตถุใด ๆ__get__, หรือ__set__ __delete__อ็อบเจ็กต์ descriptor เหล่านี้สามารถใช้เป็นแอ็ตทริบิวต์บนนิยามคลาสอ็อบเจ็กต์อื่น ตอนนี้เราจะดูว่ามีการใช้งานอย่างไรโดยใช้รหัสของคุณเป็นตัวอย่าง
การวิเคราะห์รหัสจากคำถาม
นี่คือรหัสของคุณตามด้วยคำถามและคำตอบของคุณสำหรับแต่ละข้อ:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Temperature(object):
celsius = Celsius()
- เหตุใดฉันจึงต้องใช้คลาส descriptor
ตัวให้คำอธิบายของคุณทำให้แน่ใจได้ว่าคุณมีทุ่นสำหรับแอตทริบิวต์ของคลาสนี้Temperatureและคุณไม่สามารถใช้delเพื่อลบแอตทริบิวต์:
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
มิฉะนั้นตัวอธิบายของคุณจะไม่สนใจเจ้าของคลาสและอินสแตนซ์ของเจ้าของแทนโดยเก็บสถานะไว้ใน descriptor แทน คุณสามารถแบ่งปันสถานะในทุก ๆ อินสแตนซ์ได้ง่ายๆด้วยแอตทริบิวต์ class แบบง่าย ๆ (ตราบใดที่คุณตั้งเป็นแบบลอยตัวไปที่คลาสและไม่ต้องลบมันทิ้งหรือไม่สบายใจกับผู้ใช้โค้ดของคุณ):
class Temperature(object):
celsius = 0.0
สิ่งนี้ทำให้คุณมีพฤติกรรมเช่นเดียวกับตัวอย่างของคุณ (ดูการตอบคำถาม 3 ด้านล่าง) แต่ใช้ Pythons builtin ( property) และถือว่าเป็นสำนวนมากขึ้น:
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
- อินสแตนซ์และเจ้าของที่นี่คืออะไร ( ได้รับ ) วัตถุประสงค์ของพารามิเตอร์เหล่านี้คืออะไร?
instanceเป็นอินสแตนซ์ของเจ้าของที่เรียก descriptor เจ้าของคือคลาสที่วัตถุ descriptor ถูกใช้เพื่อจัดการการเข้าถึงจุดข้อมูล ดูคำอธิบายของวิธีการพิเศษที่กำหนด descriptor ถัดจากย่อหน้าแรกของคำตอบนี้สำหรับชื่อตัวแปรอธิบายเพิ่มเติม
- ฉันจะโทร / ใช้ตัวอย่างนี้ได้อย่างไร
นี่คือการสาธิต:
>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>>
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
คุณไม่สามารถลบแอตทริบิวต์:
>>> del t2.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
และคุณไม่สามารถกำหนดตัวแปรที่ไม่สามารถแปลงเป็นทศนิยมได้:
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02
มิฉะนั้นสิ่งที่คุณมีที่นี่คือสถานะโกลบอลสำหรับทุกอินสแตนซ์ที่จัดการโดยกำหนดให้กับอินสแตนซ์
วิธีที่คาดหวังว่าโปรแกรมเมอร์ Python ที่มีประสบการณ์มากที่สุดจะบรรลุผลนี้คือการใช้propertyมัณฑนากรซึ่งใช้ descriptor เดียวกันภายใต้ประทุน แต่นำพฤติกรรมเข้าสู่การใช้งานคลาสเจ้าของ
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
ซึ่งมีพฤติกรรมที่คาดหวังเหมือนกันแน่นอนของชิ้นส่วนของรหัสต้นฉบับ:
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02
ข้อสรุป
เราได้กล่าวถึงคุณลักษณะที่กำหนดตัวอธิบายความแตกต่างระหว่าง data- และ non-data-descriptors วัตถุบิวด์อินที่ใช้พวกเขาและคำถามเฉพาะเกี่ยวกับการใช้งาน
ดังนั้นอีกครั้งคุณจะใช้ตัวอย่างของคำถามได้อย่างไร ฉันหวังว่าคุณจะไม่ ฉันหวังว่าคุณจะเริ่มต้นด้วยคำแนะนำแรกของฉัน (คุณลักษณะระดับง่าย) และไปที่ข้อเสนอแนะที่สอง (มัณฑนากรคุณสมบัติ) ถ้าคุณรู้สึกว่ามันจำเป็น
selfและinstanceคืออะไร