ฉันพยายามเข้าใจว่า 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
คืออะไร