[Python 3.x] 構造の定義と型アノテーションに対応した単純なデータの入れ物

上から順に検討するのが良いかも?

■typing.NamedTuple      [new in 3.6]
https://docs.python.org/ja/3.11/library/typing.html#typing.NamedTuple
 ・名前付きの tuple なので軽い
 ・イミュータブル
 ・デフォルト値可能
 ・アンパックで一括で値を設定する事が可能
 ・多重継承はできない?
 ※自作メソッドを作れるができる限り作らない方が良い
 
 操作メソッド
  _make(iterable), _asdict(), _replace(**kwargs)
   ※アンダースコア付きだけどprotectedではなくpublic扱いという特殊仕様

■dataclasses.dataclass  [new in 3.7]
https://docs.python.org/ja/3.11/library/dataclasses.html#module-dataclasses
 ・__init__の記述を省略できるクラス
 ・イミュータブル化も可能
 ・デフォルト値可能
 ・アンパックで一括で値を設定する事が可能
 ・slots=Trueのクラスからの多重継承はできない?
 ・特殊メソッドの生成の有無を指定できる
 ※自作メソッドを作れるができる限り作らない方が良い
  自作メソッドが必要なら普通のクラスの採用も検討すること

■typing.TypedDict       [new in 3.8]
https://docs.python.org/ja/3.11/library/typing.html#typing.TypedDict
 ・構造が定義できるdict
 ・デフォルト値は不可能
 ※dictを使っている既存プロジェクトで使用?
 ※json関連で使用

[Python 3.10] コマンドライン サブコマンド 雛形

#!/usr/bin/env python
# -*- coding:utf-8 -*-

r"""" コマンドラインプログラムの雛形 最小

各コマンドの実装はargparse.Actionの派生クラスで実装できるが
コード量が多くなるのと単純なもの以外では面倒くさい感じなので
ここでは使用しない。
"""
# from typing import override
from abc import ABC, abstractmethod
from argparse import(
    ArgumentParser,
    _SubParsersAction, # type: ignore
    RawDescriptionHelpFormatter,
    Namespace,
)
import locale
import pathlib
import sys
from typing import Any, TypeAlias, TYPE_CHECKING


__version__ = "0.0.1"


if TYPE_CHECKING:
    SubParserType: TypeAlias = _SubParsersAction[ArgumentParser]

else:
    SubParserType: TypeAlias = Any


class AbstractSubCommand(ABC):
    r"""サブコマンドの抽象基底クラス"""

    @classmethod
    def set_argument(cls, parser_: SubParserType) -> None:
        r"""パーサーに登録"""
        parser = cls._set_argument_impl(parser_)
        parser.set_defaults(func=cls._run)

    @classmethod
    @abstractmethod
    def _set_argument_impl(cls, parser_: SubParserType) -> ArgumentParser:
        r"""パーサーに登録 実装"""
        raise NotImplementedError()

    @classmethod
    def _run(cls, args: Namespace) -> int:
        r"""処理内容"""
        return cls._run_impl(args)

    @classmethod
    @abstractmethod
    def _run_impl(cls, args: Namespace) -> int:
        r"""処理内容 実装"""
        raise NotImplementedError()

class CatCommand(AbstractSubCommand):
    r"""サブコマンド cat """

    # @override
    @classmethod
    def _set_argument_impl(cls, parser_: SubParserType) -> ArgumentParser:
        r"""パーサーに登録 実装"""
        parser = parser_.add_parser("cat", help="ファイルを出力する")
        parser.add_argument("inputfile", help="対象ファイル")
        parser.add_argument("--encoding", default="utf-8")
        return parser

    # @override
    @classmethod
    def _run_impl(cls, args: Namespace) -> int:
        r"""処理内容 実装"""
        path = pathlib.Path(args.inputfile)
        if not path.exists():
            print(f"ファイルが存在しません。'{args.inputfile}'"
                  , file=sys.stderr)
            return 1

        if not path.is_file():
            print(f"ファイルを指定して下さい。'{args.inputfile}'"
                  , file=sys.stderr)
            return 1

        text = path.read_text(encoding=args.encoding)
        print(text)

        return 0


def set_argsparse() -> ArgumentParser:
    r"""パーサーの起動とメインコマンドの登録"""
    description ="""説明 1行目
   2行目
   3行目"""
    argparser = ArgumentParser(
        prog="コマンドプログラムの雛形",
        description=description,
        formatter_class=RawDescriptionHelpFormatter,
        add_help=True,
        allow_abbrev=False)

    argparser.add_argument(
        "-v", "--version",
        action="version",
        version=f"%(prog)s {__version__}")

    argparser.add_argument(
        "--env",
        action="store_true",
        help="実行環境の情報を表示する")

    return argparser

def set_subcommand(parser_: ArgumentParser):
    r"""サブコマンドを登録"""
    commands: list[type[AbstractSubCommand]] = [
        CatCommand,
    ]
    subparser = parser_.add_subparsers(title="sub commands")
    for cmd in commands:
        cmd.set_argument(subparser)

def main_command_env() -> int:
    r"""実行環境の出力"""
    print("{}  {}  {}.{}.{} {} {}".format(
        sys.platform,
        sys.implementation.cache_tag,
        *sys.implementation.version))
    print(f"  sys.getrecursionlimit(): {sys.getrecursionlimit()}")
    print(f"  sys.stdout.encoding: {sys.stdout.encoding}")
    print(f"  sys.getdefaultencoding(): {sys.getdefaultencoding()}")
    print(f"  sys.getfilesystemencoding(): {sys.getfilesystemencoding()}")
    print(f"  locale.getpreferredencoding(): {locale.getpreferredencoding()}")
    print("  sys.path: [\n    {}]".format("\n    ".join(sys.path)))

    return 0

def main_command(args: Namespace) -> tuple[int, bool]:
    r"""
    メインコマンドがあるならここに処理を書く
    """
    ret: int = 0
    is_continue: bool = True

    if args.env:
        ret = main_command_env()
        is_continue = False

    return (ret, is_continue)


def main() -> int:
    r"""メイン処理"""
    argparser = set_argsparse()
    set_subcommand(argparser)

    try:
        args = argparser.parse_args()

    except SystemExit:
        return 1

    ret, is_continue = main_command(args)
    if not is_continue:
        return ret

    if hasattr(args, 'func'):
        return args.func(args)

    argparser.print_help()
    return 0


if __name__ == "__main__":
    sys.exit(main())