1. ホーム
  2. python

[解決済み] 変数の文字列

2023-04-22 06:16:09

質問

docstringをプレーンな変数に使用することは可能ですか?例えば、私は t

def f():
    """f"""

l = lambda x: x
"""l"""

で、私は

>>> import t
>>> t.f.__doc__
'f'

しかし

>>> t.l.__doc__
>>> 

のような例です。 PEP 258 のものです("this is g"を検索してください)。

どのように解決するのですか?

使用方法 typing.Annotated を使用して、変数の docstring を提供します。

私はもともと、これは不可能であると言った回答(下記参照)を書きました。2012年当時はその通りでしたが、Pythonは進歩しました。今日では、グローバル変数やクラスまたはインスタンスの属性に対して、docstring に相当するものを提供することができます。これを動作させるには、少なくともPython 3.9を実行する必要があります。

from __future__ import annotations
from typing import Annotated

Feet = Annotated[float, "feet"]
Seconds = Annotated[float, "seconds"]
MilesPerHour = Annotated[float, "miles per hour"]

day: Seconds = 86400
legal_limit: Annotated[MilesPerHour, "UK national limit for single carriageway"] = 60
current_speed: MilesPerHour

def speed(distance: Feet, time: Seconds) -> MilesPerHour:
    """Calculate speed as distance over time"""
    fps2mph = 3600 / 5280  # Feet per second to miles per hour
    return distance / time * fps2mph

実行時にアノテーションにアクセスするには typing.get_type_hints() :

Python 3.9.1 (default, Jan 19 2021, 09:36:39) 
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import calc
>>> from typing import get_type_hints
>>> hints = get_type_hints(calc, include_extras=True)
>>> hints
{'day': typing.Annotated[float, 'seconds'], 'legal_limit': typing.Annotated[float, 'miles per hour', 'UK national limit for single carriageway'], 'current_speed': typing.Annotated[float, 'miles per hour']}

変数が宣言されたモジュールまたはクラスのヒントを使用して、変数に関する情報を抽出します。ネストしたときにアノテーションがどのように結合されるかに注意してください。

>>> hints['legal_limit'].__metadata__
('miles per hour', 'UK national limit for single carriageway')
>>> hints['day']
typing.Annotated[float, 'seconds']

型アノテーションを持つが値が割り当てられていない変数でも動作します。もし私がcalc.current_speedを参照しようとすると、属性エラーが発生しますが、そのメタデータにはまだアクセスすることができます。

>>> hints['current_speed'].__metadata__
('miles per hour',)

モジュールのタイプヒントはグローバル変数だけを含んでいます。 get_type_hints() を再度呼び出す必要があります。

>>> get_type_hints(calc.speed, include_extras=True)
{'distance': typing.Annotated[float, 'feet'], 'time': typing.Annotated[float, 'seconds'], 'return': typing.Annotated[float, 'miles per hour']}

私は今のところ typing.Annotated を使って変数に関するドキュメントを保存できるツールは、今のところPydanticだけです。これは単に docstring を格納するよりも少し複雑ですが、実際には のインスタンスを期待します。 pydantic.Field . 以下はその例です。

from typing import Annotated
import typing_extensions
from pydantic import Field
from pydantic.main import BaseModel
from datetime import date

# TypeAlias is in typing_extensions for Python 3.9:
FirstName: typing_extensions.TypeAlias = Annotated[str, Field(
        description="The subject's first name", example="Linus"
    )]

class Subject(BaseModel):
    # Using an annotated type defined elsewhere:
    first_name: FirstName = ""

    # Documenting a field inline:
    last_name: Annotated[str, Field(
        description="The subject's last name", example="Torvalds"
    )] = ""

    # Traditional method without using Annotated
    # Field needs an extra argument for the default value
    date_of_birth: date = Field(
        ...,
        description="The subject's date of birth",
        example="1969-12-28",
    )

モデルクラスを使って

>>> guido = Subject(first_name='Guido', last_name='van Rossum', date_of_birth=date(1956, 1, 31))
>>> print(guido)
first_name='Guido' last_name='van Rossum' date_of_birth=datetime.date(1956, 1, 31)

PydanticモデルはJSONスキーマを与えることができます。

>>> from pprint import pprint
>>> pprint(Subject.schema())
{'properties': {'date_of_birth': {'description': "The subject's date of birth",
                                  'example': '1969-12-28',
                                  'format': 'date',
                                  'title': 'Date Of Birth',
                                  'type': 'string'},
                'first_name': {'default': '',
                               'description': "The subject's first name",
                               'example': 'Linus',
                               'title': 'First Name',
                               'type': 'string'},
                'last_name': {'default': '',
                              'description': "The subject's last name",
                              'example': 'Torvalds',
                              'title': 'Last Name',
                              'type': 'string'}},
 'required': ['date_of_birth'],
 'title': 'Subject',
 'type': 'object'}
>>> 

FastAPIアプリケーションでこのクラスを使用する場合、OpenApi仕様には、関連するFieldから引用したこれら3つすべての例と説明があります。

そしてこれが、当時は正しかったが、時の試練に耐えていないオリジナルの答えです。

いいえ、それはできませんし、できても便利ではありません。

docstringは常にオブジェクト(モジュール、クラス、関数)の属性であり、特定の変数に結びつけられるものではありません。

つまり、もしあなたが ができました。 することができます。

t = 42
t.__doc__ = "something"  # this raises AttributeError: '__doc__' is read-only

という変数ではなく、整数値42のドキュメントを設定することになります。 t . を再定義するとすぐに t を再バインドするとすぐにdocstringを失います。文字列の数値のような不変のオブジェクトは、時に一つのオブジェクトを異なるユーザ間で共有することがあります。したがってこの例では、おそらく実際には 42 のすべての出現に対して docstring を設定したことになります。

print(42 .__doc__) # would print "something" if the above worked!

ミュータブルオブジェクトの場合、必ずしも有害ではありませんが、オブジェクトを再バインドした場合、まだ使用は限定的でしょう。

もしあなたがクラスの属性を文書化したいのであれば、それを記述するためにクラスのdocstringを使用してください。