Comment by eloisius
1 year ago
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:
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 = 'save-update, merge', 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 = 'select', passive_deletes: Literal['all'] | 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]
https://docs.sqlalchemy.org/en/20/orm/relationship_api.html#...
> 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.
Maybe try using an IDE? Without one any language's type system will feel more frustrating than it's worth, since you won't get inline error messages either.
> Even strongly typed languages like rust have ergonomics to help you avoid explicitly specifying types like let x = 1.
This is called type inference, and as far as I can tell this level of basic type inference is supported by the major python type checkers. If you're seeing people explicitly annotate types on local variables that's a cultural problem with people who are unaccustomed to using types.
As for that function signature, it would be bonkers with or without types. The types themselves look pretty straightforward, the problem is just that they formatted it all on one line and have a ridiculous number of keyword arguments.
Of course I’ve used an IDE before. I still prefer Vim to an IDE. And I enjoy writing typed languages in Vim because the compiler catches mistakes.
I agree part of the problem is cultural. Maybe a bunch of Python coders are eager to use types, or maybe linters are pushing them to type every last variable because that is “right.” I don’t know.
I don’t hate typed languages at all. In fact I love writing Rust. Even C++ is tolerable from a type perspective. I don’t agree that _RelationshipJoinConditionArgument is a meaningful type. It feels like bolting a type system onto the language after the fact is weird and necessitates crazy types like that to make some linter happy, maybe to make VS Code users happy, at the expense of readability.
Vim is an IDE, with more steps. Nothing stopping you from having code completion setup in vim and benefiting from the additional meta info.
2 replies →
You can run mypy as the Python equivalent of the typey bit of a compiler.
As for SQLAlchemy, I wouldn't assume that the object model would be particularly different in any other OO language for the problem it's solving.
> _RelationshipJoinConditionArgument
Is it particularly different from Rust's unusual types like `Map<Chain<FromRef<Box dyn Vec<Foo>>>>>` that you can get when doing chained operations on iterators?
Protocol/Trait based typing necessitates weird names for in-practice traits/protocols that are used.
Edit: IDK why that function signature is that ridiculous, reformatting it as:
It's 2-3 expected arguments and then ~30 options (that are all like Optional[bool] or Optional[str] to customize the relationship factory. Types like `_ORMColCollectionArgument` do stick out, but they're mainly there because these functions accept `Union[str, ResolvedORMType]` and will convert some sql string to a resolved type for you, and like, this is an ORM, there are going to be some weird ORM types.
It definitely seems to be a target audience issue. I use VS code and MyPy regularly catches mistakes, some of which would have been fairly subtle.
I have MyPy and Ruff going all the time and generally aim for zero linter errors.
Thank you, for putting many of my frustrations to words.
How does Vim prevent you from using a language server?
> Without one any language's type system will feel more frustrating than it's worth, since you won't get inline error messages either.
I disagree, for me the integration with the editor mostly shortens feedback cycles, and enables some more advanced features. The utility of identifying problems without running the code is still there.
> I don’t use an IDE, so code completion or whatever you get for it doesn’t apply to me.
This is a reasonable take if you're a solo developer working without an IDE. Though I suspect you'd still find a few missing None checks with type checking.
If you're working on a team, though, the idea is to put type-checking into your build server, alongside your tests, linting, and whatnot.
> You see extraneous code like x: int = 1 in Python now.
This shouldn't be necessary in most cases; Python type checkers are fine with inferring types.
> Third party libs have bonkers types. This function signature is ridiculous:
It is. Part of that is that core infrastructure libraries tend to have wonky signatures just by their nature. A bigger part, though, is that a lot of APIs in popular Python libraries are poorly designed, in that they're extremely permissive (like pandas APIs allowing dataframes, ndarrays, list of dicts, and whatever else) and use kwargs inappropriately. Type declarations just bring that to the surface.
What's wrong with being extremely permissive? I'd argue that's a strength of the python ecosystem. It's true that very dense api:s are difficult to type, but I wouldn't say they're typically poorly designed because of it.
When you’ve got to pass in something that isn’t permitted and the list of things that is permitted isn’t documented you’ve got to dig 12 levels down into the library across 9 branching paths to figure out what input it actually does support.
Permissive libraries violate the "one and only one obvious way" philosophy.
I suspect they probably confuse AI tools more than restrictive APIs too, and give non-AI auto complete less to go on.
Even without an IDE, I use Mypy like a test suite. It catches real bugs that would be either hard to find in testing, or intrusive and annoying to test for.
That signature is ridiculous in any programming language. Types aren't the problem here.
Was about to say the same thing... that method takes like 20 arguments. Types are the only thing making it usable.
I wouldn't mind all of that if the SQLAlchemy documentation would hide all the types until I mouse over them.
Ditto for vim!
> [...] rust have ergonomics [...]
For starters Rust's official linter, clippy, would also tell you that this function has too many arguments. ;) The default (max) is seven[1].
The above function has 36 named arguments ... That is a code UX wtf with or without type annotations.
[1] https://rust-lang.github.io/rust-clippy/master/index.html#/t...
I've literally never seen anyone put types on trivial variables like that. Maybe your team is just inexperienced with types and/or python?