<story><title>Shape typing in Python</title><url>https://jameshfisher.com/2024/04/12/shape-typing-in-python/</url></story><parent_chain><item><author>jmduke</author><text>Having migrated my application&amp;#x27;s Python and JS codebases to their typed siblings respectively last year, my 2c is that Python typing feels good and worthwhile when you&amp;#x27;re in the standard lib, but _awful_ (and net-negative) once you leave &amp;quot;normal Python&amp;quot; for the shores of third-party packages, particularly ones that lean heavily on duck typing (Django and BeautifulSoup both come to mind.)&lt;p&gt;This is where some of the stuff in the TypeScript ecosystem really shines, IMHO — being able to have a completely typesafe ORM such as Drizzle (&lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;orm.drizzle.team&amp;#x2F;&quot; rel=&quot;nofollow&quot;&gt;https:&amp;#x2F;&amp;#x2F;orm.drizzle.team&amp;#x2F;&lt;/a&gt;) feels like a Rubicon moment, and touching anything else feels like a significant step backwards.</text></item></parent_chain><comment><author>networked</author><text>My experience has been different: last year I started writing Python again after a long break, and I am yet to regret using types pervasively. If some library has no type definitions, I prefer to have my typed code interact with its untyped code. It is still better than having no types at all. You can sometimes get some useful type safety by annotating your functions with the untyped library&amp;#x27;s classes.&lt;p&gt;Since then, I have used established libraries like Beautiful Soup, Jinja, Pillow, platformdirs, psutil, python-dateutil, redis-py, and xmltodict with either official or third-party types. I remember their types being useful to varying degrees and not a problem. I have replaced Requests with the very similar but typed and optionally async HTTPX. My most objectionable experience with types in Python so far has been having to write&lt;p&gt;&lt;pre&gt;&lt;code&gt;    root = cast(
        lxml.etree._Element,  # noqa: SLF001
        html5.parse(html, return_root=True),
    )
&lt;/code&gt;&lt;/pre&gt;
when I used types-lxml with &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;kovidgoyal&amp;#x2F;html5-parser&quot;&gt;https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;kovidgoyal&amp;#x2F;html5-parser&lt;/a&gt;. In return I have been able to catch some bugs early and to &amp;quot;fearlessly&amp;quot;refactor code with few or no unit tests, only integration tests. The style I have arrived at is close to &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;kobzol.github.io&amp;#x2F;rust&amp;#x2F;python&amp;#x2F;2023&amp;#x2F;05&amp;#x2F;20&amp;#x2F;writing-python-like-its-rust.html&quot; rel=&quot;nofollow&quot;&gt;https:&amp;#x2F;&amp;#x2F;kobzol.github.io&amp;#x2F;rust&amp;#x2F;python&amp;#x2F;2023&amp;#x2F;05&amp;#x2F;20&amp;#x2F;writing-pyth...&lt;/a&gt;.&lt;p&gt;Admittedly, I don&amp;#x27;t use Django. Maybe I won&amp;#x27;t like typed Django if I do. My choice of type checker is Pyright in non-strict mode. It seems to usually, though not always, catch more and more subtle type errors than mypy. I understand that for Django, mypy with a Django plugin is preferred.</text></comment>
<story><title>Shape typing in Python</title><url>https://jameshfisher.com/2024/04/12/shape-typing-in-python/</url></story><parent_chain><item><author>jmduke</author><text>Having migrated my application&amp;#x27;s Python and JS codebases to their typed siblings respectively last year, my 2c is that Python typing feels good and worthwhile when you&amp;#x27;re in the standard lib, but _awful_ (and net-negative) once you leave &amp;quot;normal Python&amp;quot; for the shores of third-party packages, particularly ones that lean heavily on duck typing (Django and BeautifulSoup both come to mind.)&lt;p&gt;This is where some of the stuff in the TypeScript ecosystem really shines, IMHO — being able to have a completely typesafe ORM such as Drizzle (&lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;orm.drizzle.team&amp;#x2F;&quot; rel=&quot;nofollow&quot;&gt;https:&amp;#x2F;&amp;#x2F;orm.drizzle.team&amp;#x2F;&lt;/a&gt;) feels like a Rubicon moment, and touching anything else feels like a significant step backwards.</text></item></parent_chain><comment><author>eloisius</author><text>I agree. Prior to the introduction of types in Python, I thought I wanted it. Now I hate them. It feels like a bunch of rigmarole for no benefit. I don’t use an IDE, so code completion or whatever you get for it doesn’t apply to me. Even strongly typed languages like rust have ergonomics to help you avoid explicitly specifying types like let x = 1. You see extraneous code like x: int = 1 in Python now. Third party libs have bonkers types. This function signature is ridiculous:&lt;p&gt;&lt;pre&gt;&lt;code&gt;    sqlalchemy.orm.relationship(argument: _RelationshipArgumentType[Any] | None = None, secondary: _RelationshipSecondaryArgument | None = None, *, uselist: bool | None = None, collection_class: Type[Collection[Any]] | Callable[[], Collection[Any]] | None = None, primaryjoin: _RelationshipJoinConditionArgument | None = None, secondaryjoin: _RelationshipJoinConditionArgument | None = None, back_populates: str | None = None, order_by: _ORMOrderByArgument = False, backref: ORMBackrefArgument | None = None, overlaps: str | None = None, post_update: bool = False, cascade: str = &amp;#x27;save-update, merge&amp;#x27;, viewonly: bool = False, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: _NoArg | _T = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, lazy: _LazyLoadArgumentType = &amp;#x27;select&amp;#x27;, passive_deletes: Literal[&amp;#x27;all&amp;#x27;] | bool = False, passive_updates: bool = True, active_history: bool = False, enable_typechecks: bool = True, foreign_keys: _ORMColCollectionArgument | None = None, remote_side: _ORMColCollectionArgument | None = None, join_depth: int | None = None, comparator_factory: Type[RelationshipProperty.Comparator[Any]] | None = None, single_parent: bool = False, innerjoin: bool = False, distinct_target_key: bool | None = None, load_on_pending: bool = False, query_class: Type[Query[Any]] | None = None, info: _InfoType | None = None, omit_join: Literal[None, False] = None, sync_backref: bool | None = None, **kw: Any) → Relationship[Any]
&lt;/code&gt;&lt;/pre&gt;
&lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;docs.sqlalchemy.org&amp;#x2F;en&amp;#x2F;20&amp;#x2F;orm&amp;#x2F;relationship_api.html#sqlalchemy.orm.relationship&quot; rel=&quot;nofollow&quot;&gt;https:&amp;#x2F;&amp;#x2F;docs.sqlalchemy.org&amp;#x2F;en&amp;#x2F;20&amp;#x2F;orm&amp;#x2F;relationship_api.html#...&lt;/a&gt;</text></comment>