دسترسی به دیکشنری حاشیهنویسیهای یک شیء در پایتون 3.10 و جدیدتر¶
پایتون 3.10 یک تابع جدید به کتابخانه استاندارد میافزاید: inspect.get_annotations(). در نسخههای 3.10 تا 3.13 پایتون، فراخوانی این تابع بهترین روش برای دسترسی به دیکشنری حاشیهنویسیهای هر شیءای است که از حاشیهنویسی پشتیبانی میکند. این تابع همچنین میتواند حاشیهنویسیهای رشتهایشده را برای شما از حالت رشتهای خارج کند (un-stringize).
اگر به هر دلیلی inspect.get_annotations() برای مورد استفاده شما قابلاستفاده نیست، میتوانید بهصورت دستی به عضو دادهای __annotations__ دسترسی پیدا کنید. بهترین رویه برای این کار نیز در Python 3.10 تغییر کرد: از Python 3.10 به بعد، تضمین شده است که o.__annotations__همیشه برای توابع، کلاسها و ماژولهای پایتون کار میکند. اگر مطمئن هستید شیء مورد بررسی شما یکی از این سه شیء خاص است، میتوانید بهسادگی از o.__annotations__ برای دسترسی به دیکشنری حاشیهنویسیهای شیء استفاده کنید.
با این حال، سایر انواع اشیای فراخوانیپذیر—برای مثال، آنهایی که توسط functools.partial() ایجاد شدهاند—ممکن است ویژگی __annotations__ تعریفشدهای نداشته باشند. هنگام دسترسی به __annotations__ شیءای که ممکن است ناشناخته باشد، بهترین روش در نسخههای 3.10 و جدیدتر پایتون این است که getattr() را با ۳ آرگومان فراخوانی کنید، برای مثال getattr(o,'__annotations__',None).
پیش از پایتون 3.10، دسترسی به __annotations__ در کلاسی که هیچ حاشیهنویسیایتعریف نمیکند، اما کلاس والد آن حاشیهنویسی دارد، __annotations__ کلاس والد را برمیگرداند. در پایتون 3.10 و نسخههای جدیدتر، حاشیهنویسیهای کلاس فرزند در عوض یک دیکشنری خالی خواهد بود.
دسترسی به دیکشنری حاشیهنویسیهامتعلق به یک شیء در پایتون 3.9 و قدیمیتر¶
در پایتون 3.9 و نسخههای قدیمیتر، دسترسی به دیکشنری حاشیهنویسیها برای یک شیء، بسیار پیچیدهتر از نسخههای جدیدتر است. این مشکل یک نقص طراحی در این نسخههای قدیمیتر پایتون است، بهویژه مربوط به حاشیهنویسیهای کلاس (class annotations).
بهترین روش برای دسترسی به دیکشنری حاشیهنویسیهادر اشیای دیگر--توابع، سایر اشیای فراخوانیپذیر و ماژولها--همان بهترین روش برای 3.10 است، با فرض اینکه شما inspect.get_annotations() را فراخوانی نمیکنید: باید برای دسترسی به ویژگی __annotations__ شیء، از getattr() با سه آرگومان استفاده کنید.
متأسفانه، این بهترین روش برای کلاسها نیست. مشکل این است که از آنجا که __annotations__ در کلاسها اختیاری است و چون کلاسها میتوانند ویژگیها را از کلاسهای پایه خود به ارث ببرند، دسترسی به ویژگی __annotations__ یک کلاس ممکن است ناخواسته دیکشنری حاشیهنویسیهای یک کلاس پایه را برگرداند. بهعنوان مثال:
این، دیکشنری حاشیهنویسی را از Base چاپ میکند، نه Derived.
اگر شیءای که بررسی میکنید یک کلاس باشد (isinstance(o,type))، کد شما باید مسیر کد جداگانهای داشته باشد. در این صورت، بهترین روش به یک جزئیات پیادهسازی در پایتون 3.9 و نسخههای پیش از آن متکی است: اگر برای کلاسی حاشیهنویسیهاییتعریف شده باشند، آنها در دیکشنری __dict__ کلاس ذخیره میشوند. از آنجا که ممکن است برای کلاس حاشیهنویسیهاییتعریف شده باشند یا نشده باشند، بهترین روش این است که متد get() را روی دیکشنری کلاس فراخوانی کنید.
برای جمعبندی همه موارد، در ادامه کد نمونهای آمده است که بهطور امن به ویژگی __annotations__ یک شیء دلخواه در پایتون 3.9 و پیش از آن دسترسی پیدا میکند:
پس از اجرای این کد، ann باید یا یک دیکشنری باشد یا None. توصیه میشود پیش از بررسی بیشتر، نوع ann را با استفاده از isinstance() دوباره بررسی کنید.
توجه داشته باشید که برخی اشیای نوع غیرعادی یا نقصدار ممکن است ویژگی __dict__ را نداشته باشند، بنابراین برای احتیاط بیشتر، ممکن است بخواهید برای دسترسی به __dict__ از getattr() نیز استفاده کنید.
در موقعیتهایی که برخی حاشیهنویسیهاممکن است «رشتهایشده» (stringized) باشند و شما میخواهید آن رشتهها را ارزیابی کنید تا مقادیر پایتونی متناظر با آنها تولید شوند، واقعاً بهترین کار این است که inspect.get_annotations() را فراخوانی کنید تا این کار را برای شما انجام دهد.
اگر از پایتون 3.9 یا نسخههای قدیمیتر استفاده میکنید، یا اگر به هر دلیلی نمیتوانید از inspect.get_annotations() استفاده کنید، باید همان منطق را پیادهسازی کنید. به شما توصیه میشود پیادهسازی inspect.get_annotations() را در نسخهی فعلی پایتون بررسی کنید و رویکرد مشابهی را دنبال کنید.
بهطور خلاصه، اگر میخواهید یک حاشیهنویسی رشتهشده (stringized annotation) را روی یک شیء دلخواه o ارزیابی کنید:
اگر o یک ماژول باشد، از o.__dict__ بهعنوان globals هنگام فراخوانی eval() استفاده کنید.
اگر o یک کلاس باشد، هنگام فراخوانی eval()، از sys.modules[o.__module__].__dict__ بهعنوان globals و از dict(vars(o)) بهعنوان locals استفاده کنید.
اگر o یک فراخوانیپذیر پوششیشده با استفاده از functools.update_wrapper()، @functools.wraps یا functools.partial() باشد، آن را بهصورت تکراری با دسترسی به o.__wrapped__ یا o.func بسته به مورد از پوشش خارج کنید، تا تابع ریشهای بدون پوشش را پیدا کنید.
اگر o فراخوانیپذیر باشد (اما کلاس نباشد)، هنگام فراخوانی eval() از o.__globals__ بهعنوان سراسریها استفاده کنید.
با این حال، نمیتوان همهی مقادیر رشتهای را که بهعنوان حاشیهنویسی استفاده میشوند، با موفقیت بهوسیلهی eval() به مقادیر پایتون تبدیل کرد. مقادیر رشتهای از نظر تئوری میتوانند شامل هر رشتهی معتبری باشند و در عمل موارد استفادهی معتبری برای راهنماییهای نوع وجود دارد که مستلزم حاشیهنویسی با مقادیری رشتهای هستند که مشخصاً نمیتوانند ارزیابی شوند. برای مثال:
انواع اجتماعی (union types) PEP 604 با استفاده از |، پیش از آنکه پشتیبانی از این قابلیت به Python 3.10 اضافه شود.
تعریفهایی که در رانتایم نیازی به آنها نیست و فقط زمانی ایمپورت میشوند که typing.TYPE_CHECKING درست باشد.
اگر eval() سعی در ارزیابی چنین مقادیری داشته باشد، شکست خواهد خورد و استثنایی پرتاب خواهد کرد. بنابراین، هنگام طراحی API یک کتابخانه که با حاشیهنویسیها کار میکند، توصیه میشود تنها زمانی برای ارزیابی مقادیر رشتهای اقدام کنید که فراخواننده بهصراحت این کار را درخواست کرده باشد.
بهترین شیوهها برای __annotations__ در هر نسخهای از پایتون¶
باید از انتساب مستقیم به عضو __annotations__ شیءها خودداری کنید. اجازه دهید پایتون تنظیم __annotations__ را مدیریت کند.
اگر مستقیماً به عضو __annotations__ یک شیء انتساب میدهید، باید همیشه آن را روی یک شیء dict تنظیم کنید.
در تمام نسخههای Python 3، اشیای تابع اگر هیچیک از حاشیهنویسیها برای آنها تعریف نشده باشد، یک دیکشنری حاشیهنویسیها را بهصورت تنبل (lazy-create) ایجاد میکنند. شما میتوانید ویژگی __annotations__ را با استفاده از delfn.__annotations__ حذف کنید، اما اگر سپس به fn.__annotations__ دسترسی پیدا کنید، شیء یک دیکشنری خالی جدید ایجاد میکند که آن را ذخیره میکند و بهعنوان حاشیهنویسیهای خود بازمیگرداند. حذف حاشیهنویسیهای یک تابع پیش از آنکه دیکشنری حاشیهنویسیهای خود را بهصورت تنبل ایجاد کرده باشد، یک AttributeError را پرتاب میکند؛ تضمین میشود که استفاده از delfn.__annotations__ دو بار پشت سر هم، همیشه یک AttributeError را پرتاب کند.
همه موارد پاراگراف بالا، در پایتون 3.10 و نسخههای جدیدتر، برای اشیای کلاس و ماژول نیز اعمال میشود.
در همه نسخههای پایتون 3، میتوانید __annotations__ را روی یک شیء تابع به None تنظیم کنید. با این حال، دسترسی بعدی به حاشیهنویسیهای آن شیء با استفاده از fn.__annotations__، مطابق پاراگراف اول این بخش، یک دیکشنری خالی را بهصورت تنبل ایجاد میکند. این موضوع در هیچ نسخهای از پایتون برای ماژولها و کلاسها صادق نیست؛ آن اشیاء اجازه میدهند __annotations__ را به هر مقدار پایتون تنظیم کنید، و هر مقداری را که تنظیم شده باشد حفظ میکنند.
اگر پایتون حاشیهنویسیهای شمارا برایتان به رشته تبدیل کند (با استفاده از from__future__importannotations) و شما یک رشته را بهعنوان حاشیهنویسی مشخص کنید، خود رشته داخل علامت نقلقول قرار خواهد گرفت. در عمل، حاشیهنویسی داخل علامت نقلقول قرار میگیرد، دو بار. برای مثال:
این {'a':"'str'"} را چاپ میکند. این واقعاً نباید یک «رفتار عجیب» تلقی شود؛ اینجا صرفاً به این دلیل ذکر شده است که ممکن است غافلگیرکننده باشد.
اگر از کلاسی با فراکلاس سفارشی استفاده کنید و به __annotations__ کلاس دسترسی پیدا کنید، ممکن است رفتار غیرمنتظرهای را مشاهده کنید؛ برای مشاهده چند مثال، 749 را ببینید. میتوانید با استفاده از annotationlib.get_annotations() در پایتون 3.14+ یا inspect.get_annotations() در پایتون 3.10+ از این رفتارهای عجیب اجتناب کنید. در نسخههای پیشین پایتون، میتوانید با دسترسی به حاشیهنویسیها از طریق __dict__ کلاس، از این اشکالها اجتناب کنید (برای مثال، cls.__dict__.get('__annotations__',None)).
نکته
On Python 3.14 and later, annotations are evaluated lazily (see
PEP 649 and PEP 749), and how the annotations dict is stored in
the class's __dict__ is a private implementation detail.
Reading cls.__dict__.get('__annotations__',None) may therefore
return None even when the class has annotations. On these versions,
always use annotationlib.get_annotations() (or
inspect.get_annotations()) rather than accessing
__dict__ directly.
Only on Python 3.9 and earlier, where neither function is available, can you
avoid these bugs by reading the annotations from the class's
__dict__ (for example, cls.__dict__.get('__annotations__',None)).
در برخی نسخههای پایتون، نمونههای کلاسها ممکن است دارای ویژگی __annotations__ باشند. با این حال، این یک قابلیت پشتیبانیشده نیست. اگر به حاشیهنویسیهای یک نمونهنیاز دارید، میتوانید از type() برای دسترسی به کلاس آن استفاده کنید (برای مثال، annotationlib.get_annotations(type(myinstance)) در پایتون 3.14+).
آخرین بهروزرسانی در نوامبر 12, 2026 (06:19 UTC).
آیا اشکالی یافتید؟
ساخته شده با Sphinx 9.1.0.
You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.