यह एक लंबा घुमावदार जवाब होगा जो केवल प्रशंसात्मक होने के लिए काम कर सकता है ... लेकिन आपके प्रश्न ने मुझे खरगोश के छेद के नीचे की सवारी के लिए ले लिया, इसलिए मैं अपने निष्कर्षों (और दर्द) को भी साझा करना चाहूंगा।
आप अंततः इस जवाब को अपनी वास्तविक समस्या के लिए उपयोगी नहीं पा सकते हैं। वास्तव में, मेरा निष्कर्ष यह है कि - मैं ऐसा बिल्कुल नहीं करूंगा। कहा जाता है कि, इस निष्कर्ष की पृष्ठभूमि आपको थोड़ा मनोरंजन कर सकती है, क्योंकि आप अधिक विवरण की तलाश कर रहे हैं।
कुछ गलत धारणा को संबोधित करते हुए
ज्यादातर मामलों में सही होने के दौरान पहला उत्तर हमेशा ऐसा नहीं होता है। उदाहरण के लिए, इस वर्ग पर विचार करें:
class Foo:
def __init__(self):
self.name = 'Foo!'
@property
def inst_prop():
return f'Retrieving {self.name}'
self.inst_prop = inst_prop
inst_prop
, जबकि एक होने के नाते property
, वास्तव में एक उदाहरण विशेषता है:
>>> Foo.inst_prop
Traceback (most recent call last):
File "<pyshell#60>", line 1, in <module>
Foo.inst_prop
AttributeError: type object 'Foo' has no attribute 'inst_prop'
>>> Foo().inst_prop
<property object at 0x032B93F0>
>>> Foo().inst_prop.fget()
'Retrieving Foo!'
यह सब इस बात पर निर्भर करता है कि आपका property
स्थान पहली बार में कहां परिभाषित किया गया है। यदि आपके @property
वर्ग "दायरे" (या वास्तव में namespace
) के भीतर परिभाषित किया गया है , तो यह एक वर्ग विशेषता बन जाता है। मेरे उदाहरण में, inst_prop
तत्काल ही क्लास को किसी भी तरह की जानकारी नहीं है। बेशक, यह यहाँ एक संपत्ति के रूप में बहुत उपयोगी नहीं है।
लेकिन पहले, चलो विरासत संकल्प पर अपनी टिप्पणी को संबोधित ...
तो वास्तव में इस मुद्दे में वंशानुक्रम कारक कैसे है? यह निम्नलिखित लेख विषय में थोड़ा गोता लगाता है, और विधि संकल्प आदेश कुछ हद तक संबंधित है, हालांकि यह गहराई के बजाय वंशानुक्रम की चौड़ाई पर चर्चा करता है।
हमारे खोज के साथ संयुक्त, इन नीचे सेटअप दिया:
@property
def some_prop(self):
return "Family property"
class Grandparent:
culture = some_prop
world_view = some_prop
class Parent(Grandparent):
world_view = "Parent's new world_view"
class Child(Parent):
def __init__(self):
try:
self.world_view = "Child's new world_view"
self.culture = "Child's new culture"
except AttributeError as exc:
print(exc)
self.__dict__['culture'] = "Child's desired new culture"
कल्पना कीजिए कि जब ये रेखाएँ निष्पादित होती हैं तो क्या होता है:
print("Instantiating Child class...")
c = Child()
print(f'c.__dict__ is: {c.__dict__}')
print(f'Child.__dict__ is: {Child.__dict__}')
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')
परिणाम इस प्रकार है:
Instantiating Child class...
can't set attribute
c.__dict__ is: {'world_view': "Child's new world_view", 'culture': "Child's desired new culture"}
Child.__dict__ is: {'__module__': '__main__', '__init__': <function Child.__init__ at 0x0068ECD8>, '__doc__': None}
c.world_view is: Child's new world_view
Child.world_view is: Parent's new world_view
c.culture is: Family property
Child.culture is: <property object at 0x00694C00>
नोटिस कैसे:
self.world_view
लागू होने में सक्षम था, जबकि self.culture
असफल रहा
culture
वर्ग में मौजूद नहीं है Child.__dict__
( mappingproxy
उदाहरण के साथ भ्रमित नहीं होना __dict__
)
- इसमें
culture
मौजूद होने के बावजूद c.__dict__
इसे संदर्भित नहीं किया जाता है।
आप अनुमान लगाने में सक्षम हो सकते हैं कि - गैर-संपत्ति के रूप में वर्ग world_view
द्वारा अधिलेखित क्यों किया गया था Parent
, इसलिए Child
इसे भी अधिलेखित करने में सक्षम था। इस बीच, culture
विरासत में मिला है, यह केवल के भीतर मौजूद mappingproxy
हैGrandparent
:
Grandparent.__dict__ is: {
'__module__': '__main__',
'culture': <property object at 0x00694C00>,
'world_view': <property object at 0x00694C00>,
...
}
वास्तव में यदि आप हटाने की कोशिश करते हैं Parent.culture
:
>>> del Parent.culture
Traceback (most recent call last):
File "<pyshell#67>", line 1, in <module>
del Parent.culture
AttributeError: culture
आप देखेंगे कि यह भी मौजूद नहीं है Parent
। क्योंकि ऑब्जेक्ट सीधे वापस संदर्भित कर रहा है Grandparent.culture
।
तो, संकल्प आदेश के बारे में क्या?
इसलिए हम वास्तविक रिज़ॉल्यूशन ऑर्डर को देखने के इच्छुक हैं, आइए Parent.world_view
इसके बजाय हटाने की कोशिश करें :
del Parent.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
आश्चर्य है कि परिणाम क्या है?
c.world_view is: Family property
Child.world_view is: <property object at 0x00694C00>
यह वापस ग्रैंडपरेंट के पास लौट आया world_view
property
, भले ही हमने self.world_view
पहले असाइन करने के लिए सफलतापूर्वक प्रबंधन किया था ! लेकिन क्या होगा अगर हम world_view
अन्य उत्तर की तरह, जबरदस्ती वर्ग स्तर पर बदलाव करें ? अगर हम इसे हटा दें तो क्या होगा? क्या होगा यदि हम संपत्ति होने के लिए वर्तमान वर्ग विशेषता प्रदान करते हैं?
Child.world_view = "Child's independent world_view"
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
del c.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
Child.world_view = property(lambda self: "Child's own property")
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
परिणाम है:
# Creating Child's own world view
c.world_view is: Child's new world_view
Child.world_view is: Child's independent world_view
# Deleting Child instance's world view
c.world_view is: Child's independent world_view
Child.world_view is: Child's independent world_view
# Changing Child's world view to the property
c.world_view is: Child's own property
Child.world_view is: <property object at 0x020071B0>
यह दिलचस्प है क्योंकि c.world_view
इसकी आवृत्ति विशेषता को पुनर्स्थापित किया जाता है, जबकि Child.world_view
एक वह है जिसे हमने सौंपा है। इंस्टेंस विशेषता को हटाने के बाद, यह क्लास एट्रिब्यूट पर लौटता है। और Child.world_view
संपत्ति को फिर से सौंपने के बाद , हम तुरंत इंस्टेंस विशेषता तक पहुंच खो देते हैं।
इसलिए, हम निम्नलिखित रिज़ॉल्यूशन ऑर्डर को सुरक्षित कर सकते हैं :
- यदि एक वर्ग विशेषता मौजूद है और यह एक है
property
, तो इसके माध्यम से getter
या fget
(बाद में इस पर) इसका मान प्राप्त करें। बेस क्लास की वर्तमान कक्षा पहली।
- अन्यथा, यदि कोई उदाहरण विशेषता मौजूद है, तो आवृत्ति विशेषता मान पुनर्प्राप्त करें।
- गैर, गैर-
property
श्रेणी विशेषता को पुनः प्राप्त करें । बेस क्लास की वर्तमान कक्षा पहली।
उस स्थिति में, आइए जड़ को हटा दें property
:
del Grandparent.culture
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')
जो देता है:
c.culture is: Child's desired new culture
Traceback (most recent call last):
File "<pyshell#74>", line 1, in <module>
print(f'Child.culture is: {Child.culture}')
AttributeError: type object 'Child' has no attribute 'culture'
टा-दाह! Child
अब culture
बल में सम्मिलन के आधार पर उनका अपना है c.__dict__
। Child.culture
इसका कोई मतलब नहीं है, क्योंकि इसे कभी भी क्लास Parent
या Child
क्लास की विशेषता में परिभाषित नहीं किया गया था और Grandparent
इसे हटा दिया गया था।
क्या यह मेरी समस्या का मूल कारण है?
दरअसल, नहीं । जो त्रुटि आपको मिल रही है, जिसे हम असाइन करते समय अभी भी देख रहे हैं self.culture
, पूरी तरह से अलग है । लेकिन उत्तराधिकार का क्रम उत्तर की पृष्ठभूमि तय करता है - जो property
स्वयं है।
पहले बताई गई getter
विधि के अलावा , property
इसकी आस्तीन के साथ कुछ साफ सुथरी चालें भी हैं। इस मामले में सबसे अधिक प्रासंगिक है setter
, या fset
विधि, जो self.culture = ...
लाइन से चालू होती है । चूँकि आपके property
किसी भी कार्य setter
या fget
कार्य को लागू नहीं किया था , अजगर को पता नहीं है कि क्या करना है, और AttributeError
इसके बजाय (यानी can't set attribute
) फेंकता है ।
हालांकि अगर आपने एक setter
तरीका लागू किया है:
@property
def some_prop(self):
return "Family property"
@some_prop.setter
def some_prop(self, val):
print(f"property setter is called!")
# do something else...
जब Child
आपको क्लास में तत्काल मिल जाएगा:
Instantiating Child class...
property setter is called!
ए प्राप्त करने के बजाय AttributeError
, अब आप वास्तव में some_prop.setter
विधि को बुला रहे हैं । जो आपको आपकी वस्तु पर अधिक नियंत्रण प्रदान करता है ... हमारे पिछले निष्कर्षों के साथ, हम जानते हैं कि संपत्ति तक पहुंचने से पहले हमें एक वर्ग विशेषता को अधिलेखित करने की आवश्यकता है । इसे ट्रिगर के रूप में बेस क्लास के भीतर लागू किया जा सकता है। यहाँ एक ताजा उदाहरण है:
class Grandparent:
@property
def culture(self):
return "Family property"
# add a setter method
@culture.setter
def culture(self, val):
print('Fine, have your own culture')
# overwrite the child class attribute
type(self).culture = None
self.culture = val
class Parent(Grandparent):
pass
class Child(Parent):
def __init__(self):
self.culture = "I'm a millennial!"
c = Child()
print(c.culture)
जिसके परिणामस्वरूप:
Fine, have your own culture
I'm a millennial!
टीए-डाह! अब आप विरासत में मिली संपत्ति पर अपनी खुद की विशेषता को अधिलेखित कर सकते हैं!
तो, समस्या हल हो गई?
... ज़रुरी नहीं। इस दृष्टिकोण के साथ समस्या है, अब आपके पास एक उचित setter
तरीका नहीं हो सकता है । ऐसे मामले हैं जहां आप अपने मूल्यों को निर्धारित करना चाहते हैं property
। लेकिन अब आप सेट जब भी self.culture = ...
यह होगा हमेशा के ऊपर लिख जो कुछ भी कार्य करते हैं आप में परिभाषित किया गया getter
है (जो इस उदाहरण में, वास्तव में सिर्फ है @property
लिपटे भाग। आप कर सकते हैं अधिक सूक्ष्म उपायों में जोड़ने के लिए, लेकिन एक तरह से या किसी अन्य रूप में यह हमेशा से ही अधिक से अधिक शामिल करेंगे self.culture = ...
। उदाहरण के लिए:
class Grandparent:
# ...
@culture.setter
def culture(self, val):
if isinstance(val, tuple):
if val[1]:
print('Fine, have your own culture')
type(self).culture = None
self.culture = val[0]
else:
raise AttributeError("Oh no you don't")
# ...
class Child(Parent):
def __init__(self):
try:
# Usual setter
self.culture = "I'm a Gen X!"
except AttributeError:
# Trigger the overwrite condition
self.culture = "I'm a Boomer!", True
यह है waaaaay अधिक अन्य जवाब से जटिल, size = None
श्रेणी स्तर पर।
आप अपने खुद के लिखने पर विचार कर सकता है वर्णनकर्ता संभाल करने के बजाय __get__
और __set__
, या अतिरिक्त तरीकों। लेकिन दिन के अंत में, जब self.culture
संदर्भित किया जाता है, तो __get__
हमेशा पहले ट्रिगर किया जाएगा, और जब self.culture = ...
संदर्भित किया जाता है, __set__
तो हमेशा पहले ट्रिगर किया जाएगा। जहाँ तक मैंने कोशिश की है, उसके आसपास कोई नहीं है।
इस मुद्दे की जड़, आई.एम.ओ.
मैं यहाँ जो समस्या देख रहा हूँ वह है - आप अपना केक नहीं खा सकते हैं और इसे खा भी सकते हैं। property
का अर्थ है एक विवरणक की तरह getattr
या जैसे तरीकों से सुविधाजनक पहुंच के साथ setattr
। यदि आप भी इन तरीकों को एक अलग उद्देश्य को प्राप्त करना चाहते हैं, तो आप सिर्फ परेशानी के लिए पूछ रहे हैं। मैं शायद दृष्टिकोण पर पुनर्विचार करूंगा:
- क्या मुझे वास्तव में इसके लिए आवश्यकता है
property
?
- क्या कोई विधि मुझे अलग तरीके से परोस सकती है?
- अगर मुझे इसकी आवश्यकता है
property
, तो क्या कोई कारण है कि मुझे इसे अधिलेखित करने की आवश्यकता होगी?
- यदि ये
property
लागू नहीं होते हैं तो क्या उपवर्ग वास्तव में एक ही परिवार के हैं ?
- अगर मुझे किसी भी / सभी को अधिलेखित करने की आवश्यकता है
property
, तो क्या एक अलग विधि मुझे केवल पुन: असाइन करने की तुलना में बेहतर सेवा प्रदान करेगी, क्योंकि पुन: संयोगवश गलती से property
एस शून्य हो सकता है ?
बिंदु 5 के लिए, मेरे दृष्टिकोण overwrite_prop()
में बेस क्लास में एक विधि होगी जो वर्तमान वर्ग विशेषता को ओवरराइट करती है ताकि property
अब और ट्रिगर नहीं होगा:
class Grandparent:
# ...
def overwrite_props(self):
# reassign class attributes
type(self).size = None
type(self).len = None
# other properties, if necessary
# ...
# Usage
class Child(Parent):
def __init__(self):
self.overwrite_props()
self.size = 5
self.len = 10
जैसा कि आप देख सकते हैं, जबकि अभी भी थोड़ा सा विवादित है, यह एक गुप्त से कम से कम अधिक स्पष्ट है size = None
। उस ने कहा, आखिरकार, मैं संपत्ति को बिल्कुल भी अधिलेखित नहीं करूंगा, और अपने डिजाइन को रूट से पुनर्विचार करूंगा।
यदि आपने इसे अभी तक बनाया है - मेरे साथ इस यात्रा को चलाने के लिए धन्यवाद। यह एक छोटा सा व्यायाम था।