मैं यह कैसे निर्दिष्ट करूं कि एक विधि का वापसी प्रकार केवल कक्षा के समान है?


410

मेरे पास अजगर 3 में निम्नलिखित कोड हैं:

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: Position) -> Position:
        return Position(self.x + other.x, self.y + other.y)

लेकिन मेरे संपादक (PyCharm) का कहना है कि संदर्भ स्थिति को हल नहीं किया जा सकता है ( __add__विधि में)। मुझे यह कैसे निर्दिष्ट करना चाहिए कि मुझे वापसी प्रकार की उम्मीद है Position?

संपादित करें: मुझे लगता है कि यह वास्तव में एक PyCharm मुद्दा है। यह वास्तव में इसकी चेतावनियों और कोड पूरा होने में सूचना का उपयोग करता है

लेकिन मुझे गलत होने पर सही करें, और कुछ अन्य वाक्यविन्यास का उपयोग करने की आवश्यकता है।

जवाबों:


574

TL; DR : यदि आप Python 4.0 का उपयोग कर रहे हैं तो यह काम करता है। 3.7+ में आज (2019) तक आपको भविष्य कथन ( from __future__ import annotations) - पायथन 3.6 के लिए या नीचे दिए गए स्ट्रिंग का उपयोग करके इस सुविधा को चालू करना होगा ।

मुझे लगता है कि आपको यह अपवाद मिला है:

NameError: name 'Position' is not defined

ऐसा इसलिए है क्योंकि Positionजब तक आप पायथन 4 का उपयोग नहीं कर रहे हैं, तब तक इसे एनोटेशन में उपयोग करने से पहले इसे परिभाषित किया जाना चाहिए।

अजगर 3.7+: from __future__ import annotations

पायथॉन 3.7 पीईपी 563 का परिचय देता है : एनोटेशन का स्थगित मूल्यांकन । एक मॉड्यूल जो भविष्य कथन का उपयोग करता है, from __future__ import annotationsएनोटेशन को स्ट्रिंग्स के रूप में स्वचालित रूप से संग्रहीत करेगा:

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

यह पायथन 4.0 में डिफ़ॉल्ट बनने के लिए निर्धारित है। चूंकि पायथन अभी भी एक गतिशील रूप से टाइप की गई भाषा है, इसलिए रनटाइम पर किसी भी प्रकार की जाँच नहीं की जाती है, टाइपिंग एनोटेशन का कोई प्रदर्शन प्रभाव नहीं होना चाहिए, है ना? गलत! अजगर 3.7 से पहले टाइपिंग मॉड्यूल कोर में सबसे धीमे अजगर मॉड्यूल में से एक हुआ करता था, इसलिए यदि आप 3.7 में अपग्रेड करते हैं तो आप प्रदर्शन में 7 गुना वृद्धिimport typing देखेंगे ।

पायथन <3.7: एक स्ट्रिंग का उपयोग करें

PEP 484 के अनुसार , आपको कक्षा के बजाय एक स्ट्रिंग का उपयोग करना चाहिए:

class Position:
    ...
    def __add__(self, other: 'Position') -> 'Position':
       ...

यदि आप Django ढांचे का उपयोग करते हैं तो यह परिचित हो सकता है क्योंकि Django मॉडल भी आगे के संदर्भों के लिए तार का उपयोग करते हैं (विदेशी प्रमुख परिभाषाएं जहां विदेशी मॉडल है selfया अभी घोषित नहीं है)। यह Pycharm और अन्य उपकरणों के साथ काम करना चाहिए।

सूत्रों का कहना है

पीईपी 484 और पीईपी 563 के प्रासंगिक हिस्से , आपको यात्रा को खाली करने के लिए:

आगे के संदर्भ

जब एक प्रकार के संकेत में ऐसे नाम होते हैं जिन्हें अभी तक परिभाषित नहीं किया गया है, तो उस परिभाषा को एक स्ट्रिंग शाब्दिक के रूप में व्यक्त किया जा सकता है, जिसे बाद में हल किया जा सकता है।

ऐसी स्थिति जहां यह आमतौर पर होता है, एक कंटेनर वर्ग की परिभाषा है, जहां परिभाषित की जा रही कक्षा कुछ विधियों के हस्ताक्षर में होती है। उदाहरण के लिए, निम्न कोड (एक सरल बाइनरी ट्री कार्यान्वयन की शुरुआत) काम नहीं करता है:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

इसे संबोधित करने के लिए, हम लिखते हैं:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

स्ट्रिंग शाब्दिक में एक मान्य पायथन अभिव्यक्ति होनी चाहिए (जैसे, संकलन (प्रकाशित, '', 'eval') एक वैध कोड ऑब्जेक्ट होना चाहिए) और मॉड्यूल पूरी तरह से लोड होने के बाद त्रुटियों के बिना इसका मूल्यांकन करना चाहिए। जिस स्थानीय और वैश्विक नामस्थान का मूल्यांकन किया गया है, वह वही नामस्थान होना चाहिए जिसमें एक ही कार्य के लिए डिफ़ॉल्ट तर्क का मूल्यांकन किया जाएगा।

और पीईपी 563:

पायथन 4.0 में, फ़ंक्शन और वेरिएबल एनोटेशन का अब परिभाषा के समय पर मूल्यांकन नहीं किया जाएगा। इसके बजाय, संबंधित __annotations__शब्दकोश में एक स्ट्रिंग फॉर्म संरक्षित किया जाएगा । स्टेटिक टाइप चेकर्स के व्यवहार में कोई अंतर नहीं दिखेगा, जबकि रनटाइम पर एनोटेशन का उपयोग करने वाले टूल को स्थगित मूल्यांकन करना होगा।

...

ऊपर वर्णित कार्यक्षमता को निम्न विशेष आयात का उपयोग करके पायथन 3.7 से शुरू किया जा सकता है:

from __future__ import annotations

चीजें जो आप के बजाय करने के लिए परीक्षा हो सकती है

A. एक डमी को परिभाषित करें Position

कक्षा की परिभाषा से पहले, एक डमी परिभाषा रखें:

class Position(object):
    pass


class Position(object):
    ...

इससे छुटकारा मिलेगा NameErrorऔर ठीक भी लग सकता है:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

पर है क्या?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

एन बंदर-पैच एनोटेशन जोड़ने के लिए:

आप कुछ पायथन मेटा प्रोग्रामिंग जादू को आज़माना चाहते हैं और मतदाता को जोड़ने के लिए कक्षा परिभाषा को बंदर-पैच करने के लिए डेकोरेटर लिख सकते हैं:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

डेकोरेटर इसके बराबर के लिए जिम्मेदार होना चाहिए:

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

कम से कम यह सही लगता है:

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

शायद बहुत अधिक परेशानी।

निष्कर्ष

यदि आप 3.6 या नीचे का उपयोग कर रहे हैं, तो स्ट्रिंग नाम शाब्दिक है जिसमें 3.7 का उपयोग किया गया है, 3.7 उपयोग में है from __future__ import annotationsऔर यह सिर्फ काम करेगा।


2
ठीक है, यह एक PyCharm मुद्दा और अधिक Python 3.5 PEP 484 समस्या है। मुझे संदेह है कि अगर आप इसे mypy टाइप टूल के माध्यम से चलाते हैं तो आपको वही चेतावनी मिलेगी।
पॉल एवरिट

23
> यदि आप पायथन 4.0 का उपयोग कर रहे हैं तो यह वैसे ही काम करता है, क्या आपने सारा कॉनर देखा है? :)
स्क्रूटरी

@JoelBerkeley मैंने अभी-अभी इसका परीक्षण किया और टाइप मापदंडों ने मेरे लिए 3.6 पर काम किया, बस इसके लिए आयात करना न भूलें कि typingआपके द्वारा उपयोग किए जाने वाले किसी भी प्रकार के स्कोप का मूल्यांकन होने पर यह स्कोप में होना चाहिए।
16o पर पाउलो स्कर्दिन

आह, मेरी गलती है, मैं केवल ''कक्षा में चक्कर लगा रहा था , न कि प्रकार के मापदंडों
जोएलब

5
उपयोग करने वाले किसी को भी महत्वपूर्ण नोट from __future__ import annotations- इसे अन्य सभी आयातों से पहले आयात किया जाना चाहिए।
आर्टूर

16

प्रकार को स्ट्रिंग के रूप में निर्दिष्ट करना ठीक है, लेकिन हमेशा मुझे थोड़ा सा परेशान करता है कि हम मूल रूप से पार्सर को दरकिनार कर रहे हैं। तो आप बेहतर इन शाब्दिक तार में से किसी एक को याद नहीं है:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

एक मामूली बदलाव एक बाध्य टाइपवार का उपयोग करना है, कम से कम तब आपको स्ट्रिंग को केवल एक बार लिखना होगा जब टाइपवार घोषित किया जाता है:

from typing import TypeVar

T = TypeVar('T', bound='Position')

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: T) -> T:
        return Position(self.x + other.x, self.y + other.y)

8
काश, पायथन के पास typing.Selfयह स्पष्ट रूप से निर्दिष्ट करने के लिए होता।
अलेक्जेंडर हुज़ाग

2
मैं यहां यह देखने के लिए आया था कि क्या तुम्हारा कुछ typing.Selfअस्तित्व है। पॉलिमोर्फिज्म का लाभ उठाने पर एक कठोर कोडित स्ट्रिंग को वापस करने से सही प्रकार वापस नहीं आता है। मेरे मामले में मैं एक deserialize classmethod को लागू करना चाहता था । मैं एक तानाशाह (कंवर) को वापस बुलाने और फोन करने लगा some_class(**some_class.deserialize(raw_data))
स्कॉट पी।

उपवर्गों का उपयोग करने के लिए इसे सही ढंग से लागू करते समय यहां उपयोग किए जाने वाले एनोटेशन उपयुक्त हैं। हालांकि, कार्यान्वयन वापस आता है Position, और वर्ग नहीं, इसलिए ऊपर का उदाहरण तकनीकी रूप से गलत है। कार्यान्वयन को Position(कुछ इस तरह से प्रतिस्थापित करना चाहिए self.__class__(
सैम बुल

इसके अतिरिक्त, एनोटेशन का कहना है कि रिटर्न प्रकार पर निर्भर करता है other, लेकिन शायद यह वास्तव में निर्भर करता है self। इसलिए, आपको selfसही व्यवहार का वर्णन करने के लिए एनोटेशन लगाने की आवश्यकता होगी (और शायद यह दिखाने के लिए otherहोना चाहिए Positionकि यह रिटर्न प्रकार से बंधा हुआ नहीं है)। इसका उपयोग उन मामलों के लिए भी किया जा सकता है जब आप केवल साथ काम कर रहे हों self। जैसेdef __aenter__(self: T) -> T:
सैम बुल

15

जिस समय क्लास बॉडी को पार्स किया जाता है उस समय 'स्थिति' नाम उपलब्ध नहीं होता है। मुझे नहीं पता कि आप किस प्रकार की घोषणाओं का उपयोग कर रहे हैं, लेकिन पायथन के पीईपी 484 - जो कि इन टाइपिंग संकेत का उपयोग करते हुए सबसे अधिक मोड का उपयोग करना चाहिए, कहते हैं कि आप बस इस बिंदु पर एक स्ट्रिंग के रूप में नाम रख सकते हैं:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

Https://www.python.org/dev/peps/pep-0484/#forward-references की जाँच करें - इसके अनुरूप उपकरण वहां से वर्ग नाम को खोलना और उसका उपयोग करना जानेंगे। (यह हमेशा महत्वपूर्ण है ध्यान में रखते हुए कि पायथन भाषा स्वयं इन एनोटेशनों में से कुछ भी नहीं करती है - वे आमतौर पर स्थैतिक-कोड विश्लेषण के लिए होती हैं, या किसी के पास रन-टाइम में टाइप चेकिंग के लिए लाइब्रेरी / फ्रेमवर्क हो सकता है - लेकिन आपको स्पष्ट रूप से यह निर्धारित करना होगा)।

अद्यतन इसके अलावा, पायथन 3.8 के रूप में, पेप -563 की जांच करें - पायथन 3.8 के रूप में from __future__ import annotations, एनोटेशन के मूल्यांकन को स्थगित करने के लिए लिखना संभव है - आगे संदर्भित कक्षाओं को सीधा काम करना चाहिए।


9

जब एक स्ट्रिंग-आधारित प्रकार संकेत स्वीकार्य होता है, तो __qualname__आइटम का उपयोग भी किया जा सकता है। यह वर्ग का नाम रखता है, और यह वर्ग परिभाषा के शरीर में उपलब्ध है।

class MyClass:
    @classmethod
    def make_new(cls) -> __qualname__:
        return cls()

ऐसा करने से, क्लास का नाम बदलने से प्रकार के संकेत संशोधित नहीं होते हैं। लेकिन मैं व्यक्तिगत रूप से स्मार्ट कोड संपादकों से इस फॉर्म को अच्छी तरह से संभालने की उम्मीद नहीं करूंगा।


1
यह विशेष रूप से उपयोगी है क्योंकि यह वर्ग के नाम को हार्डकोड नहीं करता है, इसलिए यह उपवर्गों में काम करता रहता है।
फ्लोरियन ब्रूकर

मुझे यकीन नहीं है कि यह एनोटेशन (स्थगित पीईपी 563) के स्थगित मूल्यांकन के साथ काम करेगा, इसलिए मैंने इसके लिए एक प्रश्न पूछा है
फ्लोरियन ब्रूकर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.