コンテンツにスキップ

注釈付きバリデーター

??? API「APIドキュメント」 pydantic.functional_validators.WrapValidator
pydantic.functional_validators.PlainValidator
pydantic.functional_validators.BeforeValidator
pydantic.functional_validators.AfterValidator

Pydantic は、 Annotatedを使用してバリデータを適用する方法を提供します。検証をモデルやフィールドではなく型にバインドする場合は常にこれを使用する必要があります。

from typing import Any, List

from typing_extensions import Annotated

from pydantic import BaseModel, ValidationError
from pydantic.functional_validators import AfterValidator


def check_squares(v: int) -> int:
    assert v**0.5 % 1 == 0, f'{v} is not a square number'
    return v


def double(v: Any) -> Any:
    return v * 2


MyNumber = Annotated[int, AfterValidator(double), AfterValidator(check_squares)]


class DemoModel(BaseModel):
    number: List[MyNumber]


print(DemoModel(number=[2, 8]))
#> number=[4, 16]
try:
    DemoModel(number=[2, 4])
except ValidationError as e:
    print(e)
    """
    1 validation error for DemoModel
    number.1
      Assertion failed, 8 is not a square number
    assert ((8 ** 0.5) % 1) == 0 [type=assertion_error, input_value=4, input_type=int]
    """

この例では、いくつかの型エイリアス ( MyNumber = Annotated[...] ) を使用しました。これはコードの読みやすさに役立ちますが、必須ではありません。モデル フィールド タイプのヒントでAnnotatedを直接使用できます。これらの型エイリアスも実際の型ではありませんが、 TypeAliasTypeと同様のアプローチを使用して実際の型を作成できます。カスタム タイプの詳細については、 「カスタム タイプ」を参照してください。

Annotated他の型の中にネストできることにも注目してください。この例では、これを使用してリストの内部項目に検証を適用しました。同じアプローチを辞書キーなどにも使用できます。

Before、After、Wrap、および Plain バリデーター

Pydantic は複数のタイプのバリデーター関数を提供します。

  • Pydantic の内部解析後にバリデータが実行されたAfter 。これらは通常、よりタイプセーフであるため、実装が容易です。
  • Pydantic の内部解析と検証 (例: strからintへの強制) の前にバリデータが実行されるBefore 。これらは生の入力を変更できるため、 Afterバリデータよりも柔軟ですが、理論上は任意のオブジェクトである可能性がある生の入力も処理する必要があります。
  • Plainバリデーターは、 mode='before'バリデーターに似ていますが、検証をすぐに終了し、それ以上のバリデーターは呼び出されず、Pydantic は内部検証を行いません。
  • Wrapバリデーターは、すべての中で最も柔軟です。 Pydantic や他のバリデーターが動作する前または後にコードを実行したり、成功またはエラーの両方の値で検証をすぐに終了したりできます。

before、after、またはmode='wrap'バリデーターは複数使用できますが、プレーンなバリデーターは内部バリデーターを呼び出さないため、 PlainValidator 1 つだけです。

以下は、 mode='wrap'バリデーターの例です。

import json
from typing import Any, List

from typing_extensions import Annotated

from pydantic import (
    BaseModel,
    ValidationError,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
)
from pydantic.functional_validators import WrapValidator


def maybe_strip_whitespace(
    v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> int:
    if info.mode == 'json':
        assert isinstance(v, str), 'In JSON mode the input must be a string!'
        # you can call the handler multiple times
        try:
            return handler(v)
        except ValidationError:
            return handler(v.strip())
    assert info.mode == 'python'
    assert isinstance(v, int), 'In Python mode the input must be an int!'
    # do no further validation
    return v


MyNumber = Annotated[int, WrapValidator(maybe_strip_whitespace)]


class DemoModel(BaseModel):
    number: List[MyNumber]


print(DemoModel(number=[2, 8]))
#> number=[2, 8]
print(DemoModel.model_validate_json(json.dumps({'number': [' 2 ', '8']})))
#> number=[2, 8]
try:
    DemoModel(number=['2'])
except ValidationError as e:
    print(e)
    """
    1 validation error for DemoModel
    number.0
      Assertion failed, In Python mode the input must be an int!
    assert False
     +  where False = isinstance('2', int) [type=assertion_error, input_value='2', input_type=str]
    """

同じ「モード」が@field_validatorに適用されます。これについては次のセクションで説明します。

Annotated内のバリデーターの順序

Annotated事項内の検証メタデータの順序。検証は右から左、そして右から左へと進みます。つまり、右から左にすべての「前」バリデーターを実行し (または「ラップ」バリデーターを呼び出し)、次に左から右に戻り、すべての「後」バリデーターを呼び出します。

from typing import Any, Callable, List, cast

from typing_extensions import Annotated, TypedDict

from pydantic import (
    AfterValidator,
    BaseModel,
    BeforeValidator,
    PlainValidator,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
    WrapValidator,
)
from pydantic.functional_validators import field_validator


class Context(TypedDict):
    logs: List[str]


def make_validator(label: str) -> Callable[[Any, ValidationInfo], Any]:
    def validator(v: Any, info: ValidationInfo) -> Any:
        context = cast(Context, info.context)
        context['logs'].append(label)
        return v

    return validator


def make_wrap_validator(
    label: str,
) -> Callable[[Any, ValidatorFunctionWrapHandler, ValidationInfo], Any]:
    def validator(
        v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
    ) -> Any:
        context = cast(Context, info.context)
        context['logs'].append(f'{label}: pre')
        result = handler(v)
        context['logs'].append(f'{label}: post')
        return result

    return validator


class A(BaseModel):
    x: Annotated[
        str,
        BeforeValidator(make_validator('before-1')),
        AfterValidator(make_validator('after-1')),
        WrapValidator(make_wrap_validator('wrap-1')),
        BeforeValidator(make_validator('before-2')),
        AfterValidator(make_validator('after-2')),
        WrapValidator(make_wrap_validator('wrap-2')),
        BeforeValidator(make_validator('before-3')),
        AfterValidator(make_validator('after-3')),
        WrapValidator(make_wrap_validator('wrap-3')),
        BeforeValidator(make_validator('before-4')),
        AfterValidator(make_validator('after-4')),
        WrapValidator(make_wrap_validator('wrap-4')),
    ]
    y: Annotated[
        str,
        BeforeValidator(make_validator('before-1')),
        AfterValidator(make_validator('after-1')),
        WrapValidator(make_wrap_validator('wrap-1')),
        BeforeValidator(make_validator('before-2')),
        AfterValidator(make_validator('after-2')),
        WrapValidator(make_wrap_validator('wrap-2')),
        PlainValidator(make_validator('plain')),
        BeforeValidator(make_validator('before-3')),
        AfterValidator(make_validator('after-3')),
        WrapValidator(make_wrap_validator('wrap-3')),
        BeforeValidator(make_validator('before-4')),
        AfterValidator(make_validator('after-4')),
        WrapValidator(make_wrap_validator('wrap-4')),
    ]

    val_x_before = field_validator('x', mode='before')(
        make_validator('val_x before')
    )
    val_x_after = field_validator('x', mode='after')(
        make_validator('val_x after')
    )
    val_y_wrap = field_validator('y', mode='wrap')(
        make_wrap_validator('val_y wrap')
    )


context = Context(logs=[])

A.model_validate({'x': 'abc', 'y': 'def'}, context=context)
print(context['logs'])
"""
[
    'val_x before',
    'wrap-4: pre',
    'before-4',
    'wrap-3: pre',
    'before-3',
    'wrap-2: pre',
    'before-2',
    'wrap-1: pre',
    'before-1',
    'after-1',
    'wrap-1: post',
    'after-2',
    'wrap-2: post',
    'after-3',
    'wrap-3: post',
    'after-4',
    'wrap-4: post',
    'val_x after',
    'val_y wrap: pre',
    'wrap-4: pre',
    'before-4',
    'wrap-3: pre',
    'before-3',
    'plain',
    'after-3',
    'wrap-3: post',
    'after-4',
    'wrap-4: post',
    'val_y wrap: post',
]
"""

デフォルト値の検証

デフォルト値が使用されている場合、バリデーターは実行されません。これは、 @field_validatorバリデーターとAnnotatedバリデーターの両方に適用されます。 Field(validate_default=True)を使用して強制的に実行できます。 validate_default Trueに設定すると、Pydantic v1 のvalidatoralways=True使用することに最も近い動作になります。ただし、一般的には、 @model_validator(mode='before') ここで、関数は内部バリデーターが呼び出される前に呼び出されます。

from typing_extensions import Annotated

from pydantic import BaseModel, Field, field_validator


class Model(BaseModel):
    x: str = 'abc'
    y: Annotated[str, Field(validate_default=True)] = 'xyz'

    @field_validator('x', 'y')
    @classmethod
    def double(cls, v: str) -> str:
        return v * 2


print(Model())
#> x='abc' y='xyzxyz'
print(Model(x='foo'))
#> x='foofoo' y='xyzxyz'
print(Model(x='abc'))
#> x='abcabc' y='xyzxyz'
print(Model(x='foo', y='bar'))
#> x='foofoo' y='barbar'

フィールドバリデーター

??? API「APIドキュメント」 pydantic.functional_validators.field_validator

モデルの特定のフィールドにバリデーターをアタッチしたい場合は、 @field_validatorデコレーターを使用できます。

from pydantic import (
    BaseModel,
    ValidationError,
    ValidationInfo,
    field_validator,
)


class UserModel(BaseModel):
    name: str
    id: int

    @field_validator('name')
    @classmethod
    def name_must_contain_space(cls, v: str) -> str:
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v.title()

    # you can select multiple fields, or use '*' to select all fields
    @field_validator('id', 'name')
    @classmethod
    def check_alphanumeric(cls, v: str, info: ValidationInfo) -> str:
        if isinstance(v, str):
            # info.field_name is the name of the field being validated
            is_alphanumeric = v.replace(' ', '').isalnum()
            assert is_alphanumeric, f'{info.field_name} must be alphanumeric'
        return v


print(UserModel(name='John Doe', id=1))
#> name='John Doe' id=1

try:
    UserModel(name='samuel', id=1)
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
    name
      Value error, must contain a space [type=value_error, input_value='samuel', input_type=str]
    """

try:
    UserModel(name='John Doe', id='abc')
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
    id
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='abc', input_type=str]
    """

try:
    UserModel(name='John Doe!', id=1)
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
    name
      Assertion failed, name must be alphanumeric
    assert False [type=assertion_error, input_value='John Doe!', input_type=str]
    """

バリデーターに関して注意すべき点がいくつかあります。

  • @field_validatorは「クラス メソッド」であるため、 @field_validator が受け取る最初の引数値はUserModelのインスタンスではなく、 UserModelクラスです。適切な型チェックを行うには、 @field_validatorデコレータの下で@classmethodデコレータを使用することをお勧めします。
  • 2 番目の引数は検証するフィールド値です。好きなように名前を付けることができます
  • 3 番目の引数が存在する場合、それはpydantic.ValidationInfoのインスタンスです。
  • バリデータは解析された値を返すか、 ValueErrorまたはAssertionErrorを発生させる必要があります ( assertステートメントを使用することもできます)。
  • 単一のバリデーターに複数のフィールド名を渡すことで、複数のフィールドに適用できます。
  • 特別な値'*'を渡すことによって、_すべての_フィールドに対して単一のバリデータを呼び出すこともできます。

!!!警告assertステートメントを使用する場合は、 -O最適化フラグを指定して Python を実行すると、 assertステートメントが無効になり、バリデーターが機能しなくなることに注意してください。

!!!注: FieldValidationInfoは 2.4 で非推奨になりました。代わりにValidationInfo使用してください。

@field_validator内の別のフィールドの値にアクセスしたい場合は、フィールド名とフィールド値の辞書であるValidationInfo.dataを使用すると可能になる場合があります。検証はフィールドが定義された順序で行われるため、 ValidationInfo.data使用するときは、まだ検証/入力されていないフィールドにアクセスしないように注意する必要があります。たとえば、上記のコードでは、次のフィールドにアクセスできません。 name_must_contain_space内からのinfo.data['id'] 。ただし、複数のフィールド値を使用して検証を実行する場合、ほとんどの場合は、以下のセクションで説明する@model_validator使用することをお勧めします。

モデルバリデーター

??? API「APIドキュメント」 pydantic.functional_validators.model_validator

@model_validatorを使用してモデル全体のデータに対して検証を実行することもできます。

from typing import Any

from typing_extensions import Self

from pydantic import BaseModel, ValidationError, model_validator


class UserModel(BaseModel):
    username: str
    password1: str
    password2: str

    @model_validator(mode='before')
    @classmethod
    def check_card_number_omitted(cls, data: Any) -> Any:
        if isinstance(data, dict):
            assert (
                'card_number' not in data
            ), 'card_number should not be included'
        return data

    @model_validator(mode='after')
    def check_passwords_match(self) -> Self:
        pw1 = self.password1
        pw2 = self.password2
        if pw1 is not None and pw2 is not None and pw1 != pw2:
            raise ValueError('passwords do not match')
        return self


print(UserModel(username='scolvin', password1='zxcvbn', password2='zxcvbn'))
#> username='scolvin' password1='zxcvbn' password2='zxcvbn'
try:
    UserModel(username='scolvin', password1='zxcvbn', password2='zxcvbn2')
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
      Value error, passwords do not match [type=value_error, input_value={'username': 'scolvin', '... 'password2': 'zxcvbn2'}, input_type=dict]
    """

try:
    UserModel(
        username='scolvin',
        password1='zxcvbn',
        password2='zxcvbn',
        card_number='1234',
    )
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
      Assertion failed, card_number should not be included
    assert 'card_number' not in {'card_number': '1234', 'password1': 'zxcvbn', 'password2': 'zxcvbn', 'username': 'scolvin'} [type=assertion_error, input_value={'username': 'scolvin', '..., 'card_number': '1234'}, input_type=dict]
    """

!!! note 「戻り値の型チェック時」 @model_validatorで修飾されたメソッドは、メソッドの最後に self インスタンスを返す必要があります。型チェックの目的で、装飾されたメソッドの戻り値の型として、 typingまたはtyping_extensionsバックポートのいずれかからSelfを使用できます。上記の例のコンテキストでは、次のように使用することもできます。 def check_passwords_match(self: 'UserModel') -> 'UserModel' メソッドがモデルのインスタンスを返すことを示します。

!!! note 「継承時」 基本クラスで定義された@model_validator 、サブクラス インスタンスの検証中に呼び出されます。

Overriding a `@model_validator` in a subclass will override the base class' `@model_validator`, and thus only the subclass' version of said `@model_validator` will be called.

モデルバリデーターはmode='before'mode='after'またはmode='wrap'のいずれかになります。

モデルバリデーターに渡される前に、生の入力が渡されます。これは、多くの場合、 dict[str, Any]ですが、モデル自体のインスタンスである場合もあります (例: UserModel.model_validate(UserModel.construct(...)) が呼び出されます)、または任意のオブジェクトをmodel_validateに渡すことができるため、その他の何か。このmode='before'のおかげで、バリデーターは非常に柔軟で強力ですが、実装が面倒でエラーが発生しやすい可能性があります。モデルバリデータの前にはクラスメソッドを使用する必要があります。最初の引数はclsである必要があり (また、適切な型チェックのために@model_validatorの下に@classmethod使用することをお勧めします)、2 番目の引数は入力になります (通常はAnyと入力し、型を絞り込むにはisinstance使用する必要があります)。引数 (存在する場合) はpydantic.ValidationInfoになります。

mode='after'バリデーターはインスタンス メソッドであり、常に最初の引数としてモデルのインスタンスを受け取ります。バリデーターの最後で必ずインスタンスを返してください。署名として(cls, ModelType)を使用せず、代わりに(self)を使用し、型チェッカーにselfの型を推測させます。これらは完全にタイプセーフであるため、多くの場合、 mode='before'バリデーターよりも実装が簡単です。いずれかのフィールドが検証に失敗した場合、そのフィールドのmode='after'バリデータは呼び出されません。

バリデーターでのエラーの処理

前のセクションで説明したように、バリデータ内でValueErrorまたはAssertionError ( assert ...ステートメントによって生成されたものを含む) を発生させて、検証が失敗したことを示すことができます。 PydanticCustomError発生させることもできます。これはもう少し冗長ですが、柔軟性が高まります。その他のエラー ( TypeErrorを含む) はバブルアップされ、 ValidationErrorにラップされません。

from pydantic_core import PydanticCustomError

from pydantic import BaseModel, ValidationError, field_validator


class Model(BaseModel):
    x: int

    @field_validator('x')
    @classmethod
    def validate_x(cls, v: int) -> int:
        if v % 42 == 0:
            raise PydanticCustomError(
                'the_answer_error',
                '{number} is the answer!',
                {'number': v},
            )
        return v


try:
    Model(x=42 * 2)
except ValidationError as e:
    print(e)
    """
    1 validation error for Model
    x
      84 is the answer! [type=the_answer_error, input_value=84, input_type=int]
    """

特殊なタイプ

Pydantic は、検証をカスタマイズするために使用できるいくつかの特別なタイプを提供します。

  • [InstanceOf][pydantic.function_validators.InstanceOf] は、値が特定のクラスのインスタンスであることを検証するために使用できる型です。

    from typing import List

    from pydantic import BaseModel, InstanceOf, ValidationError

    class Fruit: def repr(self): return self.class.name

    class Banana(Fruit): ...

    class Apple(Fruit): ...

    class Basket(BaseModel): fruits: List[InstanceOf[Fruit]]

    print(Basket(fruits=[Banana(), Apple()]))

    > fruits=[Banana, Apple]

    try: Basket(fruits=[Banana(), 'Apple']) except ValidationError as e: print(e) """ 1 validation error for Basket fruits.1 Input should be an instance of Fruit [type=is_instance_of, input_value='Apple', input_type=str] """

  • [SkipValidation][pydantic.function_validators.SkipValidation] は、フィールドの検証をスキップするために使用できる型です。

    from typing import List

    from pydantic import BaseModel, SkipValidation

    class Model(BaseModel): names: List[SkipValidation[str]]

    m = Model(names=['foo', 'bar']) print(m)

    > names=['foo', 'bar']

    m = Model(names=['foo', 123]) # (1)! print(m)

    > names=['foo', 123]

  • 2 番目の項目の検証はスキップされることに注意してください。タイプが間違っている場合は、シリアル化中に警告が表示されます。

フィールドチェック

クラスの作成中に、バリデーターがチェックされて、指定したフィールドがモデル上に実際に存在するかどうかが確認されます。

これは、たとえば、バリデーターが定義されているモデルのサブクラスにのみ存在するフィールドを検証するためにバリデーターを定義する場合には、望ましくない可能性があります。

クラスの作成中にこれらのチェックを無効にしたい場合は、キーワード引数としてcheck_fields=Falseバリデーターに渡すことができます。

データクラスバリデーター

バリデーターは Pydantic データクラスでも動作します。

from pydantic import field_validator
from pydantic.dataclasses import dataclass


@dataclass
class DemoDataclass:
    product_id: str  # should be a five-digit string, may have leading zeros

    @field_validator('product_id', mode='before')
    @classmethod
    def convert_int_serial(cls, v):
        if isinstance(v, int):
            v = str(v).zfill(5)
        return v


print(DemoDataclass(product_id='01234'))
#> DemoDataclass(product_id='01234')
print(DemoDataclass(product_id=2468))
#> DemoDataclass(product_id='02468')

検証コンテキスト

コンテキスト オブジェクトを検証メソッドに渡すことができます。検証メソッドには、 info引数から装飾されたバリデーター関数にアクセスできます。

from pydantic import BaseModel, ValidationInfo, field_validator


class Model(BaseModel):
    text: str

    @field_validator('text')
    @classmethod
    def remove_stopwords(cls, v: str, info: ValidationInfo):
        context = info.context
        if context:
            stopwords = context.get('stopwords', set())
            v = ' '.join(w for w in v.split() if w.lower() not in stopwords)
        return v


data = {'text': 'This is an example document'}
print(Model.model_validate(data))  # no context
#> text='This is an example document'
print(Model.model_validate(data, context={'stopwords': ['this', 'is', 'an']}))
#> text='example document'
print(Model.model_validate(data, context={'stopwords': ['document']}))
#> text='This is an example'

これは、実行時に検証動作を動的に更新する必要がある場合に便利です。たとえば、フィールドに動的に制御可能な一連の許可値を持たせたい場合は、許可値をコンテキストで渡し、許可内容を更新する別のメカニズムを用意することで実現できます。

from typing import Any, Dict, List

from pydantic import (
    BaseModel,
    ValidationError,
    ValidationInfo,
    field_validator,
)

_allowed_choices = ['a', 'b', 'c']


def set_allowed_choices(allowed_choices: List[str]) -> None:
    global _allowed_choices
    _allowed_choices = allowed_choices


def get_context() -> Dict[str, Any]:
    return {'allowed_choices': _allowed_choices}


class Model(BaseModel):
    choice: str

    @field_validator('choice')
    @classmethod
    def validate_choice(cls, v: str, info: ValidationInfo):
        allowed_choices = info.context.get('allowed_choices')
        if allowed_choices and v not in allowed_choices:
            raise ValueError(f'choice must be one of {allowed_choices}')
        return v


print(Model.model_validate({'choice': 'a'}, context=get_context()))
#> choice='a'

try:
    print(Model.model_validate({'choice': 'd'}, context=get_context()))
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    choice
      Value error, choice must be one of ['a', 'b', 'c'] [type=value_error, input_value='d', input_type=str]
    """

set_allowed_choices(['b', 'c'])

try:
    print(Model.model_validate({'choice': 'a'}, context=get_context()))
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    choice
      Value error, choice must be one of ['b', 'c'] [type=value_error, input_value='a', input_type=str]
    """

同様に、シリアル化にコンテキストを使用できます。

BaseModel初期化で検証コンテキストを使用する

標準のBaseModelイニシャライザでコンテキストを指定する方法はありませんが、 contextvars.ContextVarとカスタム__init__メソッドを使用することでこれを回避できます。

from contextlib import contextmanager
from contextvars import ContextVar
from typing import Any, Dict, Iterator

from pydantic import BaseModel, ValidationInfo, field_validator

_init_context_var = ContextVar('_init_context_var', default=None)


@contextmanager
def init_context(value: Dict[str, Any]) -> Iterator[None]:
    token = _init_context_var.set(value)
    try:
        yield
    finally:
        _init_context_var.reset(token)


class Model(BaseModel):
    my_number: int

    def __init__(self, /, **data: Any) -> None:
        self.__pydantic_validator__.validate_python(
            data,
            self_instance=self,
            context=_init_context_var.get(),
        )

    @field_validator('my_number')
    @classmethod
    def multiply_with_context(cls, value: int, info: ValidationInfo) -> int:
        if info.context:
            multiplier = info.context.get('multiplier', 1)
            value = value * multiplier
        return value


print(Model(my_number=2))
#> my_number=2

with init_context({'multiplier': 3}):
    print(Model(my_number=2))
    #> my_number=6

print(Model(my_number=2))
#> my_number=2

バリデータの再利用

場合によっては、複数のフィールド/モデルで同じバリデーターを使用したい場合があります (たとえば、一部の入力データを正規化するため)。 「単純な」アプローチは、別の関数を作成し、それを複数のデコレータから呼び出すことです。明らかに、これには多くの繰り返しと定型コードが必要になります。次のアプローチは、冗長性を最小限に抑え、モデルを再び宣言型に近づけるためにバリデーターを再利用する方法を示しています。

from pydantic import BaseModel, field_validator


def normalize(name: str) -> str:
    return ' '.join((word.capitalize()) for word in name.split(' '))


class Producer(BaseModel):
    name: str

    _normalize_name = field_validator('name')(normalize)


class Consumer(BaseModel):
    name: str

    _normalize_name = field_validator('name')(normalize)


jane_doe = Producer(name='JaNe DOE')
print(repr(jane_doe))
#> Producer(name='Jane Doe')
john_doe = Consumer(name='joHN dOe')
print(repr(john_doe))
#> Consumer(name='John Doe')

本文总阅读量