Would Mypy have been of any usage with some typestubs? E.g., that qb was meant to be of Type[SomeQueryBuilderInterface] which shouldn’t have an engine field?
I was thinking the same: Any statically typed language would disallow modifying types at runtime. By definition. But does Mypy care? Let’s try:
class QueryBuilder:
pass
class QueryBuilderMysqlEngine:
pass
def main():
qb = QueryBuilder()
qb.engine = QueryBuilderMysqlEngine()
[ ] mypy: Success: no issues found in 1 source file
[ ] ruff:
[x] pylint | grep -Ev 'docstring|snake_case|Too few public methods': Attribute ‘engine’ defined outside __init__ (attribute-defined-outside-init)
Thankfully, pylint cares, but it also cares about things I find rather normal.
My takeaway from the article: As someone not used to thinking in dynamic types and concurrency in the same world: Oh boy, this is also a thing to check for.
This is because mypy doesn’t check functions that don’t have type annotations on them by default. You can change that via various flags, which I think should be included in the --strict mentioned in a sibling comment.
I was about to ask the same. Maybe some would count casting and polymorphism, but I wouldn’t.
That’s a question of definition, but my answer is that you can’t change types at runtime, because types don’t exist at runtime. Wikipedia is foggy as usual on what static typing precisely is, but I think the meaningful distinction is that the type of each variable is static. That’s what it must mean if C++ is statically typed. In other words, it can’t be relevant that you can violate typing as much as you want at runtime by pointing at or interposing objects of different types, and that the language even helps you do that in safe forms (OOP).
I don’t think this is an accurate representation of the original code. This is constructing a new object of type QueryBuilder. If a new object was being constructed each time then there would not have been an issue. But the code was doing the equivalent of qb = QueryBuilder, not qb = QueryBuilder(). So instead of creating a new object and then mutating that new object, we’re instead mutating the QueryBuilder class itself.
error: "type[QueryBuilder]" has no attribute "engine" [attr-defined]
which is what we want to happen.
You need either the -> None annotation or the --strict command line option to get mypy to kick in here. Otherwise it will treat the entire main function as something that’s not yet meant to have type checking applied.
Probably, but whoever wrote it probably wrote it this way intentionally. I don’t know why, but I am guessing it was because qb.engine was a better developer-facing API than qb_engine separately attached to the local namespace. Alas, better design was to not have this “global state” at all, which we eventually removed.
Would Mypy have been of any usage with some typestubs? E.g., that qb was meant to be of Type[SomeQueryBuilderInterface] which shouldn’t have an engine field?
I was thinking the same: Any statically typed language would disallow modifying types at runtime. By definition. But does Mypy care? Let’s try:
mypy: Success: no issues found in 1 source fileruff:pylint | grep -Ev 'docstring|snake_case|Too few public methods': Attribute ‘engine’ defined outside __init__ (attribute-defined-outside-init)Thankfully, pylint cares, but it also cares about things I find rather normal.
My takeaway from the article: As someone not used to thinking in dynamic types and concurrency in the same world: Oh boy, this is also a thing to check for.
Mypy does have an error code for this: attr-defined
easiest way to get it to show up is passing
--strictto mypyThis is because mypy doesn’t check functions that don’t have type annotations on them by default. You can change that via various flags, which I think should be included in the
--strictmentioned in a sibling comment.That’s just not true, the “by definition” – even Java and C++ allow modifying types at run-time.
How do you modify a type at runtime in C++?
Classes in both Java and C++ support run-time mutable static fields
That only allows you to modify a value at runtime, not modify a type. You can’t add a previously non existing static field to a type at runtime.
Oh, I understand where you are drawing the line now. You mean something like adding new behavior
I was about to ask the same. Maybe some would count casting and polymorphism, but I wouldn’t.
That’s a question of definition, but my answer is that you can’t change types at runtime, because types don’t exist at runtime. Wikipedia is foggy as usual on what static typing precisely is, but I think the meaningful distinction is that the type of each variable is static. That’s what it must mean if C++ is statically typed. In other words, it can’t be relevant that you can violate typing as much as you want at runtime by pointing at or interposing objects of different types, and that the language even helps you do that in safe forms (OOP).
I don’t think this is an accurate representation of the original code. This is constructing a new object of type
QueryBuilder. If a new object was being constructed each time then there would not have been an issue. But the code was doing the equivalent ofqb = QueryBuilder, notqb = QueryBuilder(). So instead of creating a new object and then mutating that new object, we’re instead mutating theQueryBuilderclass itself.If I use this code:
Then mypy complains with:
which is what we want to happen.
You need either the
-> Noneannotation or the--strictcommand line option to get mypy to kick in here. Otherwise it will treat the entiremainfunction as something that’s not yet meant to have type checking applied.Probably, but whoever wrote it probably wrote it this way intentionally. I don’t know why, but I am guessing it was because
qb.enginewas a better developer-facing API thanqb_engineseparately attached to the local namespace. Alas, better design was to not have this “global state” at all, which we eventually removed.