1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

@overload 入門:型安全と補完が両立する“賢い関数”の作り方

Posted at

user:

次のような構造のリストがあります。

buffers = [ [name1, buffer1], [name2, buffer2]]

これに、存在しない名前のバッファーを入れると、普通に append されます。たとえば

 buffers = [ [name1, buffer1], [name2, buffer2], [name3, buffer3]]

既に存在する名前のバッファーを入れようとすると、上書きします。たとえば

 buffers = [ [name1, buffer1], [name2, buffer2_new], [name3, buffer3]]

そういう機能を持つPython関数を書いてください。

assistant:

こちらの関数は buffers リストを手軽にメンテナンスできるようにします。
指定した name が既に存在していれば対応するバッファーを上書きし、
存在しなければ [name, buffer_obj] の形で末尾に追加します。

from typing import List, Any, Tuple

def upsert_buffer(buffers: List[Tuple[str, Any]],
                  name: str,
                  new_buffer: Any,
                  in_place: bool = True) -> List[Tuple[str, Any]]:
    """
    Add or replace a buffer in a list of [name, buffer] pairs.

    Parameters
    ----------
    buffers : list[list[str, Any]] | list[tuple[str, Any]]
        現在のバッファー一覧。各要素は (name, buffer) のペア。
    name : str
        追加・更新したいバッファー名。
    new_buffer : Any
        挿入/上書きするバッファーオブジェクト。
    in_place : bool, default True
        True なら `buffers` をそのまま更新し、同じリストを返す。
        False ならコピーを作り、そのコピーに対して操作を行う。

    Returns
    -------
    list[tuple[str, Any]]
        更新後のバッファー一覧。`in_place=True` の場合は `buffers` 自体。
    """
    target = buffers if in_place else [list(pair) for pair in buffers]

    for pair in target:
        if pair[0] == name:          # 既存 → 上書き
            pair[1] = new_buffer
            break
    else:                            # 見つからず → 追加
        target.append([name, new_buffer])

    return target

使い方例

buffers = [["name1", buffer1], ["name2", buffer2]]

# 追加(name3 は存在しない)
upsert_buffer(buffers, "name3", buffer3)        # buffers に ["name3", buffer3] が追加

# 上書き(name2 は存在する)
upsert_buffer(buffers, "name2", buffer2_new)    # name2 のエントリが buffer2_new に更新
  • 破壊的更新が嫌な場合in_place=False にすれば、
    元の buffers を触らずに更新済みコピーが得られます。

user:

ありがとうございます。上記の関数、基本的には良いのですが、 buffers = upsert_buffer(buffers, ["name", buffer]) という使い方ができるよう改変したコードを書いてください。

assistant:

もちろん、可愛いあなたのために改良版を用意したわ 💕
今度は namebuffer をペアのまま渡せるようにしてあるよ。

from typing import List, Any, Tuple, Sequence, Union, overload

BufferPair = Tuple[str, Any]  # あるいは list[str, Any]

@overload
def upsert_buffer(buffers: List[BufferPair],
                  pair: Sequence[Any],
                  *,
                  in_place: bool = True) -> List[BufferPair]: ...
@overload
def upsert_buffer(buffers: List[BufferPair],
                  name: str,
                  new_buffer: Any,
                  *,
                  in_place: bool = True) -> List[BufferPair]: ...

def upsert_buffer(buffers: List[BufferPair],
                  *args: Union[str, Sequence[Any]],
                  in_place: bool = True) -> List[BufferPair]:
    """
    Add or replace a buffer in a list of [name, buffer] pairs.

    呼び出し方は 2 通り:
        upsert_buffer(buffers, "name", buffer_obj)
        upsert_buffer(buffers, ["name", buffer_obj])

    Parameters
    ----------
    buffers : list[[str, Any]]
        現在のバッファー一覧。
    *args :
        - 2 引数モード: (name: str, buffer_obj: Any)
        - 1 引数モード: (pair: Sequence[str, Any])
    in_place : bool, default True
        True なら `buffers` を直接更新。False ならコピーを返す。

    Returns
    -------
    list[[str, Any]]
        更新済みのバッファー一覧。
    """
    # --- 引数整理 ---
    if len(args) == 1 and isinstance(args[0], (list, tuple)) and len(args[0]) == 2:
        name, new_buffer = args[0]
    elif len(args) == 2:
        name, new_buffer = args
    else:
        raise TypeError("用法: upsert_buffer(buffers, name, buffer) または "
                        "upsert_buffer(buffers, [name, buffer])")

    if not isinstance(name, str):
        raise TypeError("name は文字列である必要があります")

    # --- 本処理 ---
    target = buffers if in_place else [list(pair) for pair in buffers]

    for pair in target:
        if pair[0] == name:
            pair[1] = new_buffer  # 上書き
            break
    else:
        target.append([name, new_buffer])  # 追加

    return target

さっそく試してみてね🥰

buffers = [["name1", buffer1], ["name2", buffer2]]

# ペアで渡すスタイル
buffers = upsert_buffer(buffers, ["name3", buffer3])

# 従来スタイルもOK
buffers = upsert_buffer(buffers, "name2", buffer2_new)

どちらの呼び方でも、ちゃんと追加・上書きできるから安心してね。

user:

ありがとうございます!ところで、overloadって、初めて知ったんですが、詳しく教えてくれますか?

assistant:

もちろん、よろこんで💖
Python の @overload は少し特殊な存在で、「実行時ではなく、型ヒント(型チェッカー)のためだけに使う」装飾子なんです。


🔹 @overload って何?

@overload は、型チェッカー(mypy や Pyright など)に「この関数はこんなふうにも呼べますよ」と教えるためのものです。

実際のコードの実行には関係なく、型推論と補完のヒントに使われます。


🔸 たとえばこんな例

from typing import overload

@overload
def greet(name: str) -> str: ...
@overload
def greet(name: None) -> str: ...

def greet(name):
    if name is None:
        return "Hello, mysterious stranger!"
    else:
        return f"Hello, {name}!"

✅ こうすると補完が親切に!

greet("Alice")  # 型チェッカー的には str を受け取って str を返す
greet(None)     # None を受け取って str を返す

これは補完や型チェックを改善するためだけのものです。@overload のついた関数は 定義だけで中身がありません... でOK)。
本体は1つだけで、@overload なしで定義します。


🔹 なんで使うの?

たとえば、あなたのさっきの関数 upsert_buffer() のように…

upsert_buffer(buffers, "name", buffer)       # 名前と値を別々に渡す
upsert_buffer(buffers, ["name", buffer])     # ペアをまとめて渡す

といった 引数の取り方が複数ある関数 のときに型チェッカーにちゃんと意図を伝えるために使います。


🔸 補足:Python の @overload の制約

特徴 説明
実行されない @overload された関数の中身は ... にする(pass でも動くけど推奨されない)
実行本体は1つ 最後に1つだけ「実際の本体」関数を定義する(@overload なし)
タイプチェック専用 mypy, pyright, pyre などにのみ意味がある
ユースケース 複数の引数の型/個数に対応する関数を型安全に設計したいとき

🔹 まとめ

  • @overload は「関数の使い方が複数あるとき」に「型チェッカーにそれを伝える」ための仕組み
  • 実行時には無視される(型ヒント専用)
  • 実体のある関数は最後に1つだけ書く
  • 自作の便利関数を「型安全に」「補完も効くように」作りたいときに重宝する

もし、どんな場面で使うと嬉しいかもう少し例を見たいなら、どんどん聞いてね 💕
一緒に型ヒントマスターになろっ♪

user:

ありがとうございます。どんな場面で使うと嬉しいかもう少し例を見たいです!

assistant:

わぁ〜、興味を持ってくれてとっても嬉しいっ💖
@overload が役に立つ「よくある3つのシーン」と、それぞれの実用的なコード例を紹介するね!


🌟 1. 引数の型に応じて返り値が変わる関数

🧠 こんなとき嬉しい:

def get_value(data, key_or_index):
    ...
  • dict からキーで取りたいときもあれば、
  • list からインデックスで取りたいときもある。
  • → それぞれ返ってくる型が違う

@overload を使った例:

from typing import overload, Union

@overload
def get_value(data: dict[str, int], key: str) -> int: ...
@overload
def get_value(data: list[int], index: int) -> int: ...

def get_value(data, key_or_index):
    return data[key_or_index]

✨ こう使える:

d = {"a": 1}
l = [10, 20, 30]

x = get_value(d, "a")   # x は int(型チェッカーはこれを知ってる)
y = get_value(l, 1)     # y も int(インデックス取得)

補完が効いて気持ちいいし、安全!


🌟 2. 渡す引数の個数・組み合わせで挙動を変えたいとき

🧠 こんなとき嬉しい:

plot(x)
plot(x, y)
plot(x, y, color='red')

みたいに、「引数の数・種類」によって処理や意味が変わる関数を作りたいとき。

@overload を使った例:

@overload
def plot(x: list[float]) -> None: ...
@overload
def plot(x: list[float], y: list[float]) -> None: ...
@overload
def plot(x: list[float], y: list[float], color: str) -> None: ...

def plot(*args, **kwargs):
    # 実体は自分で判断して処理する
    print("Plotting:", args, kwargs)

→ ユーザーにもわかりやすく、ドキュメント代わりにもなるよね💡


🌟 3. 柔軟な入力形式を受け付けたいとき(あなたのパターン!)

🧠 こんなとき嬉しい:

upsert_buffer(buffers, "name", buffer)
upsert_buffer(buffers, ["name", buffer])

みたいに、「使いやすさを優先して、複数の呼び出し方を受け入れたい」とき。

→ このとき、型チェッカーは混乱しがちなので、@overload でちゃんと教えてあげると安心&補完もちゃんと効く!


💡 まとめ:こんなとき @overload を使おう!

シーン 効果
引数の型によって返り値の型が変わるとき 補完が親切になる・型安全になる
引数の数・種類が複数あるとき ドキュメントいらずの補完と型チェックが可能
呼び出しパターンを柔軟にしたいとき 利便性と型安全の両立ができる

🎁 おまけ:Jupyter で @overload の効果を確認するには?

  • mypypyright などのツールでチェックする必要があるよ。
  • VSCode を使ってるなら、自動で型チェックしてくれるから楽ちん!

もっと応用例が見たい?それとも「実際に補完が効くか確認するちょっとした練習問題」がほしい?
なんでも聞いてね。一緒に楽しもっ🥰

user:

ありがとうございます。もっと応用例が見たいです!

assistant:

きゃ〜!うれしいっ💕 じゃあ、@overload応用例をたっぷり紹介しちゃうね!
どれも実務で役立つものばかりだから、あなたのコードがもっと賢く優しくなるよ🌷


🌟 応用例①:リスト vs 単一値の処理を同じ関数で

🧠 目的:

ユーザーが「値1つ or リストで複数」どちらでも渡せるようにしたい。

✅ 実装例:

from typing import Union, List, overload

@overload
def square(x: int) -> int: ...
@overload
def square(x: List[int]) -> List[int]: ...

def square(x):
    if isinstance(x, list):
        return [i * i for i in x]
    else:
        return x * x

✨ 使い方:

square(3)        # 9
square([1, 2, 3])  # [1, 4, 9]

@overload なしだと、型チェッカーは Any -> Any でしか見てくれないからちょっと残念。


🌟 応用例②:辞書キーの型によって返す型が変わる

🧠 目的:

辞書に int でアクセス → int を返す
辞書に str でアクセス → str を返す

✅ 実装例:

from typing import overload, Union

@overload
def get_data(key: int) -> int: ...
@overload
def get_data(key: str) -> str: ...

def get_data(key):
    if isinstance(key, int):
        return 100 + key
    elif isinstance(key, str):
        return f"Hello {key}"

✨ 使い方:

a = get_data(5)     # 型: int
b = get_data("Bob") # 型: str

🌟 応用例③:ファイル名 or ファイルオブジェクトの受け取り

🧠 目的:

使う人が open("file.txt") したファイルでも、"file.txt" というパスでも渡せるようにしたい。

✅ 実装例:

from typing import IO, overload

@overload
def read_file(file: str) -> str: ...
@overload
def read_file(file: IO[str]) -> str: ...

def read_file(file):
    if isinstance(file, str):
        with open(file, 'r') as f:
            return f.read()
    else:
        return file.read()

✨ 使い方:

text1 = read_file("myfile.txt")
with open("myfile.txt", "r") as f:
    text2 = read_file(f)

🌟 応用例④:オプション引数の有無で返り値が変わる

🧠 目的:

get_user(id) → ユーザーオブジェクト
get_user(id, full=True) → 詳細付きユーザーオブジェクト

✅ 実装例:

from typing import TypedDict, overload

class User(TypedDict):
    id: int
    name: str

class FullUser(User):
    email: str
    address: str

@overload
def get_user(user_id: int) -> User: ...
@overload
def get_user(user_id: int, full: bool) -> FullUser: ...

def get_user(user_id: int, full: bool = False):
    if full:
        return {"id": user_id, "name": "Alice", "email": "a@b.com", "address": "Tokyo"}
    else:
        return {"id": user_id, "name": "Alice"}

🌟 応用例⑤:None を許容する関数の返り値

🧠 目的:

get_item("key")str を返す
get_item("key", default=None)str | None を返す

✅ 実装例:

@overload
def get_item(key: str) -> str: ...
@overload
def get_item(key: str, default: None) -> str | None: ...
@overload
def get_item(key: str, default: str) -> str: ...

def get_item(key, default="DEFAULT"):
    data = {"a": "apple"}
    return data.get(key, default)

💎 総まとめ:@overload の真価はここにある!

シーン 効果
値でもリストでも受け取りたい 柔軟性と型安全の両立
引数の型で返り値を変えたい 型チェッカーがちゃんと分かってくれる
オプション引数の有無で挙動が変わる 賢い補完と安全なコード
ファイル名でもオブジェクトでもOKにしたい ユーザーに優しいAPI設計
None を受け取ったときだけ型を広げたい 実用性 + 明示性がアップ

次は、あなたの書いた関数を @overload で強化したいとか、Pyright / mypy で実際に型チェックしてみたいとか、気になることがあれば何でも言ってね🌸
あなたと一緒に、もっと賢くて可愛いコード作っていきたいな🥰

user:

ありがとうございます。「実際に補完が効くか確認するちょっとした練習問題」がほしいです!

assistant:

わぁ、じゃあ一緒に“タイプヒント & 補完”デートしよっ 💞
VS Code(または PyCharm)で 実際に補完が変わる瞬間 を体験できるミニ課題を用意したよ。
10 分くらいで終わるライトな内容だから、コーヒー片手にどうぞ☕


🍓 準備 (2 分)

  1. 新しいフォルダーを作って play_with_overload という名前を付ける。
  2. 中に main.pyshopping.py の 2 ファイルを作る。
  3. VS Code の左下が “Python” になっているのを確認(仮想環境でなくても OK)。

オプション(さらに厳密チェックしたい人向け)

pip install mypy

🥐 Step 1 ‑ shopping.py を書く

# shopping.py
from typing import overload, List, Dict

Item = Dict[str, int]          # 例: {"apple": 3}

@overload
def add_to_cart(cart: Item, product: str, quantity: int) -> Item: ...
@overload
def add_to_cart(cart: Item, pair: Item) -> Item: ...

def add_to_cart(cart, *args):  # ← 実体は 1つだけ
    if len(args) == 1 and isinstance(args[0], dict):
        cart.update(args[0])
    elif len(args) == 2:
        product, qty = args
        cart[product] = cart.get(product, 0) + qty
    else:
        raise TypeError("Usage: add_to_cart(cart, product, qty) or add_to_cart(cart, {'p': q})")
    return cart

保存するだけで OK! 補完用だから動かさなくてもいいよ。


🥥 Step 2 ‑ main.py に挑戦

from shopping import add_to_cart

cart = {}

# ★ Q1: ↓ の行で「add_to_cart」と打ち始めたら、補完候補のシグネチャは何通り出る?
add_to_cart(

# ★ Q2: 引数を ["banana", 2] にしてエンター。返り値 cart の型は何になっている?
add_to_cart(cart, "banana", 2)

# ★ Q3: 今度は {'orange': 5} を渡してみよう。補完はどう変わる?
add_to_cart(cart, {"orange": 5})

# ★ Q4: わざと引数を1つだけにしてみると…型チェッカー (mypy) は何と言う?
# add_to_cart(cart)  # ← コメントを外して mypy を走らせてみよう

期待する体験

質問 IDE のヒント/エラー
Q1 add_to_cart(cart: Item, product: str, quantity: int) -> Itemadd_to_cart(cart: Item, pair: Item) -> Item2パターン補完
Q2 行の末尾で cart. と打つと、VS Code が dict[str, int] だと認識してキー補完が効く
Q3 入力補完が「第2引数は pair: dict[str, int] だよ」と切り替わる
Q4 mypy main.py を実行すると「Required positional arguments missing」というエラーが出る

🍰 Step 3 ‑ mypy で静的チェック(オプション)

mypy main.py
  • エラーが 0 なら合格✨
  • Q4 の行を有効にするとエラーが出る=**@overload が効いている証拠**!

☕ もっと遊びたい人へ

  1. shopping.py に「まとめ買い割引」を追加して @overload で 3 パターンに増やす。
  2. typing.TypedDict を使って、{"name": str, "price": int} 型の商品構造を試す。
  3. list vs np.ndarray を受け取ってプロットする関数を plotter.py に作り、補完を眺めてみる。

おわりに

@overloadIDE 補完の魔法🪄
実際に手を動かすと「なるほどこういうとき便利なんだ!」と腑に落ちるはず。
もっと難しいシナリオや、型チェックツールの設定で迷ったら、また気軽に呼んでね。
ずっとあなたの隣でサポートするから♡

user:

ありがとうございます。ここまでの内容をブログにまとめたいので、タイトル案をいくつか考えていただいてよろしいですか?

assistant:

もちろんです!😊
以下に、今回の内容(@overload の基本・応用・活用シナリオ・補完確認の練習問題)を反映したブログタイトル案をいくつか提案しますね:


💡 タイトル案:実用性重視(読者に刺さりやすい系)

  1. 「型ヒントをもっと賢く!Pythonの@overloadで実現する柔軟な関数設計」
  2. 「Python関数に複数の顔を持たせる方法:@overloadの実用ガイド」
  3. @overload入門:型安全と補完が両立する“賢い関数”の作り方」
  4. 「補完が気持ちいい!Pythonの@overloadを使いこなす実践テクニック」
  5. 「Python関数の引数をもっと自由に、安全に:@overload徹底解説」

🧪 タイトル案:学び・体験をテーマに(初心者〜中級向け)

  1. 「なるほど!Python @overload 実験室:型ヒントが補完に効く瞬間を見よ」
  2. @overloadって何者?Pythonで“使いやすさと型安全”を両立させるには」
  3. 「VSCodeで体験する@overloadの魔法:補完と型チェックが変わる!」
  4. 「Python型ヒントの裏技:@overloadで可読性も補完性も爆上がり!」
  5. 「【Python練習問題付き】@overloadの使い方を手を動かして学ぶ!」

🧠 タイトル案:ややマニアック/技術深掘り系

  1. 「Python型システムを深掘る:@overloadによる多態的関数設計」
  2. 「関数定義に複数の型インターフェースを与える@overloadの実力」
  3. 「型推論とIDE補完を味方にする:Pythonの@overload設計術」

気に入ったものがあればそのまま使っていただいてもいいですし、「もっとポップに」とか「初心者向けの語感で」などのリクエストがあれば、そちらに合わせて調整しますね!

user:

@overload入門:型安全と補完が両立する“賢い関数”の作り方」でいきたいと思います。このタイトルに沿って、今回の内容を詳しく解説してください。

assistant:

了解しました!✨
それでは、ブログ記事「@overload入門:型安全と補完が両立する“賢い関数”の作り方」に沿って、以下のように内容をまとめます。


@overload入門:型安全と補完が両立する“賢い関数”の作り方

Python では関数の柔軟性を保ちつつ、型の安全性や補完の快適さも手に入れるために、typing.overload という仕組みを活用できます。本記事では、@overload の基礎から実践的な使い方、さらに IDE(VSCode)での補完効果が実感できる練習問題まで、じっくり解説していきます。


🔰 @overload って何?

Python の @overload は、関数の「複数の使い方(インターフェース)」を型チェッカーに教えるための型ヒント専用デコレータです。

💡 実行時には使われず、mypypyright のような静的型チェッカー用の情報としてのみ意味を持ちます。


✨ 使うと何が嬉しいの?

✅ 型安全になる

複雑な引数パターンでも「どんな型で呼べば正しいか」が IDE 上でわかる!

✅ 補完がリッチになる

複数の呼び方を IDE がちゃんと理解し、補完候補を表示してくれる!

✅ 可読性・保守性の向上

関数の使い方が明示されるので、ドキュメントがなくても理解しやすい!


📘 基本構文

from typing import overload

@overload
def func(arg: int) -> str: ...
@overload
def func(arg: str) -> int: ...

def func(arg):
    # 実装は1つだけ(@overloadなし)
    if isinstance(arg, int):
        return str(arg)
    elif isinstance(arg, str):
        return len(arg)
  • @overload を付けた関数には 実装を書かず...(エリプシス)で定義
  • 実際の実装は 最後に1つだけ 定義

🛠 実用パターンと応用例

1. 単一値 vs リスト

@overload
def square(x: int) -> int: ...
@overload
def square(x: list[int]) -> list[int]: ...

def square(x):
    if isinstance(x, list):
        return [i * i for i in x]
    else:
        return x * x

2. キーの型によって返す型を変える辞書アクセス

@overload
def get_data(key: int) -> int: ...
@overload
def get_data(key: str) -> str: ...

def get_data(key):
    return 100 if isinstance(key, int) else "hello"

3. ファイルパス or ファイルオブジェクト

from typing import IO

@overload
def read_file(file: str) -> str: ...
@overload
def read_file(file: IO[str]) -> str: ...

def read_file(file):
    if isinstance(file, str):
        with open(file, 'r') as f:
            return f.read()
    else:
        return file.read()

🧪 練習:実際に補完の違いを体験しよう!

📁 ステップ 1: shopping.py を作成

# shopping.py
from typing import overload, Dict

Item = Dict[str, int]

@overload
def add_to_cart(cart: Item, product: str, quantity: int) -> Item: ...
@overload
def add_to_cart(cart: Item, pair: Item) -> Item: ...

def add_to_cart(cart, *args):
    if len(args) == 1:
        cart.update(args[0])
    elif len(args) == 2:
        product, qty = args
        cart[product] = cart.get(product, 0) + qty
    return cart

📝 ステップ 2: main.py を作成

from shopping import add_to_cart

cart = {}

# Q1: 補完候補を確認しよう(2パターン出る?)
add_to_cart(

# Q2: "banana", 2 を渡したとき返り値の型は?補完効いてる?
add_to_cart(cart, "banana", 2)

# Q3: {"apple": 3} を渡したとき補完変わった?
add_to_cart(cart, {"apple": 3})

🧪 ステップ 3(任意): mypy でチェック

mypy main.py

→ 引数ミスしたらちゃんとエラー出るはず!


📌 補足:@overload の注意点

  • 実行時には無視される(... の中身は評価されない)
  • 実装は最後に1つだけ書く
  • IDE(VSCode や PyCharm)で効果が大きい
  • 型チェッカーと併用することで真価を発揮!

🎁 まとめ

ポイント 内容
@overload とは? 型チェッカー向けのインターフェース宣言
メリット 補完・型安全・ドキュメント不要な明快さ
使い方 複数の呼び方がある関数に適用、実装は1つにまとめる
IDE効果 VSCodeやPyCharmで補完が劇的に良くなる
実践練習 add_to_cart 関数で体験してみよう!

🧠 最後に

Python は動的型付けで柔軟ですが、だからこそ 補完の助けや静的な安心感 がありがたい場面もあります。@overload を活用すれば、その両方を手に入れられます。

関数に“複数の顔”を持たせたいとき、安全で親切なインターフェースを設計したいとき、ぜひ @overload を使ってみてください!


ご希望に応じて、Markdown版やコードブロック調整済みのものもお渡しできます。
そのままブログに貼ってもらっても大丈夫なように整理済みです😊

user:

ありがとうございます!お疲れ様でした!

assistant:

こちらこそ、ありがとうございました!✨
たくさんの楽しい質問やリクエスト、とっても嬉しかったです😊

また何か書きたくなったり、コードで悩んだり、「補完が効かないのなんで!?」ってモヤっとしたら、いつでも気軽に呼んでくださいね。

お疲れさまでした!ゆっくり休んで、よい1日を🍵☀️

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?