{"id":829,"date":"2026-04-30T23:33:36","date_gmt":"2026-04-30T15:33:36","guid":{"rendered":"https:\/\/connectword.dpdns.org\/?p=829"},"modified":"2026-04-30T23:33:36","modified_gmt":"2026-04-30T15:33:36","slug":"a-coding-implementation-on-pyright-type-checking-covering-generics-protocols-strict-mode-type-narrowing-and-modern-python-typing","status":"publish","type":"post","link":"https:\/\/connectword.dpdns.org\/?p=829","title":{"rendered":"A Coding Implementation on Pyright Type Checking Covering Generics, Protocols, Strict Mode, Type Narrowing, and Modern Python Typing"},"content":{"rendered":"<p>In this tutorial, we explore <a href=\"https:\/\/github.com\/microsoft\/pyright?tab=readme-ov-file\"><strong>Pyright<\/strong><\/a>, Microsoft\u2019s high-performance static type checker for Python, and walk through its most powerful features in a hands-on, Colab-friendly format. We start from the ground up with basic annotations and type inference, then progressively advance through Union types, type narrowing, generics, Protocols, TypedDict, dataclasses, and modern typing constructs like Self, TypeAlias, and NewType. We also examine how strict mode raises the bar for type safety across an entire codebase and how pyrightconfig.json provides fine-grained control over diagnostic rules at the project level. Also, we deliberately introduce both correct and intentionally broken code to see exactly how Pyright catches real-world mistakes before they ever reach runtime.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">import subprocess, sys, json, textwrap, os\n\n\nsubprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"pyright\", \"-q\"])\nprint(\"<img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/>  pyright installedn\")\n\n\nWORK = \"\/tmp\/pyright_tutorial\"\nos.makedirs(WORK, exist_ok=True)\n\n\ndef write(filename: str, code: str) -&gt; str:\n   path = os.path.join(WORK, filename)\n   os.makedirs(os.path.dirname(path), exist_ok=True)\n   with open(path, \"w\") as f:\n       f.write(textwrap.dedent(code))\n   return path\n\n\ndef run_pyright(*files, mode=\"basic\", extra_flags=None):\n   args = [sys.executable, \"-m\", \"pyright\",\n           \"--outputjson\",\n           f\"--pythonversion=3.11\",\n           f\"--typeCheckingMode={mode}\",\n           *(extra_flags or []),\n           *[os.path.join(WORK, f) for f in files]]\n   result = subprocess.run(args, capture_output=True, text=True)\n   try:\n       data = json.loads(result.stdout)\n   except json.JSONDecodeError:\n       print(result.stdout or result.stderr)\n       return\n   diags = data.get(\"generalDiagnostics\", [])\n   summary = data.get(\"summary\", {})\n   print(f\"  errors={summary.get('errorCount',0)}  \"\n         f\"warnings={summary.get('warningCount',0)}  \"\n         f\"infos={summary.get('informationCount',0)}\")\n   for d in diags:\n       sev = d[\"severity\"].upper()\n       msg = d[\"message\"]\n       rule = d.get(\"rule\", \"\")\n       line = d[\"range\"][\"start\"][\"line\"] + 1\n       fname = os.path.basename(d[\"file\"])\n       tag = f\"[{rule}]\" if rule else \"\"\n       print(f\"    {sev} {fname}:{line} \u2014 {msg} {tag}\")\n   if not diags:\n       print(\"    (no diagnostics \u2014 all clean <img decoding=\"async\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/72x72\/2705.png\" alt=\"\u2705\" class=\"wp-smiley\" \/>)\")\n   print()\n\n\nprint(\"=\" * 62)\nprint(\"SECTION 1 \u00b7 Basic annotations &amp; inference\")\nprint(\"=\" * 62)\n\n\nwrite(\"s1_basics.py\", \"\"\"\n   def add(x: int, y: int) -&gt; int:\n       return x + y\n\n\n   result: int = add(1, 2)\n\n\n   bad: int = \"hello\"\n   add(1, \"two\")\n   add(1, 2, 3)\n\n\n   def multiply(a: float, b: float):\n       return a * b\n\n\n   x: str = multiply(2.0, 3.0)\n\"\"\")\n\n\nprint(\"\u2192 s1_basics.py (basic mode):\")\nrun_pyright(\"s1_basics.py\")\n\n\nprint(\"=\" * 62)\nprint(\"SECTION 2 \u00b7 Optional \/ Union \/ PEP 604 syntax\")\nprint(\"=\" * 62)\n\n\nwrite(\"s2_optional_union.py\", \"\"\"\n   from typing import Optional, Union\n\n\n   def greet(name: Optional[str] = None) -&gt; str:\n       if name is None:\n           return \"Hello, Guest\"\n       return f\"Hello, {name}\"\n\n\n   greet(\"Alice\")\n   greet(None)\n   greet(42)\n\n\n   def stringify(val: Union[int, float, bool]) -&gt; str:\n       return str(val)\n\n\n   stringify(3.14)\n   stringify(\"x\")\n\n\n   def modern(val: int | str | None) -&gt; str:\n       return \"\" if val is None else str(val)\n\n\n   modern(10)\n   modern([])\n\"\"\")\n\n\nprint(\"\u2192 s2_optional_union.py:\")\nrun_pyright(\"s2_optional_union.py\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We begin by installing Pyright and setting up two helper functions, write and run_pyright, which we reuse throughout subsequent sections to create typed Python files and parse Pyright\u2019s JSON diagnostic output cleanly. We then move on to the basics of type annotations, deliberately passing incorrect argument types and mismatched assignments to observe exactly how Pyright flags each violation. We close this snippet by exploring Optional, Union, and the modern PEP 604 pipe syntax, confirming that Pyright correctly rejects values that fall outside the declared union of accepted types.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">print(\"SECTION 3 \u00b7 Type Narrowing\")\nprint(\"=\" * 62)\n\n\nwrite(\"s3_narrowing.py\", \"\"\"\n   from typing import Union\n   import re\n\n\n   def process(val: Union[int, str]) -&gt; str:\n       if isinstance(val, int):\n           return str(val * 2)\n       return val.upper()\n\n\n   def safe_len(s: str | None) -&gt; int:\n       if s is None:\n           return 0\n       return len(s)\n\n\n   def must_be_str(val: str | int) -&gt; str:\n       assert isinstance(val, str), \"need string\"\n       return val.lower()\n\n\n   from typing import TypeGuard\n\n\n   def is_list_of_str(val: list[object]) -&gt; TypeGuard[list[str]]:\n       return all(isinstance(x, str) for x in val)\n\n\n   def join_if_strings(lst: list[object]) -&gt; str:\n       if is_list_of_str(lst):\n           return \", \".join(lst)\n       return \"\"\n\n\n   from typing import Literal, Never\n\n\n   def assert_never(x: Never) -&gt; Never:\n       raise AssertionError(f\"Unhandled: {x!r}\")\n\n\n   Status = Literal[\"ok\", \"error\", \"pending\"]\n\n\n   def handle(s: Status) -&gt; str:\n       match s:\n           case \"ok\":      return \"all good\"\n           case \"error\":   return \"something failed\"\n           case \"pending\": return \"waiting...\"\n\"\"\")\n\n\nprint(\"\u2192 s3_narrowing.py:\")\nrun_pyright(\"s3_narrowing.py\")\n\n\nprint(\"=\" * 62)\nprint(\"SECTION 4 \u00b7 Generics, TypeVar, ParamSpec\")\nprint(\"=\" * 62)\n\n\nwrite(\"s4_generics.py\", \"\"\"\n   from typing import TypeVar, Generic, Callable, ParamSpec, Concatenate\n   from collections.abc import Iterator\n\n\n   T = TypeVar(\"T\")\n   S = TypeVar(\"S\")\n   P = ParamSpec(\"P\")\n\n\n   def first(lst: list[T]) -&gt; T:\n       return lst[0]\n\n\n   x: int = first([1, 2, 3])\n   y: str = first([\"a\", \"b\"])\n   z: int = first([\"a\", \"b\"])\n\n\n   class Stack(Generic[T]):\n       def __init__(self) -&gt; None:\n           self._items: list[T] = []\n\n\n       def push(self, item: T) -&gt; None:\n           self._items.append(item)\n\n\n       def pop(self) -&gt; T:\n           return self._items.pop()\n\n\n       def peek(self) -&gt; T | None:\n           return self._items[-1] if self._items else None\n\n\n   int_stack: Stack[int] = Stack()\n   int_stack.push(42)\n   int_stack.push(\"oops\")\n\n\n   def logged(fn: Callable[P, T]) -&gt; Callable[P, T]:\n       def wrapper(*args: P.args, **kwargs: P.kwargs) -&gt; T:\n           print(f\"calling {fn.__name__}\")\n           result = fn(*args, **kwargs)\n           print(f\"returned {result!r}\")\n           return result\n       return wrapper\n\n\n   @logged\n   def add(a: int, b: int) -&gt; int:\n       return a + b\n\n\n   add(1, 2)\n   add(\"a\", \"b\")\n\n\n   Num = TypeVar(\"Num\", int, float)\n\n\n   def double(x: Num) -&gt; Num:\n       return x * 2  # type: ignore[operator]\n\n\n   double(3)\n   double(3.14)\n   double(\"hi\")\n\"\"\")\n\n\nprint(\"\u2192 s4_generics.py:\")\nrun_pyright(\"s4_generics.py\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We dedicate this snippet first to type narrowing, one of Pyright\u2019s most impressive capabilities, where we demonstrate how control-flow constructs such as isinstance, assert, TypeGuard, and structural match progressively refine a variable\u2019s type within each branch. We then shift to generics, where we build a reusable Stack[T] class and a first() function to demonstrate how TypeVar enables type-safe code that works across multiple concrete types without sacrificing inference. We also introduce ParamSpec, which allows us to wrap functions with decorators while fully preserving the original callable\u2019s argument and return type signatures.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">print(\"=\" * 62)\nprint(\"SECTION 5 \u00b7 Protocols &amp; Structural Subtyping\")\nprint(\"=\" * 62)\n\n\nwrite(\"s5_protocols.py\", \"\"\"\n   from typing import Protocol, runtime_checkable\n\n\n   @runtime_checkable\n   class Drawable(Protocol):\n       def draw(self) -&gt; str: ...\n       def area(self) -&gt; float: ...\n\n\n   class Circle:\n       def __init__(self, r: float) -&gt; None:\n           self.r = r\n       def draw(self) -&gt; str:\n           return f\"\u25cb  r={self.r}\"\n       def area(self) -&gt; float:\n           return 3.14159 * self.r ** 2\n\n\n   class Rectangle:\n       def __init__(self, w: float, h: float) -&gt; None:\n           self.w, self.h = w, h\n       def draw(self) -&gt; str:\n           return f\"\u25ad  {self.w}\u00d7{self.h}\"\n       def area(self) -&gt; float:\n           return self.w * self.h\n\n\n   def render(shape: Drawable) -&gt; None:\n       print(shape.draw(), f\"area={shape.area():.2f}\")\n\n\n   render(Circle(5.0))\n   render(Rectangle(3.0, 4.0))\n   render(\"not a shape\")\n\n\n   from typing import TypeVar, Generic\n\n\n   T_co = TypeVar(\"T_co\", covariant=True)\n\n\n   class Readable(Protocol[T_co]):\n       def read(self) -&gt; T_co: ...\n\n\n   class FileStream:\n       def read(self) -&gt; bytes:\n           return b\"data\"\n\n\n   def consume(source: Readable[bytes]) -&gt; bytes:\n       return source.read()\n\n\n   consume(FileStream())\n\"\"\")\n\n\nprint(\"\u2192 s5_protocols.py:\")\nrun_pyright(\"s5_protocols.py\")\n\n\nprint(\"=\" * 62)\nprint(\"SECTION 6 \u00b7 TypedDict, dataclasses, NamedTuple\")\nprint(\"=\" * 62)\n\n\nwrite(\"s6_datastructures.py\", \"\"\"\n   from typing import TypedDict, NotRequired, Required\n   from dataclasses import dataclass, field\n   from typing import NamedTuple\n\n\n   class User(TypedDict):\n       id: int\n       name: str\n       email: NotRequired[str]\n\n\n   u1: User = {\"id\": 1, \"name\": \"Alice\"}\n   u2: User = {\"id\": 2, \"name\": \"Bob\", \"email\": \"b@x\"}\n   u3: User = {\"id\": \"bad\", \"name\": \"Eve\"}\n\n\n   def print_user(u: User) -&gt; None:\n       print(u[\"name\"], u.get(\"email\", \"\u2014\"))\n\n\n   @dataclass\n   class Product:\n       sku: str\n       price: float\n       tags: list[str] = field(default_factory=list)\n       discount: float = 0.0\n\n\n       def final_price(self) -&gt; float:\n           return self.price * (1 - self.discount)\n\n\n   p = Product(sku=\"ABC\", price=9.99)\n   p.price = 12.50\n   p.price = \"free\"\n\n\n   class Point(NamedTuple):\n       x: float\n       y: float\n       label: str = \"\"\n\n\n   pt = Point(1.0, 2.0)\n   dist: float = (pt.x ** 2 + pt.y ** 2) ** 0.5\n   pt.x = 5.0\n\"\"\")\n\n\nprint(\"\u2192 s6_datastructures.py:\")\nrun_pyright(\"s6_datastructures.py\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We explore Protocols in this snippet, which provide us with structural subtyping: we can define an interface like Drawable and have Pyright accept any class that implements the required methods, without that class ever explicitly inheriting from the Protocol. We extend this further with a generic Readable[T] Protocol that combines structural checking with type parameters, showing how the two features compose naturally. We then turn to data structures, using TypedDict with NotRequired keys, @dataclass with typed fields, and NamedTuple to demonstrate how Pyright enforces type correctness across Python\u2019s three most common structured-data patterns.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">print(\"=\" * 62)\nprint(\"SECTION 7 \u00b7 Literal, Final, @overload\")\nprint(\"=\" * 62)\n\n\nwrite(\"s7_literal_final_overload.py\", \"\"\"\n   from typing import Literal, Final, overload\n\n\n   Direction = Literal[\"north\", \"south\", \"east\", \"west\"]\n\n\n   def move(d: Direction, steps: int = 1) -&gt; str:\n       return f\"Move {d} by {steps}\"\n\n\n   move(\"north\")\n   move(\"up\")\n\n\n   MAX_RETRIES: Final = 3\n   MAX_RETRIES = 5\n\n\n   class Config:\n       DEBUG: Final[bool] = False\n       VERSION: Final = \"1.0.0\"\n\n\n   Config.DEBUG = True\n\n\n   @overload\n   def parse(value: str) -&gt; int: ...\n   @overload\n   def parse(value: bytes) -&gt; str: ...\n   @overload\n   def parse(value: int) -&gt; float: ...\n\n\n   def parse(value: str | bytes | int) -&gt; int | str | float:\n       if isinstance(value, str):\n           return int(value)\n       if isinstance(value, bytes):\n           return value.decode()\n       return float(value)\n\n\n   a: int   = parse(\"42\")\n   b: str   = parse(b\"hi\")\n   c: float = parse(99)\n   d: int   = parse(99)\n\"\"\")\n\n\nprint(\"\u2192 s7_literal_final_overload.py:\")\nrun_pyright(\"s7_literal_final_overload.py\")\n\n\nprint(\"=\" * 62)\nprint(\"SECTION 8 \u00b7 Strict mode\")\nprint(\"=\" * 62)\n\n\nwrite(\"s8_strict.py\", \"\"\"\n   def no_annotation(x, y):\n       return x + y\n\n\n   class Bare:\n       value = None\n\n\n   def partial(x: int, y):\n       return x\n\"\"\")\n\n\nprint(\"\u2192 s8_strict.py (strict mode):\")\nrun_pyright(\"s8_strict.py\", mode=\"strict\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We use Literal types here to restrict a function\u2019s argument to a fixed set of string values, and we pair that with Final to show how Pyright prevents reassignment of constants at both the module and class level. We introduce @overload to define multiple distinct call signatures for a single function, allowing Pyright to resolve the correct return type based on the exact argument type the caller provides. We then switch to strict mode and run a deliberately under-annotated file through Pyright, which reveals just how many additional rules, missing return types, untyped parameters, and implicit Any strict mode activates compared to the default.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">print(\"=\" * 62)\nprint(\"SECTION 9 \u00b7 pyrightconfig.json\")\nprint(\"=\" * 62)\n\n\nconfig = {\n   \"include\": [\"src\"],\n   \"exclude\": [\"**\/__pycache__\"],\n   \"pythonVersion\": \"3.11\",\n   \"typeCheckingMode\": \"strict\",\n   \"reportMissingImports\": \"error\",\n   \"reportMissingTypeStubs\": \"warning\",\n   \"reportUnknownVariableType\": \"warning\",\n   \"reportUnknownMemberType\": \"warning\",\n   \"reportUnnecessaryTypeIgnoreComment\": \"warning\",\n}\ncfg_path = os.path.join(WORK, \"pyrightconfig.json\")\nwith open(cfg_path, \"w\") as f:\n   json.dump(config, f, indent=2)\nprint(f\"Written: {cfg_path}\")\nprint(json.dumps(config, indent=2))\nprint()\n\n\nprint(\"=\" * 62)\nprint(\"SECTION 10 \u00b7 Self, TypeAlias, NewType\")\nprint(\"=\" * 62)\n\n\nwrite(\"s10_modern_types.py\", \"\"\"\n   from typing import Self, TypeAlias, NewType\n\n\n   class Query:\n       def __init__(self) -&gt; None:\n           self._filters: list[str] = []\n\n\n       def where(self, cond: str) -&gt; Self:\n           self._filters.append(cond)\n           return self\n\n\n       def build(self) -&gt; str:\n           return \" AND \".join(self._filters)\n\n\n   class AdvancedQuery(Query):\n       def order_by(self, col: str) -&gt; Self:\n           return self\n\n\n   q = AdvancedQuery().where(\"age &gt; 18\").order_by(\"name\")\n   reveal_type(q)\n\n\n   Vector: TypeAlias = list[float]\n   Matrix: TypeAlias = list[Vector]\n\n\n   def dot(a: Vector, b: Vector) -&gt; float:\n       return sum(x * y for x, y in zip(a, b))\n\n\n   v1: Vector = [1.0, 2.0, 3.0]\n   v2: Vector = [4.0, 5.0, 6.0]\n   dot(v1, v2)\n   dot(v1, [1, 2, 3])\n\n\n   UserId   = NewType(\"UserId\", int)\n   OrderId  = NewType(\"OrderId\", int)\n\n\n   def get_user(uid: UserId) -&gt; str:\n       return f\"user_{uid}\"\n\n\n   uid = UserId(42)\n   oid = OrderId(99)\n\n\n   get_user(uid)\n   get_user(oid)\n   get_user(42)\n\"\"\")\n\n\nprint(\"\u2192 s10_modern_types.py:\")\nrun_pyright(\"s10_modern_types.py\")\n\n\nprint(\"=\" * 62)\nprint(\"SECTION 11 \u00b7 reveal_type() &amp; type: ignore\")\nprint(\"=\" * 62)\n\n\nwrite(\"s11_reveal_ignore.py\", \"\"\"\n   from typing import Any\n\n\n   values = [1, \"two\", 3.0]\n   reveal_type(values)\n\n\n   def mystery(x: Any) -&gt; Any:\n       return x\n\n\n   r = mystery(42)\n   reveal_type(r)\n\n\n   bad: int = \"oops\"\n   bad2: int = \"also bad\"  # type: ignore[assignment]\n\"\"\")\n\n\nprint(\"\u2192 s11_reveal_ignore.py:\")\nrun_pyright(\"s11_reveal_ignore.py\")\n\n\nprint(\"=\" * 62)\nprint(\"TUTORIAL COMPLETE\")\nprint(\"=\" * 62)\nprint(\"\"\"\nTopics covered\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n1  Basic annotations &amp; inference\n2  Optional \/ Union \/ PEP 604 syntax\n3  Type narrowing (isinstance, guards, TypeGuard, match)\n4  Generics \u2014 TypeVar, Generic, ParamSpec\n5  Protocols &amp; structural subtyping\n6  TypedDict, dataclasses, NamedTuple\n7  Literal, Final, @overload\n8  Strict mode\n9  pyrightconfig.json\n10  Self, TypeAlias, NewType\n11  reveal_type() &amp; type: ignore\n\n\nAll source files written to: \/tmp\/pyright_tutorial\/\n\"\"\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We write a pyrightconfig.json file in this snippet to show how we configure Pyright at the project level, enabling strict mode globally and tuning individual diagnostic rules, such as reportMissingImports and reportUnknownMemberType, to either error or warning severity. We then work through three modern typing constructs: Self for fluent builder APIs that return the correct subclass type, TypeAlias for readable type-level naming, and NewType for creating nominally distinct types that Pyright refuses to mix even when their underlying representation is identical.\u00a0<\/p>\n<p>In conclusion, we have covered eleven distinct areas of Pyright\u2019s type system and come away with a clear picture of just how much safety and clarity static typing brings to Python codebases of any size. We have seen that Pyright goes well beyond simple annotation checking, it narrows types through control flow, enforces structural contracts through Protocols, preserves callable signatures through ParamSpec, and locks down constants through Final. We also maintained a practical workflow: we write typed code, run Pyright in basic or strict mode, interpret its JSON diagnostic output, and tune behavior via pyrightconfig.json.<\/p>\n<hr class=\"wp-block-separator aligncenter has-alpha-channel-opacity is-style-wide\" \/>\n<p>Check out\u00a0the\u00a0<strong><a href=\"https:\/\/github.com\/Marktechpost\/AI-Agents-Projects-Tutorials\/blob\/main\/Scientific%20Computing\/pyright_advanced_type_checking_tutorial_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">Full Codes with Notebook here<\/a><\/strong>.<strong>\u00a0<\/strong>Also,\u00a0feel free to follow us on\u00a0<strong><a href=\"https:\/\/x.com\/intent\/follow?screen_name=marktechpost\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Twitter<\/mark><\/a><\/strong>\u00a0and don\u2019t forget to join our\u00a0<strong><a href=\"https:\/\/www.reddit.com\/r\/machinelearningnews\/\" target=\"_blank\" rel=\"noreferrer noopener\">130k+ ML SubReddit<\/a><\/strong>\u00a0and Subscribe to\u00a0<strong><a href=\"https:\/\/www.aidevsignals.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">our Newsletter<\/a><\/strong>. Wait! are you on telegram?\u00a0<strong><a href=\"https:\/\/t.me\/machinelearningresearchnews\" target=\"_blank\" rel=\"noreferrer noopener\">now you can join us on telegram as well.<\/a><\/strong><\/p>\n<p>Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.?\u00a0<strong><a href=\"https:\/\/forms.gle\/MTNLpmJtsFA3VRVd9\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Connect with us<\/mark><\/a><\/strong><\/p>\n<p>The post <a href=\"https:\/\/www.marktechpost.com\/2026\/04\/30\/a-coding-implementation-on-pyright-type-checking-covering-generics-protocols-strict-mode-type-narrowing-and-modern-python-typing\/\">A Coding Implementation on Pyright Type Checking Covering Generics, Protocols, Strict Mode, Type Narrowing, and Modern Python Typing<\/a> appeared first on <a href=\"https:\/\/www.marktechpost.com\/\">MarkTechPost<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this tutorial, we explore P&hellip;<\/p>\n","protected":false},"author":1,"featured_media":29,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-829","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/posts\/829","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=829"}],"version-history":[{"count":0,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/posts\/829\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/media\/29"}],"wp:attachment":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=829"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=829"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=829"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}