Python proposal (inspired by Lua)(hachyderm.io)
hachyderm.io
Python proposal (inspired by Lua)
https://hachyderm.io/@nedbat/110962461917632486
66 comments
Very much agreed.
As if Python wasn't muddied enough, people now want to tack on every bit of syntax sugar just to have...another different way to do the same thing.
Urrrghh...
As if Python wasn't muddied enough, people now want to tack on every bit of syntax sugar just to have...another different way to do the same thing.
Urrrghh...
Yeah, this is dumb as rocks.
On top of what you said, Python already has a mechanism to do what the author is claiming!
On top of what you said, Python already has a mechanism to do what the author is claiming!
>>> def f(x, y, a, b):
... print(x, y, a, b)
...
>>> args = {'x': 98, 'y': 99, 'a': 1, 'b': 2}
>>> f(**args)
98 99 1 2Doesn’t work with functions that take an arbitrary number of positional args.
Not sure of any real examples of that function API but I suspect that’s what Ned is going for.
Not sure of any real examples of that function API but I suspect that’s what Ned is going for.
[deleted]
Hm, where is the large advantage over building a list pos_args and a map kw_args and calling func(*pos_args, **kw_args)? You can also transform between both forms with a few comprehensions with a few isinstance-checks.
>>> def foo(a, b, c=42, d=23):
... print(a, b, c, d)
...
>>> args = ['a-value', 'b-value']
>>> kwargs = { 'c': 'c-value', 'd': 'd-value' }
>>> foo(*args, **kwargs)
a-value b-value c-value d-valueI think you're right. Or if you have functions you'd want to use this more often than never, I'd strongly prefer to make them kwarg-only (ie `def foo(*, arg1, arg2, ...)`). Then you don't have to care about ordering at all.
There is none. But let's get another hack in, why not.
/s
/s
I'm guessing someone got inspired by https://adamj.eu/tech/2021/05/11/python-type-hints-args-and-...
IMHO mixing args and kwargs together like this - having a dict with mixed types for keys - looks kinda grotty.
IMHO mixing args and kwargs together like this - having a dict with mixed types for keys - looks kinda grotty.
I agree, it changes the semantics of dictionaries in a way that doesn't feel right. There are several things that are off-putting, but another example in vein with the mixed key types is the implicit ordered dict.
Every dict in Python is already ordered so it doesn't change much and Python doesn't blink at something like `int | str` so you can already do something like {0: "hello", "0": "world"} if you hated your readers. The only change would be changing how ** works when used as function arguments which is already bespoke.
Hey everyone, this was my proposal. Don't take it too seriously. I tossed it off on Mastodon, it's not a PEP or anything. Python isn't going downhill because of me!
Maybe I've been writing boring code, but I haven't run across this problem all that often.
Nope, you have been writing the correct and clear code.
Boring code is good code in our profession.
Boring code is good code in our profession.
If the function definition is `def f(x, y, a=None, b=None)`, you can already just call `f(*dict(a=1, x=98, y=99, b=2))`. So instead of using `0` and `1` for the positions, like the tweet author suggests, you can just use the parameter names.
However, I've always disliked that you could do this in Python -- passing positional args out of order as kwargs. And also that you could pass kwargs as positional args.
Python 3 and later 3.8 introduced optional * and `/` in signature syntax to prevent both of these behaviors, which I appreciate, but IMO this should have been the default behavior from the beginning. Now you can prevent both behaviors by defining the function with `def f(x, y, /, *, a=None, b=None)`.
However, I've always disliked that you could do this in Python -- passing positional args out of order as kwargs. And also that you could pass kwargs as positional args.
Python 3 and later 3.8 introduced optional * and `/` in signature syntax to prevent both of these behaviors, which I appreciate, but IMO this should have been the default behavior from the beginning. Now you can prevent both behaviors by defining the function with `def f(x, y, /, *, a=None, b=None)`.
What's the benefit of prohibiting passing positional arguments by name?
When args are positional-only, you can rename them without breaking callers. The names of the kwargs can be considered "external" and the names of the positional args remain private to the function.
This gives authors flexibility to rename args, or even convert all the positional args to variadic `*args`, without affecting users.
It also gives authors control over argument order. Like you write `def pow(x, y)`, conventionally meaning "x to the power y", and you don't want users to call it like `pow(y=2, x=3)`.
This gives authors flexibility to rename args, or even convert all the positional args to variadic `*args`, without affecting users.
It also gives authors control over argument order. Like you write `def pow(x, y)`, conventionally meaning "x to the power y", and you don't want users to call it like `pow(y=2, x=3)`.
Building up kwargs should be something done as a last resort, or in situations where you need to be a pass-through eg decorators, and the current functionality covers that more or less perfectly. I am suspicious that this suggestion is just to enable poor quality code that the person suggesting it would just grow out of over time.
You can just write a class to track your kwargs and args if you want to do what is in this suggestion. Then when you want to use it you would have to do `whatever_func(*args.args, **args.kwargs)`, which isn't exactly a huge amount of trouble.
You can just write a class to track your kwargs and args if you want to do what is in this suggestion. Then when you want to use it you would have to do `whatever_func(*args.args, **args.kwargs)`, which isn't exactly a huge amount of trouble.
Ok so what about:
Nobody really needs this, but, as ever, submit a PEP and see if there’s a load of people who have a calling for it.
def fn(a)
fn(**{'a': True, 0: False)
I get it, python is dynamic so it allows all sorts of things but I’d be pretty cross if I was debugging code like this. Explicit is better and all that.Nobody really needs this, but, as ever, submit a PEP and see if there’s a load of people who have a calling for it.
That looks like something which could be implemented with a function decorator.
(A little bit ugly, though.)
(A little bit ugly, though.)
I don't see the point personally. I don't feel like this is something you'd want to do often, and it's not like the current way to do it is that much harder. This seems to just complicate things with hardly any benefit.
I'm not sure if people realize that python allows dictionaries to have integer keys. This is syntactically valid python code:
Also, it's interesting to see a lot of people saying that this is too "black magic" for python, when python is probably one of the languages with the most black magic I've seen. In fact, I'm sure that with maybe just a single call to a helper function you can simulate this new feature thanks to the python black magic...
def sum(a, b):
return a + b
d={0:5, 1:9}
sum(**d)
But it throws a TypeError: sum() keywords must be strings.Also, it's interesting to see a lot of people saying that this is too "black magic" for python, when python is probably one of the languages with the most black magic I've seen. In fact, I'm sure that with maybe just a single call to a helper function you can simulate this new feature thanks to the python black magic...
In case you’re wondering what Lua does in this case, it has a special syntax iff the sole argument is a “table” (core dict/array mix) or a string:
It doesn’t have **kwargs in pythonic form, although it has varargs syntax. But all parameters are strictly positional and don’t self-pack or self-unpack in any way.
So this inspiration is pretty far from what happens there.
f {1, 2, a=3}
is equivalent to f({1, 2, a=3})
A function f(t) receives an argument t in both cases. This makes a good use for DSLs by reducing ()-noise.It doesn’t have **kwargs in pythonic form, although it has varargs syntax. But all parameters are strictly positional and don’t self-pack or self-unpack in any way.
So this inspiration is pretty far from what happens there.
And calling without ()'s is also available if you call with a single string literal, as often seen in require "modulename".
This doesn't seem like it would solve any real-world problems? Other than maybe hacking around in repl mode?
This would require dicts to be somewhat ordered, as explained by others here already.
Also I feel like this would violate `explicit is better than implicit` as per https://peps.python.org/pep-0020/ and would most certainly lead to extremely confusing/brittle code.
I'm somewhat surprised this was upvoted so much.
Also I feel like this would violate `explicit is better than implicit` as per https://peps.python.org/pep-0020/ and would most certainly lead to extremely confusing/brittle code.
I'm somewhat surprised this was upvoted so much.
Lists (like *args) are by definition ordered, and dicts (and thus **kwargs) are by definition unordered key-value pairs.
This would be very confusing in suddenly giving dicts a half-ordered, half-not semantics in a particular context.
Why wouldn't you simply switch to setting everything through kwargs instead?
Not to mention that this would have a performance penalty for dicts as they would have to keep an ordered set of integer keys in case they are used with **.
This would be very confusing in suddenly giving dicts a half-ordered, half-not semantics in a particular context.
Why wouldn't you simply switch to setting everything through kwargs instead?
Not to mention that this would have a performance penalty for dicts as they would have to keep an ordered set of integer keys in case they are used with **.
Dicts already have confusing half-ordered, half-not semantics. As of 3.7 they are guaranteed to be insertion-ordered, but operators like == don't care about order.
I don't think it's confusing or even on the level of "semantics": looping over dict keys/elements is a common pattern and the 3.7 change makes that a little more predictable and consistent.
Two dicts with same elements not being equal would be surprising and confusing.
Two dicts with same elements not being equal would be surprising and confusing.
If we're treating them as ordered containers then it really ought to be surprising and confusing that two dicts with the same elements in a different order are considered equal. Other ordered containers such as lists or tuples don't behave this way.
I'd use an `OrderedDict` in situations where I have a mapping where the order of elements is significant. `OrderedDict` signals intent much more clearly, and its `__eq__` method cares about order.
I'd use an `OrderedDict` in situations where I have a mapping where the order of elements is significant. `OrderedDict` signals intent much more clearly, and its `__eq__` method cares about order.
Yes, but I hope nobody really relies on that ordering in their code, as, as you say, it is pretty inconsistent.
Eg. you can't rely on
Eg. you can't rely on
dict_b_keys = iter(dict_b)
for key in dict_a:
value_a = dict_a[key]
value_b = dict_b[next(dict_b_keys)]
print(f"{value_a} == {value_b}: ", value_a == value_b)
The problem with the proposal is that it will be even stronger: you either require '*' to sort in-place (I assumed that would be even worse and thus unacceptable, so I ignored that case — it seems others believe that to be a more realistic implementation), or require dicts to maintain a half-sorted map of integer keys.Just laughing at the Cunningham's Law response of everyone jumping in to let you know that dictionaries are now insertion ordered.
What I meant was "sorted", which is a bit more specific than "ordered", but it serves me right :)
The funniest part is that this does not really matter for this discussion: insertion order is pretty much arbitrary when what you need is for key 0 to come first, key 1 to come next...
The funniest part is that this does not really matter for this discussion: insertion order is pretty much arbitrary when what you need is for key 0 to come first, key 1 to come next...
The proposal in question is not that good so maybe the order of the dicts is more interesting :P
Besides the mistake about dicts being ordered in Python which was pointed out by others,
> Why wouldn't you simply switch to setting everything through kwargs instead?
Not all arguments in Python are passable both ways.
In
def f(a, /): print(a)
`a` may be passed only positionally.
> Why wouldn't you simply switch to setting everything through kwargs instead?
Not all arguments in Python are passable both ways.
In
def f(a, /): print(a)
`a` may be passed only positionally.
I'd argue that functions with only position arguments are a place where you'd never want to use this proposal, ever. A tuple or list would be the right data structure to pass in there.
Of course it would be irrelevant for a function that only took positional arguments.
I was demonstrating the existence of such things in general since OP seemed unaware. For the proposal, you'd of course only use it for functions that took more kinds of arguments.
I was demonstrating the existence of such things in general since OP seemed unaware. For the proposal, you'd of course only use it for functions that took more kinds of arguments.
Thanks for pointing it out. Still, it seems highly unlikely someone would decide to use such a function declaration yet want to use the proposed syntax to pass both positional and keyword arguments.
> Not to mention that this would have a performance penalty for dicts as they would have to keep an ordered set of integer keys in case they are used with *.
...why?
I don't love this proposal, but `{1:"a", 2:"b", 0:"c"} == {0:"c", 1:"a", 2:"b"}`, and so presumably either would have the same behavior when passed to `def f(foo, bar baz): ...`.
That the runtime would correct the order as part of the splat operation seems implied?
...why?
I don't love this proposal, but `{1:"a", 2:"b", 0:"c"} == {0:"c", 1:"a", 2:"b"}`, and so presumably either would have the same behavior when passed to `def f(foo, bar baz): ...`.
That the runtime would correct the order as part of the splat operation seems implied?
Passing a huge dict and assuming the runtime would magically do the sorting was exactly an implementation I ignored because it sounded preposterous to me.
Just imagine passing in a 500k item dict to ** by accident: dicts maintaining sorted state appeared the only sensible implementation to me.
Just imagine passing in a 500k item dict to ** by accident: dicts maintaining sorted state appeared the only sensible implementation to me.
But again: why do you need to care about the whole sorted state thing? "Sorting" a dictionary keyed by index is linear time.
Like the cost of doing that isn't going to be greater than the existing cost of keyword arguments, which can already be arbitrarily ordered!
Like, what about this is more "preposterous" than passing a massive dict to kwargs already?
Like the cost of doing that isn't going to be greater than the existing cost of keyword arguments, which can already be arbitrarily ordered!
Like, what about this is more "preposterous" than passing a massive dict to kwargs already?
Passing a dict is passing by reference, and if the called method reads 5 elements from that dict, it's tiny constant time.
Sorting of a large collection happening implicitly is too expensive for what would generally be a benign mistake.
Sorting of a large collection happening implicitly is too expensive for what would generally be a benign mistake.
But again: you don't actually need to sort anything. Like that's really important you don't need to sort anything!
Passing kwargs requires resolving n references, one for each kwarg. Passing args by index requires passing n references. Like in code:
Edit: for context,
Passing kwargs requires resolving n references, one for each kwarg. Passing args by index requires passing n references. Like in code:
def convert_dict_to_args(f, magic_kwargs):
out_args = []
out_kwargs = {}
for k, v in magic_kwargs:
if isinstance(k, int):
out_args[k] = v
else:
out_kwargs[k] = v
return f(*out_args, **out_kwargs)
There's no sorting necessary! There's potential particular optimizations that might need to happen to make this work with python's opcodes for fast function calls, since out_args isn't constructed as a tuple which is internally optimized in function calls, but afaik you're already not getting those optimizations/fast paths if you're passing by name and/or using kwargs, so this isn't a change.Edit: for context,
>>> from timeit import timeit as t
>>> t(stmt='f(1,2,3)', setup='def f(a, b, c): pass', number=int(1e8))
5.765844099999981
>>> t(stmt='f(*(1,2,3))', setup='def f(a, b, c): pass', number=int(1e8))
5.840746500000023
>>> t(stmt='f(a=1,b=2,c=3)', setup='def f(a, b, c): pass', number=int(1e8))
7.400104499999998
>>> t(stmt='f(**d)', setup='d=dict(a=1,b=2,c=3)\ndef f(a, b, c): pass', number=int(1e8))
20.779321399999958
>>> t(stmt='f(**d)', setup='d=dict()\ndef f(a=None, b=None, c=None): pass', number=int(1e8))
9.583819900000094
So yeah using kwargs at all has a significant performance penalty, so you're already hitting performance penalties for using kwargs, keeping a sorted view isn't going to help sinificantly.Since "sorting is not necessary", why don't you try:
And if it's still not clear, pass
>> out_args = []
>> out_args[3] = 'foo'
?And if it's still not clear, pass
magic_kwargs = { 'arg1': 'bar', 2: 'foo', 'arg2': 'yeah', 1: 'not', 0: 'really'}
to that helper you wrote.> >> out_args = []
>> out_args[3] = 'foo'
Yes, this should be a typeerror. I elided any kind of error checking, but you still don't need to sort to check that. You run into the same issue if you pass `*range(5)` to a function of one argument today.
> magic_kwargs = { 'arg1': 'bar', 2: 'foo', 'arg2': 'yeah', 1: 'not', 0: 'really'}
Again, this would be a TypeError (assuming the underlying function is defined as `def (arg1, arg2, ...) ` or something. And yet again, there's no need to sort anything to detect that error.
I feel like the thing you're getting at here is that you get index out of range errors given the half-assed implementation I did, which is absolutely true. It's buggy, it's broken, it's demo code. But you solve that by finding the max value of the integers keys of the dict, but finding the maximum value at worst requires a second linear scan, not a sort. You never need to sort the keys, you can always place them or error in linear time.
For a fuller implementation:
args = ('really', 'not', 'foo') kwargs = {'arg1': 'bar', 'arg2': 'yeah'}
Yes, this should be a typeerror. I elided any kind of error checking, but you still don't need to sort to check that. You run into the same issue if you pass `*range(5)` to a function of one argument today.
> magic_kwargs = { 'arg1': 'bar', 2: 'foo', 'arg2': 'yeah', 1: 'not', 0: 'really'}
Again, this would be a TypeError (assuming the underlying function is defined as `def (arg1, arg2, ...) ` or something. And yet again, there's no need to sort anything to detect that error.
I feel like the thing you're getting at here is that you get index out of range errors given the half-assed implementation I did, which is absolutely true. It's buggy, it's broken, it's demo code. But you solve that by finding the max value of the integers keys of the dict, but finding the maximum value at worst requires a second linear scan, not a sort. You never need to sort the keys, you can always place them or error in linear time.
For a fuller implementation:
def convert_dict_to_args(f, magic_kwargs):
_max = 0
for k in magic_kwargs.keys():
if isinstance(k, int):
if k < 0: raise TypeError
if k > _max: _max = k
elif not isinstance(k, str):
raise TypeError
_SENTINEL = object()
out_args = [_SENTINEL for _ in range(_max)
out_kwargs = {}
for k, v in magic_kwargs.items():
if isinstance(k, int):
out_args[k] = v
else:
out_kwargs[k] = v
if _SENTINEL in out_args:
raise TypeError
return f(*out_args, **out_kwargs)
And this version functions as expected, still without any sorting required:args = ('really', 'not', 'foo') kwargs = {'arg1': 'bar', 'arg2': 'yeah'}
You've basically sorted those int-keyed elements into a list, only optimized by making a few assumptions (eg. that all indexes are present up to a max: if you want to error check that like a real implementation would have to, you lose some of that optimization since you now have to keep track of missing elements, which is at least another loop through the list).
All of these optimizations are applicable to sorting a sparse, bounded list of integers, but that's still sorting them.
All of these optimizations are applicable to sorting a sparse, bounded list of integers, but that's still sorting them.
> You've basically sorted those int-keyed elements into a list
No, I've copied them into a list. There was no sorting. I've never compared elements or done any nlogn operations. I've shuffled, perhaps, or ordered, but not sorted. That's a meaningful distinction.
> have to keep track of missing elements, which is at least another loop through the list).
The implementation I have is complete, it errors if the array is sparse. You need at most two loops through the dict, which if you wanted to you could avoid with a small (constant time and constant space) change to the underlying dict implementation to cache the max integer key stored and require only a single iteration.
You can technically do even faster than that by making the iteration over the number of positional args, not total args, but in practice that would probably be slower.
And it's all going to be just as fast as kwargs today.
Like jumping back a bit your argument is
> Passing a dict is passing by reference, and if the called method reads 5 elements from that dict, it's tiny constant time.
But if you pass **kwargs to a method, the dict size is unbounded, so it's O(n), not constant! Exactly the same as now. And if the method being called declares it's params explicitly, it's still constant since you can immediately error on the first unknown parameter!
If it was constant before, it's still constant. If it was linear before, it's still linear. And you never need to sort anything.
Just to further clarify, above you say
> and **kwargs has every element read in O(1) time or it gets passed along by reference.
Reading every element in O(1) time is O(n)! And is precisely the same as what's happening now.
No, I've copied them into a list. There was no sorting. I've never compared elements or done any nlogn operations. I've shuffled, perhaps, or ordered, but not sorted. That's a meaningful distinction.
> have to keep track of missing elements, which is at least another loop through the list).
The implementation I have is complete, it errors if the array is sparse. You need at most two loops through the dict, which if you wanted to you could avoid with a small (constant time and constant space) change to the underlying dict implementation to cache the max integer key stored and require only a single iteration.
You can technically do even faster than that by making the iteration over the number of positional args, not total args, but in practice that would probably be slower.
And it's all going to be just as fast as kwargs today.
Like jumping back a bit your argument is
> Passing a dict is passing by reference, and if the called method reads 5 elements from that dict, it's tiny constant time.
But if you pass **kwargs to a method, the dict size is unbounded, so it's O(n), not constant! Exactly the same as now. And if the method being called declares it's params explicitly, it's still constant since you can immediately error on the first unknown parameter!
If it was constant before, it's still constant. If it was linear before, it's still linear. And you never need to sort anything.
Just to further clarify, above you say
> and **kwargs has every element read in O(1) time or it gets passed along by reference.
Reading every element in O(1) time is O(n)! And is precisely the same as what's happening now.
If you only need 5 kwargs, reading 5 elements from a 500k element dict is O(1). Just finding the max index is O(n). You might be right that even passing 500k element dict to a function **kwargs has terrible performance today anyway (I haven't checked but it does sound plausible from your timing tests).
It seems like we are now looking for imprecisions in each other's reply and discussion does not seem to be constructive: we are agreeing on rough potential implementations, it's just that we differ whether that should be acceptable or not.
That's fine, you are not convincing me and I am not convincing you, so we might as well drop it. It's not like this proposal is happening :)
Have a good day!
It seems like we are now looking for imprecisions in each other's reply and discussion does not seem to be constructive: we are agreeing on rough potential implementations, it's just that we differ whether that should be acceptable or not.
That's fine, you are not convincing me and I am not convincing you, so we might as well drop it. It's not like this proposal is happening :)
Have a good day!
Note that we are comparing against existing `*args, **kwargs`: no sorting is needed at all, as *args is processed by order, and **kwargs has every element read in O(1) time or it gets passed along by reference.
in Python, dicts are actually definitionally ordered, since 3.7, so this objection doesn't carry much weight.
additionally, you might be interested to know that the implementation of dicts as ordered was found to be more performant than its predecessor.
Finally, even if they were unordered, there would be no need for ordering in this proposal, which depends on unique integer keys starting at 0.
additionally, you might be interested to know that the implementation of dicts as ordered was found to be more performant than its predecessor.
Finally, even if they were unordered, there would be no need for ordering in this proposal, which depends on unique integer keys starting at 0.
The fact that they are definitionally ordered does not really matter for this discussion: for all intents and purposes of this proposal, they are unordered (insertion order is pretty much arbitrary order when it comes to their use as positional arguments or kwargs). And my intent was a bit more specific: I was thinking of "sorted", so definitely my bad.
For the proposed use case, either **kwargs would have to sort a subset of the dict with integer keys to map positional arguments to their, ahem, positions, and then either leave the rest as-is (or sort it altogether), or a dict would have its definition modified to keep this order as items are added.
I dislike both, and I definitely hate implicit sorting happening with **.
For the proposed use case, either **kwargs would have to sort a subset of the dict with integer keys to map positional arguments to their, ahem, positions, and then either leave the rest as-is (or sort it altogether), or a dict would have its definition modified to keep this order as items are added.
I dislike both, and I definitely hate implicit sorting happening with **.
Dicts are ordered as of Python 3.7.
>the insertion-order preservation nature of dict objects has been declared to be an official part of the Python language spec.
https://docs.python.org/3/whatsnew/3.7.html
>the insertion-order preservation nature of dict objects has been declared to be an official part of the Python language spec.
https://docs.python.org/3/whatsnew/3.7.html
Python dictionaries are guaranteed to preserve insertion order since python 3.7
Can someone explain to me why dictionaries being ordered or not matter here? It does not!
The integer key represents the argument you want to assign the variable to.
And what if you specify an argument with an integer key but also with the corresponding string key? Guess what, python allows this (for example f(1, *[2], **{"v":3}), and it throws a TypeError: sum() got multiple values for argument 'a'
The integer key represents the argument you want to assign the variable to.
And what if you specify an argument with an integer key but also with the corresponding string key? Guess what, python allows this (for example f(1, *[2], **{"v":3}), and it throws a TypeError: sum() got multiple values for argument 'a'
Thoughts:
Closest implemented thing that I know of that implements the solution to the stated problem: Raku's Capture - https://docs.raku.org/type/Capture
Maybe Python can go in that direction.
Subjectively, it doesn't feel right to mix positional and named containers in Python.
Closest implemented thing that I know of that implements the solution to the stated problem: Raku's Capture - https://docs.raku.org/type/Capture
Maybe Python can go in that direction.
Subjectively, it doesn't feel right to mix positional and named containers in Python.
It seems to me (waves hands) that this could be implemented as a decorator.
Why not jack around and find out if this makes sense?
Even if good for one use-case, it may not generalize sufficiently to make core changes.
Why not jack around and find out if this makes sense?
Even if good for one use-case, it may not generalize sufficiently to make core changes.
Python was heading downhill ever since people started introducing typing outside of the actual "typing" module.
We could have kept it as a module-only thing and it would have made it entirely optional feature that you could easily introduce or remove depending on Python version you were targeting. Now, you have types all over and new ways to type random things keep popping up. And of course, that motivated people to propose more stuff like this.
Just utter meaningless "feature", making this language even more convoluted than it already is ever since Guido sold out to Microsoft. Yes, I get it, it's his language and he can do whatever he wants with it. But a lot of people contributed to Python over the decades. And not a lot of them agree on where it is heading.
A sad thing, really.
EDIT: Downvote me to oblivion. Someone had to say it.
We could have kept it as a module-only thing and it would have made it entirely optional feature that you could easily introduce or remove depending on Python version you were targeting. Now, you have types all over and new ways to type random things keep popping up. And of course, that motivated people to propose more stuff like this.
Just utter meaningless "feature", making this language even more convoluted than it already is ever since Guido sold out to Microsoft. Yes, I get it, it's his language and he can do whatever he wants with it. But a lot of people contributed to Python over the decades. And not a lot of them agree on where it is heading.
A sad thing, really.
EDIT: Downvote me to oblivion. Someone had to say it.
Since you're asking, I did downvote this comment. My reasoning is that it is factually dubious and unrelated to the topic.
The typing features are optional, so much so that CPython by default doesn't do anything with them.
The rest of the comment honestly sounds like a rant. Python has had governance independent of Guido for some time now, I don't really get the point.
The typing features are optional, so much so that CPython by default doesn't do anything with them.
>>> def f(x: int):
... return 2 * x
...
>>> f(1)
2
>>> f("hello")
'hellohello'
It's also a weird claim that typing is unpopular, since it's almost universally considered to be a Good Thing™.The rest of the comment honestly sounds like a rant. Python has had governance independent of Guido for some time now, I don't really get the point.
> The typing features are optional, so much so that CPython by default doesn't do anything with them.
That is true...for now.
But let us look at these two PEPs, only:
https://peps.python.org/pep-0585/s and
https://peps.python.org/pep-0604/
PEP 585 added new syntax for built-in collection types that doesn't work with older versions of Python.
PEP 604 also added new Union and Optional typing syntax that doesn't work with older versions of Python, either. So, you will effectively break your code if you try adding any of these. In a large enough codebase, there could be dozens of these annoying things. Why do that when you could have offloaded every single typing annotation to it's separate module (conveniently called typing), and add a simple shortcut to an IDE to have types removed/re-introduced automatically.
There are even more built-in ways to type your arguments now. So, let's see how long your argument stands.
That is true...for now.
But let us look at these two PEPs, only:
https://peps.python.org/pep-0585/s and
https://peps.python.org/pep-0604/
PEP 585 added new syntax for built-in collection types that doesn't work with older versions of Python.
PEP 604 also added new Union and Optional typing syntax that doesn't work with older versions of Python, either. So, you will effectively break your code if you try adding any of these. In a large enough codebase, there could be dozens of these annoying things. Why do that when you could have offloaded every single typing annotation to it's separate module (conveniently called typing), and add a simple shortcut to an IDE to have types removed/re-introduced automatically.
There are even more built-in ways to type your arguments now. So, let's see how long your argument stands.
> So, you will effectively break your code if you try adding any of these.
I don't follow, isn't this just bringing in features from a later version of the language and complaining they don't work? Pattern matching doesn't work in Python 3.9, but I wouldn't say it "effectively breaks your code" just because you try them in python3.9
You also don't have to add them, they're optional.
I don't follow, isn't this just bringing in features from a later version of the language and complaining they don't work? Pattern matching doesn't work in Python 3.9, but I wouldn't say it "effectively breaks your code" just because you try them in python3.9
You also don't have to add them, they're optional.
Introducing pattern matching to Python, in a minor version no less, almost created another language inside Python that you have to learn now. New typing syntax outside of the "typing" module did the same.
People governing the development of Python forgot what backwards compatibility of a programming language means.
People governing the development of Python forgot what backwards compatibility of a programming language means.
You don't need to learn any of those, and they don't break pre-existing code. You can run code written for Python3.2 in 3.10. You can't run code written for Python3.10 in Python3.2. That's what backwards compatibility means. It has nothing to do with new features that only affect themselves.
Also Python predates SemVer, so it doesn't use major.minor.patch. 3.10 is not considered a minor update.
Also Python predates SemVer, so it doesn't use major.minor.patch. 3.10 is not considered a minor update.
https://peps.python.org/pep-0484/
> It should also be emphasized that Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.
> It should also be emphasized that Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.
Excellent. Python is becoming horrible and losing many of the good qualities it had to cater to a crowd of people that would be better served by java and c#
Popularity is a curse, quality goes downhill superfast
Popularity is a curse, quality goes downhill superfast
[deleted]
Python on the other hand has a clear separation between the two usecases as separate datatypes, so munging them like this would muddy the language.