Hacker News new | past | comments | ask | show | jobs | submit login

Why wasn't done in the first place? What was the rationale of using typing.List? Did they want to reserve `dict[x]` for some other semantics?



One reason was so the type system could be backported. You can install the typing module all the way back to Python 2.7, where list[int] will never work but nothing prohibits typing.List[int].

The system has been rolled out slowly in general, without hasty changes to the core language.

There's a similar change coming in 3.10: https://docs.python.org/3.10/whatsnew/3.10.html#pep604-new-t...


I like adding | as an alias for Union, and the PEP has lots of examples of where typing.Union gets gnarly really quick, but that doesn't seem like a great example in the changelog:

    def square(number: int | float) -> int | float:
        return number ** 2
A TypeVar constrained to int and float, T = TypeVar('T', int, float), should be used instead to indicate that the return type is the same as the argument type, no?


This example demonstrates why I don't understand Python's type annotations. They seem to be incompatible with the language's core principle of duck typing.

Why would you constrain a function like `square` to only accept `int`s and `float`s? What if I want to square, say, a `fractions.Fraction`?


Interface typing > duck typing. With any sort of code completion, it's 1000x easier and more productive to figure out what goes where when the computer has some idea of the types of a given varaible, and hence what operations you can do to it.

It's like duck typing with a built-in Audubon society taxonomy chart.

You can always do whatever you want at runtime, it's still python.

IMHO, duck typing is strictly inferior to interface typing. You'd define a `Number` interface which `int`, `float`, ect all implement.


> Interface typing > duck typing.

Duck typing has been fundamental to Python for a long time. Do you think we're seeing a shift away from it? In the future, do you think "Pythonic" code will include significant use of explicit interfaces?


> Duck typing has been fundamental to Python for a long time. Do you think we're seeing a shift away from it?

We have been since at least the introduction of abstract base classes; even in purely dynamic python, having the ability to more explicitly declare and interrogate intent than pure duck typing is frequently useful.

> do you think "Pythonic" code will include significant use of explicit interfaces?

Sure, as it already does via abcs. But it will also still use lots of duck typing, though over time more of it will be “static duck typing” by way of protocols.

https://mypy.readthedocs.io/en/stable/protocols.html


Are "protocols" the python term for what Go (and I guess java but I'm way rusty at java) calls "interfaces"? eg the set of methods exposed by an object.


Yes.


Yes but if you want Interface Typing use Java.

But of course give the slightest whiff of types and the type police will come and want to shove types down your throat all the way


The "proper" way to annotate this function would be to use the numbers abstract base classes.

https://docs.python.org/3/library/numbers.html


But I don't think this will work on numpy matrix.


Should it?

  >>> import numpy as np
  >>> np.array("asdgh")**2
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: ufunc 'square' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''


Matrix, not array.

    >>> import numpy as np
    >>> np.mat(((1,0),(0,1)))**2
    matrix([[1, 0],
            [0, 1]])


That's a particularly confusing example to choose, because matrix squaring produces the same result as array (element-wise) squaring:

  >>> np.array([[1, 0], [0, 1]])**2
  array([[1, 0],
         [0, 1]])
Here:

  >>> np.array([[1, 2], [3, 4]])**2
  array([[ 1,  4],
         [ 9, 16]])
  >>> np.mat([[1, 2], [3, 4]])**2
  matrix([[ 7, 10],
          [15, 22]])
The real problem with the

  np.array("asdgh")**2
example is squaring doesn't make sense for character data.

Matrix vs. array squaring has other issues too, as matrix squaring only works with square matrices:

  >>> np.mat([1, 2])**2
  [...]
  LinAlgError: Last 2 dimensions of the array must be square
  >>> np.array([1, 2])**2
  array([1, 4])


There is a numbers.Number for that - but yeah, that's another way in which it's just not a good example..

I think typing_extensions.Protocol helps support the general duck typing "any object that has method x", though there are some implementation issues w/ dataclasses and such..


> There is a numbers.Number for that

That won't help if I want to square a matrix, or a polynomial.

> typing_extensions.Protocol helps support the general duck typing "any object that has method x"

Even if there's a `powerable` protocol that could be used to annotate `square` and `cube`, how would you annotate a slightly more complex function like `poly`?

    def square(x): return x**2
    
    def cube(x): return x**3

    def poly(x): return 3 * x**2 + 2 * x


It wouldn’t be a “squareable”

What’s you want is something like “commutative multiplication”


While duck typing solves this problem on paper, how is a user to find out whether square really accepts a matrix?

From comments? They are again limited by the authors imagination. From reading the source?


> how is a user to find out whether square really accepts a matrix?

How do you find out that a function exists, what it accepts, and what it does normally?

> From comments? They are again limited by the authors imagination.

Sure, but an author which much experience with duck typing will typically write documentation that specified that expected methods (informal structural “types”) rather than nominal types.

With the static equivalent (protocols), it’s conceivable that this is also discoverable via tooling.


Using maths to either oppose or support generic duck typing is just pointless, because it's easy to come up with counterexamples where a mathematical operation would or would not behave as expected.

In quite a few cases where one would overload a mathematical operation to, say, sum two objects of certain type, it'd be better to explicitly type out the operation as a functor/lambda.


Yes, read the comments or the source code. Or the documentation. Or `help(square)`. Or just try `square(Matrix([1, 2], [3, 4]))` in the REPL.

How would you use type annotations to show that `square` accepts a matrix (as well as an int, float, rational number etc.)?


> How would you use type annotations to show that `square` accepts a matrix (as well as an int, float, rational number etc.)?

Semi-seriously: by specifying the input type is a Monoid.


Maybe something like `numpy.typing.ArrayLike|numbers.Number`. Not incredibly precise. But if this covers also pandas.Series (not entirely sure), then I think it would cover 99.9% of my practical needs. numpy arrays are effectively the standard library datatype for matrices in Python land.


Type annotations are just hints and aren't enforced at runtime. You can pass whatever you want into that function, and you can also create a union type with the Fraction if you want the annotations to match your application.


> You can pass whatever you want into that function

Not really. Doing so won't cause an error at runtime, but it will produce an error when you run the type checker.


You seem to have removed the previous sentence which qualified the statement as "at runtime."


How can I pass whatever I want into that function at runtime, while still satisfying the type checker?


> How can I pass whatever I want into that function at runtime, while still satisfying the type checker?

Tell the type-checker to buzz-off by any number of means, including # type: ignore


You can use the Any type, or you can not annotate at all, though I'm not sure if the latter is something that passes some stricter modes of e.g. mypy.


Type hints don't help performance either because the interpreter discards them. The hints are not enforced by CPython, so you now need a build tool instead of just running the script. The whole point of PEP 484 seems to be to enable autocomplete in Visual Studio Code.


> The whole point of PEP 484 seems to be to enable autocomplete in Visual Studio Code.

It's to enable tooling, but not just autocomplete. The main purpose of typing is preventing type-related bugs, though making editor autocompletion and other editor information more robust is a not-insignificant additional benefit.

PEP 484 (created 29-Sep-2014, accepted 22-May-2015) wasn't motivated by VS Code (announced April 29, 2015).


PyCharm understands type hints for years now. Without using mypy btw. VSCode should be able to that, there should be one or more (either finished or half baked) plugins for it.

Edit: Microsoft Python Language Server has it (i read)


> Type hints don't help performance either because the interpreter discards them.

With CPython they don't. Cython actually uses annotations to optimize the generated code.


That's literally a different language though, but I get your point. CPython is the worst implementation.


It has nothing to do with any particular editor, it is for autocomplete and build time type checking in any editor and type checking in people's build chains.


Your code should get type checked whenever you run tests and can help reveal subtle bugs (for example, allowing Optional but not handling None in all code paths)


Another great value of running mypy on your code is that you ensure it can be compiled as a whole!


Define a protocol where __pow__ returns the type of self, and use that for the argument and return type of this function.


The word you are looking for is structural typing. (as opposed to nominal typing)

It's essentially type-safe duck typing. In the case of square, you say i want x and y to support the multiply-operator, but i don't care what type they are otherwise.

Available since 3.8: https://www.python.org/dev/peps/pep-0544/


You need an interface to code something. Otherwise how can your square function work?


I was just thinking that squaring an imaginary number results in a real number - that's a pretty crazy thing for a type system to handle, no? I guess that means the correct annotations would be more along the lines of:

    def square(number: numbers.Complex) -> numbers.Real: ...

    T = typing.TypeVar('T', numbers.Real)
    def square(number: T) -> T:
        return number ** 2
What a rabbit hole!


> squaring an imaginary number results in a real number

However, in general, squaring a complex number results in another complex number.


> There's a similar change coming in 3.10

Wow, this is pretty exciting. Hope we also see match being accepted in Python 3.10, so this would make (for me at least) one of the best Python releases in years.


>You can install the typing module all the way back to Python 2.7, where list[int] will never work but nothing prohibits typing.List[int].

Why though? Even in 2.7, isn't the : type and -> type part just taken as annotation (that is, could just be ignored and not clash because it's a valid type)? Or was it some parser shortcoming?


I'm confused, type annotations is not supported in Py 2 anyways, and if you're gonna use type comments a la `# type: list[int]` then it doesn't matter really, does it?


Simple type annotations can be entirely in comments, but for more complex usage you might want to execute code, like type aliases:

  MyContainer = List[int]
On top of that, the typing module was also backported to Python 3 versions before 3.5, where you do have annotation syntax.


> One reason was so the type system could be backported. You can install the typing module all the way back to Python 2.7, where list[int] will never work but nothing prohibits typing.List[int]. The system has been rolled out slowly in general, without hasty changes to the core language.

If only they'd had that idea a bit earlier in the Python 3 life cycle...


Any word on if strong typing enforcement will ever happen? I’m sure it’ll only ever be optional but it’s the single biggest wart in the language imo.


Why? Just use mypy and disable Any types. So, it's not longer optional. Then make it so that if mypy fails, your code doesn't run.

    test-all:
       mypy main.py
       pytest test/main.py


This relies on your third party library type annotations being accurate.


Or existing in the first place. Or being fundamentally possible to implement in the first place (I say, glaring at boto3).


mypy-boto3 works fantastically: https://mypy-boto3.readthedocs.io/en/latest/


Huh, interesting:

> Auto discovery of types for boto3.client and boto3.session calls

I'm curious how this is possible. I didn't realize mypy could inspect function arguments and use them to determine the returned type.


typing.Literal makes a lot possible as of 3.8: https://docs.python.org/3/library/typing.html#typing.Literal


At one point there was exactly zero support for python3 by third party libraries, it took >10 years but we got there.

I see it as ‘use strict’ in JS land


That's true of any type system.


No it isn't. In a static type system, you're not relying on annotations because the types are all known statically. But in a dynamic type system, you can have regions of untyped code where the best your third party static analyzer can do is slap "Any" on them and you watch as the precision of your type checking erodes to nothing.


I don't know if mypy allows you to mock the API but at least flow does for javascript. If a library is untyped you get a file with all the exported functions and you can quickly set the types for the functions that you interface with. We have been using this for a couple of years and it's a godsend. There's a community project to share these external library definitions https://github.com/flow-typed/flow-typed


That's a neat feature!


You can do the same in mypy by generating a stub file: https://mypy.readthedocs.io/en/stable/stubs.html


!

I had no idea. Super cool — thanks for letting me know!


What is

`public static void main(string args)` but an "annotation"?

You can stick "untyped" code in any language. You just have to annotate things with `Object` and cast them.


An annotation is when you write some information and it doesn't directly impact the evaluation of that which it annotates. It's a "note", essentially. Type annotations in Python do not actually affect any aspect of runtime and are ignored during evaluation. This is why you need a third-party tool to look at them. You can actually stick any arbitrary code you like after that colon — it's all ignored just the same.

What you're referring to are called type ascriptions, and they are often seen in what are sometimes called manifest type systems, where you must explicitly write out your types within a static type system. C and Java are common examples of this. These are not annotations because they have real meaning (either used for declaration or for type conversion).

Additionally, there are plenty of statically typed languages which do not require ascription. These languages feature type inference, which is a process that allows the programmer to leave the deduction of the actual types to the compiler. Haskell, Swift, OCaml, Scala, and I think now C++ to some degree (with `auto`) allow for this (to name but a few).


> Type annotations in Python do not actually affect any aspect of runtime and are ignored during evaluation.

This is equally true in Java. Types are erased at runtime. They don't exist in the bytecode.

Just because the type checking and evaluation are done using one tool, "javac" doesn't really mean anything.

Again, what's the difference between a bash script that runs mypy prior to invoking python and a bash script that compiles and runs a java program, from the perspective of type safety, assuming you know nothing about the two programs?

There is no difference. Neither provides any more guarantees than the other. Put another way, by enforcing use of mypy prior to invocation, you convert the annotation to an ascription.

A deeper analysis would suggest that "ascriptions" are actually the things you use to disambiguate when type inference is not powerful enough (https://docs.scala-lang.org/style/types.html).

Scala, for example, calls its type declarations "annotations", except when they are needed to disambiguate, when they are "ascriptions". So under the scala definition, python's annotations are exactly the same as scalas as long as they are used for type checking, which, if you use mypy, they are.


> This is equally true in Java. Types are erased at runtime. They don't exist in the bytecode.

For the record this isn't true, as anyone who's seen a ClassCastException knows. A subset of type information is erased under limited circumstances, but a lot of type information is still present in the bytecode.

> Just because the type checking and evaluation are done using one tool, "javac" doesn't really mean anything.

What means something is that the types are part of the language specification and, even more importantly, part of the consensus of the language community.

In particular, if you depend on a Java library, you can be extremely confident that the type information of that Java library is actually correct, because you can be extremely confident that the library was built in a way that involved checking the types. This is simply not the case for Python; it's very normal to build Python without running mypy. It's like arguing that C is a safe language because you can run Valgrind on your C code: yes, that external tool exists, but it's not part of the language by default, and so you can't rely on the rest of the ecosystem to have used it.

> A deeper analysis would suggest that "ascriptions" are actually the things you use to disambiguate when type inference is not powerful enough (https://docs.scala-lang.org/style/types.html).

> Scala, for example, calls its type declarations "annotations", except when they are needed to disambiguate, when they are "ascriptions". So under the scala definition, python's annotations are exactly the same as scalas as long as they are used for type checking, which, if you use mypy, they are.

The distinction is a lot more general: type ascriptions change the behaviour of the code while type annotations do not. So a system that only has annotations and not ascriptions is significantly more limited than a full type system that has ascriptions as well, at least in practice - in theory we can do perfect type inference (e.g. H-M), but that ignores some edge cases that are important for real-world programming e.g. handling literals in the source code.


> part of the consensus of the language community.

Indeed, so the only difference is that you trust one languages annotations more than the others. Which is what I said.

> The distinction is a lot more general: type ascriptions change the behaviour of the code while type annotations do not.

Well no, ascriptions don't change behavior, they remove ambiguity when systems aren't able to infer things on their own. Literals aren't the issue for H-M. They're relatively easy. H-M has trouble with inheritance and heavily polymorphic code.


> Indeed, so the only difference is that you trust one languages annotations more than the others. Which is what I said.

Eh, that's like saying there's no difference between breaking the law and not breaking the law, someone might kidnap you and lock you up either way. It's true in a sense, but it's more misleading than helpful.

> Well no, ascriptions don't change behavior, they remove ambiguity when systems aren't able to infer things on their own.

You're trying to gloss over everything as "removing ambiguity", but by that logic any change to a program is just "removing ambiguity". In general there are other possible ascriptions for a given term that would form valid programs, and in general those programs could have arbitrarily different behaviour from the program in question.


> You're trying to gloss over everything as "removing ambiguity", but by that logic any change to a program is just "removing ambiguity". In general there are other possible ascriptions for a given term that would form valid programs, and in general those programs could have arbitrarily different behaviour from the program in question.

I'm not, for what it's worth. There are cases where you could argue that type annotations are just removing "ambiguity" (the type system is successfully inferring, you place an annotation that agrees with the inference, or a stricter annotation that still checks successfully). I can see how this is an ambiguity, but it isn't what I meant.

But scala's definition of ascriptions are for places where the type system can't figure something out, and needs extra information to successfully check. More powerful type inference might address these issues (most common languages don't use H-M). Ambiguity was perhaps the wrong word to use. "aren't able to infer things on their own" is maybe the more important bit.

Type ascriptions don't "change" behavior, because there wasn't valid behavior without them. The type system failed to check (but again, different inference algorithms might be able to get around this)


> Type ascriptions don't "change" behavior, because there wasn't valid behavior without them. The type system failed to check (but again, different inference algorithms might be able to get around this)

No, they exist to change behaviour, that's the whole point of that terminology. The example they give is passing a sequence to a vararg method: if you have a generic method that takes a vararg (generic) parameter, then calling that method with multiple parameters from a sequence, or a single parameter that is a sequence, are both valid but have different behaviours.


I totally agree with everything you've said in this thread. The annotation/ascription distinction comes down to whether the use of the thing changes anything in the result. (Just adding a voice of support in this discussion, for what little it's worth.)


> This is equally true in Java. Types are erased at runtime. They don't exist in the bytecode.

I work with bytecode on a daily basis (I develop a commercial Java obfuscator) and this is untrue. You may be thinking of generic type parameter erasure, but even then special interface methods are generated by the compiler so that no lowering to java/lang/Object happens too soon.

In non-generic code, the types remain and are strongly enforced. Methods have descriptors that restrict parameters to certain types (you can get a VerifyError upon loading the class if you've generated code with a type confusion), there is a 'checkcast' instruction to ensure that a stack values is a certain type, and a ClassCastException is thrown if a cast is impossible.


Alright, we've got two different discussions going on here. I'll address them separately.

---

As far as "annotation" vs "ascription", you've actually just reinforced what I said.

> This is equally true in Java. Types are erased at runtime. They don't exist in the bytecode.

Types are erased at compile time, which means that the type-checking analysis has completed. Many compilers erase types after doing type checking to improve runtime performance. That doesn't mean the types don't do anything. They're used for guiding the type-checking by the compiler.

This is in contrast to Python, which does literally nothing with the stuff you throw in the annotations. MyPy doesn't play a role in this specific aspect of the discussion because we're talking about language implementations, and in Python the type annotations are just artifacts in the code that get ignored during compilation and evaluation.

This is the distinction between ascriptions and annotations, which you actually corroborated here:

> Scala, for example, calls its type declarations "annotations", except when they are needed to disambiguate, when they are "ascriptions".

All of the type stuff in Python is handled via annotations because the language proper does not make use of them whatsoever. They're just decoration, really.

In contrast, Java uses all the types to perform type-checking, making them ascriptions.

In languages with lots of type inference, like Scala, you can supply type information. Sometimes these are ascriptions (which either help the inference algorithm or are used for casting), and sometimes they are just artifacts to help the programmer (in which case they can be regarded as annotations, which I didn't address previously).

But the key component in all of this is that we are only looking at the languages proper, not the suite of third-party tools available to those languages, because this is what dictates the correct terminology. In Python the language, types are just annotations which are completely ignored, and this is not the case in statically typed languages.

---

Which brings us to the second point: your original argument.

This thread started with somebody asking whether static typing will ever be implemented in Python proper, to which somebody said there's no reason for that when we can use MyPy and disable support for `Any` types. The response to this was that it would require all third-party libraries to be fully and correctly annotated, where you came in and claimed "that's true in any type system."

The reason I disagreed with this is because in statically typed languages you're not relying on annotations for anything. Keep in mind that we've established that annotations are specifically artifacts in the code that the language itself doesn't care about, because when the language does care they are called ascriptions. Annotations are supplied by the programmer by desire, not by necessity of the language proper.

You can release a fully-functioning Python library with absolutely zero type annotations, and it will run exactly the same as if you annotated everything. This is because type annotations don't do anything in Python; to gain any use from them requires using third-party tools, like MyPy.

But you cannot release a Java library that doesn't have all its types specified in some manner. Because the Java language requires the types to be present, meaning they aren't annotations like they are in Python.

I guess really what I'm trying to get at is that your response where you said "That's true of any type system" was completely missing the point. The point being made by the person you responded to was about how in a Python ecosystem, simply using MyPy and disabling the `Any` type requires all third-party libraries to be fully and correctly annotated — something which is not usually required in Python, so it's an extra burden on the libraries' developers. This is entirely distinct behavior from any statically typed language because in those languages, you can't just leave out the types and call it a day.


> In contrast, Java uses all the types to perform type-checking, making them ascriptions.

Javac uses them to perform type checking, prior to runtime. Exactly the same way that mypy uses them to perform type checking prior to runtime.

> The reason I disagreed with this is because in statically typed languages you're not relying on annotations for anything. Keep in mind that we've established that annotations are specifically artifacts in the code that the language itself doesn't care about, because when the language does care they are called ascriptions. Annotations are supplied by the programmer by desire, not by necessity of the language proper.

But Scala's annotations are used for type checking. Much as python's are if you enable mypy.

> But you cannot release a Java library that doesn't have all its types specified in some manner. Because the Java language requires the types to be present, meaning they aren't annotations like they are in Python.

Of course you can. You can write a functional java library that specifies that all functions take `Object`. Your argument comes down to that syntactically, java requires you stick something in the place-where-type-declarations-go, while python does not.

This ultimately doesn't have any impact on the strictness of the type checking done, because you can stick useless annotations (Any, Object) in the syntactic spot in either case. The semantics are the same.

Ultimately, your argument comes down to "I trust that more third party libraries will have useful types in Java than in python", but that isn't implicit to the type system, it's because the type ecosystem has been around longer.

Which, like, sure. But to say that python doesn't have static typing is silly. Nearly all the python I write is statically typed.

> In Python the language, types are just annotations which are completely ignored, and this is not the case in statically typed languages.

This is a uselessly semantic argument. Much as if you choose to not typecheck your python, you can choose to write essentially untyped java using Object and casts. Are you really certain that no library you depend on does that, or uses reflection to access dynamic and non-statically-verifiable features?

The safety you get from any static analysis tool, whether a type system or a linter or whatever, relies on your dependencies not trying to subvert the tool (or doing so correctly).

This is essentially the argument about rust's Unsafe. You, or any of your dependencies, is free to subvert the borrow checker and do "unsafe" things.

You either have to trust that they are doing so responsibly, or manually verify the correctness of their unsafe code. Having to opt-in to unsafe access is, indeed, an advantage since it makes static analysis of how much "unsafety" there is easy (much as it's easier to find non-static things in vanilla Java than in python), but you can (and some do!) use static analysis to enforce type annotations in python throughout the entire code base.

> so it's an extra burden on the libraries' developers.

It's exactly the same burden as they would have in Java. So I'm not clear on what the point is. Worth noting though, that mypy supports stub files, so you can annotate a third party api yourself (https://github.com/python/typeshed, https://github.com/google/pytype/tree/master/pytype/pytd/std...), so even this claim is ultimately untrue.


> This is a uselessly semantic argument.

I'm sorry, but it is your argument that is uselessly semantic.

Yes, people could write Java code where all the type ascriptions are for Object. It is technically possible. But nobody does this. It has nothing to do with "the type ecosystem has been around longer" — it has to do with the fact that the types do something in Java, and always have and always will. You cannot compile a Java program and just not check the types.

But people do write Python libraries without type annotations, and that's fine. There's plenty of not-explicitly-typed Python in the world because that's all there was for a long time before the introduction of optional type annotations. And a lot of people prefer to write Python without type annotations at all because that's one of the benefits of a dynamically typed language — explicit types are unnecessary.

I think your argument is useless because you're intentionally conflating these things and arguing that people could write type-less Java, but since this is not actually done in practice it has no bearing in this conversation.

The point originally being addressed was that in Python you either need fully annotated libraries or else you cannot simply "use MyPy with Any turned off" as the far-parent comment suggested. And my point has been that this is a genuine concern in Python because there's lots of Python code without annotations because Python the language doesn't require or even use the annotations, so they are (from the perspective of the language) purely artifacts of no consequence. This is not the case in real, actually used Java code.

Please stop making arguments in bad faith to "prove a point" or whatever. The point the entire time has been about the use of type annotations in practice, and you appear to be intentionally disregarding this in an attempt to... I don't even know. What are you after here? Do you just not like that somebody disagreed with you? Like, I'm genuinely unsure what your purpose is other than to be contrarian. The concern about "are all the third-party libraries I'm using fully annotated?" is a genuine one in Python that is never even thought about when writing Java (have you ever paused to wonder this when writing Java?), so your argument amounts to a semantic debate that has no basis in the real world.


> This is not the case in real, actually used Java code.

But is the case in its type system, which is what I said. If you want to argue that the python developers are less prone to typing things than the java devs, then say that. Don't claim wrong things about the type systems. They're functionally equivalent.

The type checkers have all the same information, however much the users choose to give them. There are actually a few ways in which python's type system is fundamentally more capable than java's (literal types, protocols).

Literally all a static type system is is an assertion (and tooling to assert) that types must validate before you can run the code. That's it. If you provide a presubmit check that types must validate before you run the code, congratulations, your code is statically typed.

Put simply: enabling mypy + no-any gives you all the same static guarantees (and arguably more) that java does, so in what way isn't it statically typed?


> Put simply: enabling mypy + no-any gives you all the same static guarantees (and arguably more) that java does, so in what way isn't it statically typed?

The original question was about Python the language, not Python the ecosystem. At no point is it ever likely to be correct to say "Python is statically typed"; it will only be possible to say "this specific Python code can be statically typed, if you run the static type checker". This is a fundamental difference between optional static typing in Python versus a natively statically typed language. Because you can run the Python code without running the static type checks, which you cannot do in, say, Java.

Because of this distinction, it is never a genuine concern of a developer in Java or another statically typed language whether third-party libraries will be properly typed. There do not exist libraries without valid static types. You can't install something from Maven or what-have-you that won't type-check.

But you can do this in Python. You can install something via pip and there is no guarantee it will come with good type annotations for your type-checking tool to use, because the type checker is not part of the language proper. It's not required. So Python code exists in the real world that cannot be used with the same assumptions as you can use code in the Java ecosystem. And this is fine, by and large, because Python wasn't meant to be typed. There's nothing wrong with using code like this, nor is there anything wrong with writing code like this. You may not wish to do so personally, which is fine, but I don't think there will ever be an expectation made by the Python language proper of all Python developers to annotate their code. Python will always work without explicit types.

My point in all of this is that your original argument that "[whether there are concerns about third-party libraries having valid type annotations] is true in any type system" does not hold in the real world, because you simply won't have untyped libraries in a statically typed language's ecosystem like you will in Python. Perhaps you didn't realize that this was the point being made, but in any case you went off on a very semantic tangent that had no real bearing on the conversation at hand: the use of type annotations in practice.


> The original question was about Python the language, not Python the ecosystem.

Python doesn't have a spec, so the argument that the language doesn't support static typing is dubious at best, since a static type checker is maintained by the language itself. If I alias python to `mypy $0 && python $0` or whatnot, is it no longer python? It's certainly statically typed.

> Because of this distinction, it is never a genuine concern of a developer in Java or another statically typed language whether third-party libraries will be properly typed. There do not exist libraries without valid static types. You can't install something from Maven or what-have-you that won't type-check.

Is this enforced by the language, or is this just an aspect of the ecosystem? "Maven-packages must typecheck" is enforced by the spec?

> But you can do this in Python. You can install something via pip and there is no guarantee it will come with good type annotations for your type-checking tool to use, because the type checker is not part of the language proper. It's not required.

Are you saying that there is a guarantee in Java that the types need to be "good"? I'm not even clear that maven (which again isn't part of the java language) requires that uploads compile, much less that the type declarations be "good", whatever that means.

Which all returns to my central point: you trust the java ecosystem more that the python ecosystem to have "good" type declarations. There is nothing implicit in the type systems of those languages that enforces that one or the other have better or worse type annotations.

Your argument relies on conflating use in practice with theoretical guarantees, I'm just pointing that out. If you want to say that your experience is that python has less reliable types than java, then sure, that's probably true. But the type systems available in those languages are functionally equivalent. The use, however, differs.


It really feels like your entire contribution to this discussion amounts to "but what if we ignore the point being made and instead just focus on very strict semantic interpretations of the language other people in the conversation are using". I feel like you have not really engaged with my actual points and instead have focused on finding details to complain about in service of appearing to make a point, which effectively derails the true conversation. I find this incredibly frustrating, and I have no interest in continuing.

I would politely suggest you consider whether this is a common issue you have with people or whether it's just me (certainly a possibility, I admit). If this happens to you often, you may want to try alternative approaches to broaching your points if you'd like better engagement. I think most people don't enjoy when their main points are continually ignored in favor of bringing up other minutiae to prove that they're wrong or something.

Regardless, cheers.


A more appropriate example is:

`var list = new ArrayList<String>();` [1]

The compiler figures out the type of `list` without "annotations". Same with C++'s `auto` and Rust's `let`.

[1] https://openjdk.java.net/jeps/286


To clarify, no, this is a complete non-sequitor. The ability for a type system to do inference has no bearing on whether or not the types are checked or not. To make things even more confusing, as an example, python can have type inference (pytype) or not (mypy). In either case, annotations are type checked.


The other discussions have already pointed out some limitations in your interpretation (heh) of types.

I think the key difference between the annotations in Python and types in Java, or even more strongly in languages like Rust and Haskell, is how they are used in the language ecosystem. In Java and Rust, the types are cental to how programmers think in those languages. A large part of the dev cycle goes into ensuring the types are consistent. In python, these type annotations are a relatively recent addition that the community has not completely adopted yet. A lot of real-world python code will have no types, thus the (3rd party) python type checkers will fail to ensure the benefits of having strong consistent types.

And how can we then justify "types" like Object in Java or Any in Rust? Well, sometimes we do want to write code where we want to tell the compiler to stop checking types. However, these are more of an escape hatch rather than a central part of the language ecosystem. As someone else already pointed out, the Java or Rust community does not go about writing code with only Object or Any, even though they can in principle. But in python, that's what the community has been doing till now.

It goes the other way too. If the python community starts taking the type annotations seriously, it will start approaching the same usefulness as in these other static languages with good type systems. Then we can start expecting that the 3rd party library type annotations being accurate, which started this discussion in the first place.


> In python, these type annotations are a relatively recent addition that the community has not completely adopted yet.

I think you'll find I've said as much elsewhere ;)

I think I make essentially the same argument as you do here: https://news.ycombinator.com/item?id=24701113


Yeah sorry, I stopped reading after I felt the discussion was going round in circles. But it's funny how the same viewpoint results in different takeaways. Of course, the topic under discussion is a pedantic one.

However, "that's true of any type system" just feels dismissive of the current state of the world. The comment you were replying to was pointing out that at present, enabling strict type checking in Python, that has been bolted on as an add-on only recently, will not be able to provide benefits to the same level as the ones in Java and Rust where types are front and center. That might become true tomorrow when all Python libraries have adopted types. Hypothetically, tomorrow the Rust community could also decide to adopt Any as the central type everyone uses. But today, the state of the world is very different. Python is still known as a dynamically typed language where devs can essentially forget explicit types, and Rust is a strongly statically typed language where devs will rarely have to worry about missing types.


> Just use mypy

Mypy doesn't work for code that isn't statically available before runtime. Though there are ways to do runtime type checking as well.


A technical note: Python is (relatively) strongly typed, but it is not statically typed.

And the lack of static typing is a fundamental feature of the language. I doubt very much whether a static type system will ever be implemented in Python proper.


It would probably never happen and I dont see why it should ever matter. You can always use tooling to enforce it as another comment mentioned. I would argue if you decide to type annotate a whole project you should just go for it.

I have been type annotating my project over time in the hopes I eventually have just enough context about what I am doing. Also a good IDE will show you any place wrong types are criss crossed.


This is an explicitly stated non-goal of type annotations in Python across several official documents.


The PEP abstract says this: "Static typing as defined by PEPs 484, 526, 544, 560, and 563 was built incrementally on top of the existing Python runtime and constrained by existing syntax and runtime behavior"


This is how i understood it (from https://www.python.org/dev/peps/pep-0563/). Correct me if im wrong please.

One of the reasons was that `list[int]` could not be done, as that would refer to a list index. Same as you would do `my_var = [1,2,3]` `my_var[0]`

Therefore it was possible to use just `int` as a type.

From python 3.9 (unless you used from __future__ import annotations in 3.7+) type hints are not parsed "as python", at function definition time, but more or less as text. In 3.9 `list[int]` does not want an item from object `list` at index 'int' when using it as a type annotation.

The typing library did not have these issues because the `types` altered the way the objects worked. Probably (didnt check it!) by overriding __getitem__ and the likes.


Well, `list` and friends were functions to construct those objects. So presumably they did it this way to prioritize adding typing over getting it perfect.

I think it was the right choice.


It was a problem only because somehow they decided that type annotations should evaluate to something meaningful at runtime, instead of being a compile-time only construct like in every other language.


"Somehow they decided" is rooted in the desire to make typing optional, and absolutely not a requirement.


Wouldn't compiler selective enforcement achieve the same goal? It seems to me the difference is that other programs can be implemented to do the typechecking (eg mypy) rather than the compiler doing so itself


It lets you do things like `functools.singledispatchmethod`.


Any language with reflections needs to evaluate types as something meaningful at runtime.


The runtime mirror doesn't have to have the same name as the compile-time type. Look at Java where you have to do weird things with class objects to do reflective access.


list is also a type. Maybe that wasn't true in older versions, but it's true now.


I suspect a big part is the general desire to minimize the runtime impact of typing support,especially on code not using it; there's a bunch of infrastructure added to the core classes that now can be generic types to support that.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: