Things to use in Python 3
datawhatnow.com
datawhatnow.com
> Static vs dynamic typing is a spicy topic in software engineering and almost everyone has an opinion on it. I will let the reader decide when they should write types, but I think you should at least know that Python 3 supports type hints.
I think this gives readers the impression that type hints have anything to do with dynamic vs. static type systems, which isn't true. These are merely annotations that are attached to values. In fact, it's legal to use full strings as type annotations — they have no inherent semantic value.
You can gain semantic value by using a tool like MyPy or an IDE like PyCharm, but the type annotations do not in themselves do anything. I think it could be worth clarifying this for readers who are unaware.
x: int = 0
This makes sense when you see that the variable production in the Python grammar [0] has been updated to essentially read "either a lone variable or a variable with a type annotation". (I highlighted the relevant line of the grammar for you in the link.)[0] https://github.com/python/cpython/blob/master/Grammar/Gramma...
That comes close to being the worst idea in the history of programming languages. Types aren't checked, you can't trust the annotations, and you give up a big optimization opportunity. But Guido's naive interpreter will still work.
If you say something is a float, the compiler needs to both enforce that and use that. Then maybe the Python crowd wouldn't need to call out to C code whenever they needed to do some number crunching.
Python is never going to be as fast as C. Ever. Even if you somehow managed to decouple numeric types from PyObject in a backwards compatible way it would not be as fast.
Type Hints can’t be used as an optimization in the interpreter due to the way they are resolved. And in any case, it would not be safe to do so and there are very few cases where optimizations make sense.
As for types not being checked, that's not a problem. JITs do extra specialization on assumed types even without annotations all the time, and just drop them at runtime when they are not valid anymore (e.g. a variable that only pointed to integers now stores a string). Still, this runtime overhead of dropping specialized code aside, those optimizations make e.g. v8 hella fast.
(And of course you could also just enable optimizations after you've statically checked the whole program types with something like mypy -- this is even easier and less dynamic than what v8 does).
>and you give up a big optimization opportunity
You don't give anything up, since an implementation can take advantage of this "big optimization opportunity" if it wants and has the manpower to add it.
>But Guido's naive interpreter will still work.
It's also less work, which unless we have a volunteer here, was and remains the intention.
Numpy and Scipy use a lot of Fortran at the backend, and unfortunately, beating Fortran when it comes to number crunching is insanely difficult, not just because of the language, but because those libraries like LAPACK have spent decades being refined into incredibly fast and accurate systems.
Python's ability to talk to those libraries is a bonus. But Python itself could never compete directly with them. Pure-python code isn't capable of some of the insane speed those libraries can pull off.
In addition, due to their use in benchmarks, x86 has in part been designed to make LAPACK fast.
As for libraries, other than MyPy, there's also Pyre by Facebook and Pytype by Google. I'm definitely excited to see more advanced static analysis being implemented beyond simple type checking. Some of these libraries are starting to explore that.
[1]: https://www.python.org/dev/peps/pep-0563/#non-typing-usage-o...
The reason this library exists in stdlib is so that all the different type hint libraries can interoperate. Otherwise if they each brought their own typing, you couldn't switch between different static analysis libraries.
The distinction I'm drawing is to statically-typed languages where simply adding annotations will actually change semantics of the program (e.g., by forcing a downcast or something).
But you're absolutely right that annotations can be useful for many things!
Unfortunately the IDE support for those files (.pyi extension) is quite limited -- I have even suggested to the PyCharm team to implement an option to automatically save all annotations to stub files, while still displaying them alongside the code in the editor.
Does PyCharm support these? Like if I write the stub files manually, will type hints and autocompletion work correctly in the main file? This could be a great way to reduce the amount of syntactic overhead, at least in function definitions and top-level code.
Another advantage is that you can use pyi files for static checking, but remove them when deploying the application.
Or linters could do some basic analysis.
I.e. A module contains a function that accepts a pandas.DataFrame. This module doesn't import pandas.
if TYPE_CHECKING:
import whatever
def foo(param: 'whatever'): ... Conda for managing environments and Deep Learning builds such as Cuda on Ubuntu
Jupyter notebooks for quick prototyping
IPython if you have never seen it before.
Requests LibraryWith format(), and hence with f-string you can:
# Replace datetime.stftime()
>>> f"{datetime.now():%m/%d/%y}"
'05/15/19'
# Show numbers in another base
>>> f"{878:b}"
'1101101110'
>>> f"{878:x}"
'36e'
>>> f"{878:e}"
'8.780000e+02'
>>> f"{878:o}"
'1556'
# Control the display of padding, filling, and precision of numbers
>>> 1 / 3
0.3333333333333333
>>> f"{1 / 3:.2f}"
'0.33'
>>> f"{69:4d}"
' 69'
>>> f"{69:=+8d}" '+ 69'
>>> f"{69:0=8d}"
'00000069'
# Center text
>>> f"{'foo':^10}"
' foo '
And so many other things, since you can create your custom __format__.
Not affiliated, but in this regard, I quite love https://pyformat.info/ as a format cheat sheet.
I never knew this existed but that's awesome! Thanks for the link!
>>> x = 1
>>> f"{x}"
'1'
This is much more readable than: >>> "{x}".format(x=x)
'1'
>>> "{}".format(x)
'1'
It makes the code immensely more readable than having to count parameters, especially for long strings with a lot of data in them.
Counting is for computers, not programmers; I shouldn't have to count anything for such a trivial task.So, thanks to f"", no need for what I find to be an ugly additional call to .format(...), that is just visual clutter for most cases.
The only reasons I could see for a call with a dedicated dict is data exfiltration from a user provided format string executed on python code they do not control, or renaming/subsetting of keywords for cleanliness. Both are special cases, and that's the effect the current implementation has, thankfully.
No its not.
"My name is {}, I'm {}'{}'' tall and live in {}".format(name, location, feet, inches)
This error is much better hidden than in the equivalent: f"My name is {name}, I'm {location}'{feet}'' tall and live in {inches}."I think this is more explicit:
"My name is {name}, I'm {feet} tall and live in {location}.".format(name="Bob", location="USA", feet=7)
Your actual example would be:
"My name is {name}, I'm {feet} tall and live in {location}.".format(name=name, location=location, feet=feet)
And with f-strings: f"My name is {name}, I'm {feet} tall and live in {location}."
f-strings is clearly more readable, concise and actually faster to run.If its faster to run, then fair enough.
"My name is {name}, I'm {feet} tall and live in {location}.".format(name="Bob", location="USA", feet=7)
is less explicit than
"My name is Bob, I'm 7 feet tall and live in USA."
I've heard similar complaints about the loop macro in Common Lisp, and this feels a bit like shell or Tcl strings which could mean anything (arguments to sed/awk or paths to widgets/urls). However, I guess people are familiar with regular expressions as a completely foreign nested language (DSL), so this isn't much different than that.
F"My string with {interpolated_value}"
f'My string with {interpolated_value}'
Like using apostrophes or quotation marks to enclose strings (or F-strings), I find myself wasting time questioning the trivial notion of whether to capitalize or not.I think Groovy's syntax is great, where quotation marks denote an interpolated string, and apostrophes denote a plain old string.
"This is my ${interpolated_value} string"
'This is my plain old string'
But these are nitpicks, and it's really too late to implement something like that.That's Perl's syntax, from about 15 years before Groovy, whippersnapper. Off my lawn, now.
Oh, although I guess Perl got that syntax from the Bourne shell…
Huh, I could have sworn that I remembered that Groovy has taken it from Ruby.
Incidentally, Ruby uses just # for variables, it uses #{} for arbitrary expressions. A common style prefers #{} even where # alone works, though.
An early beta version of Groovy 1.0 had it (thanks to a Sam Pullara), but it was later yanked out. Groovy's self-styled Project Manager at the time said he only wanted syntax in Groovy that would cause the Java syntax highlight rules in Eclipse and Netbeans to highlight Groovy code similar to Java, so if a manager was walking around the programming area, the screens would look like the programmers were using Java.
And that's how Groovy got its deficient string syntax.
>>> 'User {user} has logged in and did an action {action}.'.format(**locals())
'User Jane Doe has logged in and did an action buy.'
I thought it was clever at first, but noticed it was frustrating to refactor because it was hard to see the scope of variables (especially if the template string is defined elsewhere). I don't lean heavily on IDEs, but at the time it would flag many required variables as unused because it couldn't grok this.Do you find the same problems when using f strings?
>>> 'User {user} has logged in and did an action {action}.'.format(**locals())
D:To me, this is pretty gross. This implicitly requires all the correct variables to be bound in the local scope, and there's no easy way to check that this is the case without reading all of the code leading up to this point (which is bad, in my opinion). Because what this really does is create string-level variables `user` and `action` and pass the values of similarly-named (i.e., shadowed) variables in the outer (but still local) scope in their place. This is why IDEs struggle with this: they can't simply know the contents of the dictionary returned by the function `locals()` without actually executing the code (unless they special-case calls to functions named `locals`, but this causes problems if the user ever shadows this name).
F-strings directly use the bound variables in the outer (but still local) scope:
>>> f'User {user} has logged in and did an action {action}.'
'User Jane Done has logged in and did an action buy.'
This avoids the shadowing problem, thus making it easy to use with IDEs and (IMO) easier to grok while reading directly, although maybe most people don't think about shadowing to the same extent I do and wouldn't be as bothered by this in general. Still, the IDE support is incredibly useful.No, you can't define f strings elsewhere, and in theory your IDE can statically analyze them just as the Python interpreter does. In practice, your IDE might suck, I don't know.
>>> f'{5+5=}'
'5+5=10'
>>> foo = 5
>>> f'{foo=}'
'foo=5'
https://docs.python.org/dev/whatsnew/3.8.html#f-strings-now-...
We're not done with Python 2 yet. This would be a Python 4 for sure. That's not according the philosophy of backward compatibility of Python (even if I agree with you).
>>> f'{foo}=' 'foo=5'
print('foo=',foo)
like all languages should, if a pattern is wide-spread, useful and basically boilerplate, python decided to create a language shortcut for it. print('f{foo=}')Far too many tech blog posts use that platform now and I don't like it very at all as it feels really bloated and I can never be sure if what I'm about to click is a 'premium' Medium post or not.
Good blog post by the way.
I found this by searching "modal" on filterlists.com and clicking the subscribe button. IMO there should be a Medium-specific one like they have for stackoverflow or youtube.
I cannot personally vouch if the modal filters are overzealous. I'd rather avoid Medium than putting up with this sort of thing.
[0] https://raw.githubusercontent.com/yourduskquibbles/webannoya...
I only found the "one-click" subscribe for the masterlist in the repo readme. Additionally I couldn't link a specific filterlists.com result to the modal filter "one-click" subscribe that I use.
I'd have preferred a ublock subscribe link but I'm not sure how to make it clickable without an href.
abp:subscribe?location=https%3A%2F%2Fraw.githubusercontent.com%2Fyourduskquibbles%2Fwebannoyances%2Fmaster%2Ffilters%2Fmodal_filters.txt&title=Web%20Annoyances%20Ultralist%20-%20Modal%20Overlay%20Filters* f-strings: https://github.com/asottile/future-fstrings (this is a pretty crazy hack, all the way to Python 2)
* pathlib: https://pypi.org/project/pathlib2/
* typing: https://pypi.org/project/typing/
* enum: https://pypi.org/project/enum34/ (doesn't include any additions in 3.5+)
* functools.lru_cache: https://github.com/jaraco/backports.functools_lru_cache
* dataclasses: https://github.com/ericvsmith/dataclasses (3.6 only, requires ordered class __annotations__)
I'm half-expecting someone to come along with a hacked-up Python 2 backport for implicit namespace packages :)
Something fun to help people feel the "impending doom" and port their projects:
the same author has another library: https://pypi.org/project/aenum/ which includes the latest enum stuff from 3.5 (plus some other things)
Also, it's kind of weird that the article would mention type annotations and then not mention mypy[1].
[0] https://docs.python.org/3/library/types.html?highlight=types...
foo = json.dumps({'a': 1, 'b': {'c': 2}}) # Pretend you've downloaded some JSON...
bar = json.loads(foo, object_hook=lambda x: types.SimpleNamespace(**x))
print(bar.a)
print(bar.b.c)
print(bar.b.d) # Error
bar.b.d = 3
print(bar.b.d) # Works now
I mean, you could pretty much do the same sort of thing with namedtuple, except this is mutable and doesn't require a pre-declaration of the keys. It's easier to write off the cuff.I think Python was probably at a pretty good local sweet spot in the complexity:power ratio around the 2.0 version.
I wonder how many pythonistas are fully across all of the new syntax coming, like positional-only args or even the infamous assignment expression.
Though I'd argue that anybody who adopts the "pythonista" label is likely to be somebody who looks through version release notes for these kinds of things. I certainly do!
It's funny, because I always hear people complaining Python move too slowly to stay modern on one side, and then right after some other people saying it goes too fast.
My take on this is that people just like to complain.
Now I agree, if I could snap my gantlet I would remove a lot more. We don't need Template(), the wave module or @static and so many other things. And we could have cleaned up Python more deeply in the transition.
But reality is very messy.
Plus, you gotta realize that the Python community has nowhere near the resources of languages such as Javascript. JS is developed by giants that poured hundreds of millions in it and set dozens of geniuses to work solely on improving the implementations.
Python barely (and this is recent, we had less before!) has a few millions for operating the PSF related activities, which includes organizing PyCons all over the world and hosting python.org or pypi.org. Only a handful of people are actually paid to work on Python.
So you have a giant, diverse community, with billions of lines of code for so many different activities, and not enough resources to work on cleaning up everything.
Welcome to life, this stuff where if you want to get things done, you have to compromise.
In an effort to avoid the noise, would you be so kind as to point me in the direction of the current way to handle packaging in python? I don't work in Python consistently enough to be up to date on this, but every time I do it's hard for me to suss out the best way to package up and distribute the work for internal end users.
The least worst is https://packaging.python.org/ but it skip major issues I know beginners have (multiple installed Python, path problems, etc), it's not clear what works on what OS. It also promotes pyproject.toml over setup.cfg, and ignore pex and nuikta.
That's one of the numerous short pocket books I should write.
Give me a book deal, I'll do it :)
Also,
> not because it's hard, it's really not
and then
> Give me a book deal, I'll do it :)
gave me chuckle. Because they're both true: it's not necessarily hard, and yet despite that it still needs a short book to really tackle the subject. That's such an out of place state for something related to a core component of Python.
Don't think the same is true for python (yet?)
It will virtually merge dirs with the same name under some circonstances, and won't be importable in some others. You also lose the benefit of having an init file in which you explicitly setup the object you expose for import.
Also pathlib is .. urgh. This is one of those that shouldn't be in the stdlib. I don't get why it is, but requests isn't.
import('/modules/mon-module.js') SyntaxError: dynamic module import is not implemented
So no network access, and of course no FS access.
Hence, doesn't work in the browser. You need a lib or a bundler.
Source code and other examples are at: https://github.com/mdn/js-examples/tree/master/modules
In what way is python importing less powerful than JS? My experience is very much the opposite (to the extent that JS even has an import system).
> I don't get why it is, but requests isn't.
Because pathlib was proposed (PEP 428), considered useful and self-contained.
The requests project does not want it to be included (this was discussed at length back in 2015), and it would require merging chardet and urllib3 into the stdlib first.
And yes I'm aware why requests isn't in the stdlib, but that doesn't make it "correct". And chardet should definitely be in the stdlib. I mean, ffs, `mimetypes` is in the stdlib. urllib3 is more contentious but honestly? It probably should be as well.
@lru_cache
def slow():
return {"result": "immutable?}
answer = slow()
answer["result"] = "no"
print(slow())f-strings are mostly unusable for translatable contexts. And format-strings also don't work then (they're a security issue).
Background: turns out that the string prefix was the only ASCII syntax left in Python3, that's why it was chosen. The name "eff string" makes me cringe, but once it stuck there was no stopping it.
That explains why you're all over this post ;)
Fantastic feature, though. Really. Thanks!
Out of curiosity, what name would you have preferred over "f-strings"? "Format strings"? "Interpolation strings"? "I-will-evaluate-expressions-in-curly-braces strings"?
What to call it? Anything I guess. String interpolation is what it is called elsewhere. But the f prefix is what everyone sees, though as mentioned, it is only an implementation detail because other forms of syntax were already spoken for. I personally chose f for "format."
Early on Guido changed the scope to include expressions as well. People were already starting to say "eff string" so I changed my proposal to "e-string" for "expression." I also like the sound of it better, sounds like email, etc. But Guido (and Eric) decided to stick with f. Maybe because Guido learned English later he doesn't realize how unfortunate it sounds. But, years later we use the "iPad" and forget it sounds like a feminine napkin. So, no big deal in the end.
I miss it in a Python 2.7 project I am on right now.
Link to the docs: https://docs.python.org/3/library/functools.html#functools.s...
It's the same concept as clojure's defmethod.
What happened to "explicit is better than implicit"? "Only one way to do stuff"? One of the more baffling Python features if you ask me.
> name = ‘Jane’
> print(f”Hello, {name}”)
and PHP’s:
> $name = “Jane”;
> echo “Hello, $name”;
> name = 'Jane'
> print 'Hello, %s' % (name)
but I can't exactly reproduce all the discussions from back then. As for me personally I can see that I dislike the new
> print(f'Hello {name}')
format because my editor cannot instantly show me that {name} is in a fact a variable, and now that I wrote this down I remember that this was one of the complaints made against PHP's use of "Hello, $name" . But maybe newer editors do in fact recognize {name} as a Python variable, in any case it looks and feels "impure", for a lack of a better word.
PHP:
print("Hello, $name")
print("Hello, ${getName()})
Python: print(f"Hello, {name}")
C# Console.WriteLine($"Hello, {name}")
Javascript Console.Log(`Hello, ${name}`)
Ruby puts "Hello, #{name}"
etc.However I think it's fair to credit PHP with introducing generations of programers to the idea.
I'm reasonably sure that earlier versions of sh had some facility for this as well.
So, it's not really a PHP innovation, although some people might have seen it first there.
[1] https://www.in-ulm.de/~mascheck/bourne/v7/ -> "Parameter substitution"
In the 1970s string interpolation really got going with languages like sh, cpp, and m4, which was popularized on many platforms by the "Software Tools" book (which in a sense is a slow buildup to the presentation of m4); m4 is Turing-complete entirely through string interpolation (more so even than Tcl decades later).
I suspect Perl had string interpolation from the beginning (1988), given its shell roots, but I don't have a copy of Perl 1 to test with.
For an overview of modern languages' string-interpolation syntax, I suggest https://www.rosettacode.org/wiki/String_interpolation_%28inc....
Hope this helps!
The gist is you get a single interface for any type of threadlocal storage that also works with coroutines (asyncio). I use it pretty consistently for context logging of long running processes in our systems. However it has implications on other concurrency paradigms usages as well.
2. pathlib is cool. will come pretty handy.
3. Data classes sounds good but there should be only one way to do things, even you end up writing few more lines.
4. type-hints are ok for very very big projects.
5. No comment on implicit-namespace-packages yet as still trying to understand solid use-case.
Dataclasses greatly simplify generation of simple classes which are unnecessarily filled with boilerplate. I'd argue that they are the one way to do this, and that choosing to manually implement the same naive `__init__` function for every simple class you write is the "wrong" way.
(Here I take "simple class" to mean a class which takes in some values during initialization and stores them without anything terribly interesting going on during that initialization. A simple class can have methods defined on it, though.)
> type-hints are ok for very very big projects.
D:
> No comment on implicit-namespace-packages yet as still trying to understand solid use-case.
I think it's just... trying to keep your directories "clean"? I also don't really understand the use of this haha.
I would love to solve these boilerplate kinds of problems using plugging like emmets. Magic like this feels good when writing but troubles during debugging and customizing.
Additionally naive programmers always use such features in wrong situation if they don't get use-case. I really feel sad after seeing so much bad-django-code in production.
Great for smaller ones too! I type annotate every function, even
def parse_args() -> argparse.Namespace:
pass
at the top of simple scripts. With a good IDE (VS Code fits here too), the extra language lookups/insight you get are awesome. Command+mouse_hover gives me a great bit of information about args passed into the function if I annotate the inputs.I agree that statically typed languages are preferable. I don't consider static typing optional so I'm glad Python is growing a solution that supports it, though.
In my experience, mypy works surprisingly well. It doesn't hurt my coding speed at all but enhances it to levels that were not previously possible in Python, due to a lack of typing. False positives are rather rare.
the zen of python never mentioned that you should exclusively code in machine code.
with importlib.resources.open_binary('my.package', 'foo.png') as f: ...
Avoids the overhead of importing pkg_resources, and hassle of having to call cleanup_resources before the interpreter shuts down. Backports are available for Python <= 3.6 too!
That being said I usually end up rolling my own that can serialize complex objects.
It didn't last time I googled it (because it didn't exist then), so I had no idea it existed.
For example, I just learned about f strings, because why would I need to ever look up if there is a replacement for .format()?
And the LRU cache. I've been hand rolling that for years, but I never thought to see if they had added it to the standard library, because why would I look?
I hand rolled stuff in itertools/functools for years before discovering them.... collections Counter, etc.
Or how about str.partition instead of try: str.split() ?
> classes don't need (object) anymore
My friend, old-style classes became unnecessary with the release of Python 2.2 — almost 20 years ago now [0]. I'm afraid you've been living in the past. :)
Probably would've helped if I actually read the page I linked...
Python 2.7.15 (default, Jun 27 2018, 13:05:28)
[GCC 8.1.1 20180531] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo:
... pass
...
>>> Foo
<class __main__.Foo at 0x6218e384ee88>
>>> class Bar(object):
... pass
...
>>> Bar
<class '__main__.Bar'>
>>> isinstance(Bar, type)
True
>>> isinstance(Foo, type)
FalseWhat's a case where I'll have a problem if I don't use this decorator? (I've used the Enum class a bit since it became available, but not in any particularly interesting implementations so maybe that's why I've never heard of this.)
In [21]: @enum.unique
...: class Animal(enum.Enum):
...: DOG = 1
...: CAT = 2
...: PIG = 2
...:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-21-cbc1625bb41c> in <module>
1 @enum.unique
----> 2 class Animal(enum.Enum):
3 DOG = 1
4 CAT = 2
5 PIG = 2
~/.virtualenvs/py3/lib/python3.6/enum.py in unique(enumeration)
834 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
835 raise ValueError('duplicate values found in %r: %s' %
--> 836 (enumeration, alias_details))
837 return enumeration
838
ValueError: duplicate values found in <enum 'Animal'>: PIG -> CAT
It's intended to catch programmer errors - not really useful for small enums like this one, but in a large enum, having duplicates could be difficult to notice, and might cause some insidious bugs.So this is mitigated by using `enum.auto` for setting the values. But I can definitely see how `enum.unique` would be useful if your enums had more specific values.
Thanks for the explanation!
If you are explicitly setting enum values, it causes accidental aliases (duplicate values) to be an error early rather than a source of (potentially subtle) problems later.
It also specifically communicates intent to other readers of the code, which can be just as important if your code has a lot of enums,some of which do use aliasing intentionally.
It even works on Windows.
* generators
* defaultdict
* generators
* setsThey were there in 2.X version like 9-10 years ago when I first used Python anyway and aren't 3 specific.
Built-in set objects were added in Python 2.4: https://docs.python.org/2/whatsnew/2.4.html#pep-218-built-in...
defaultdict was added in Python 2.5: https://docs.python.org/2/library/collections.html#collectio...
For CPU intensive tasks, asyncio actually provides it's own syntax for process pools which are easily interchangeable with thread pools (just change an import alias)
But seriously f-strings? people have been talking about these for years now, it's not new is it?
All the other features mentioned in the article, except data classes, are older than that.
Ah, the classic "nothing to see here, keep moving on" argument. The lack of admission of a bad idea is itself a problem. What happens in a few years when Python 4 renders all Python 3 code incompatible?
Ah, the classic "they did it once so they'll definitely do it every single time from now on" argument.
Nick Coghlan, one of the Python core developers, said "Python 4.0 will merely be "the release that comes after Python 3.9"" [0], so I think your concerns are ill-based.
[0] https://www.curiousefficiency.org/posts/2014/08/python-4000....
The people who are not "sore in the slightest" probably don't have to deal with large Python 2 codebases that rely on libraries that have no plan to move to Python 3. They are typically people who are writing code from scratch.
Rendering a huge codebase obsolete in order to upgrade print statements, import statements, and internationalization (i.e., unicode) was not a fair trade for many existing projects.
My stuff was not overly concerned with encodings, that part was luck. I moved to the logging module early, avoiding print problems, that was smart. Other changes were trivial mechanical fixes, that was easy.
For the unlucky, obtuse, or resource challenged, you've had an extra ten years of support. That's sufficient IMHO. It is time to suck it up and port, or retire the app. Constant bitching just gets old.
Meanwhile, I'm using a great language that will be relevant for years.
Why would they say that? They know breaking compatibility was a huge blunder, but they ignore the folks that it affected. Python 3 promoters are typically folks that don't have to deal with mess it created. If the Python foundation actually admitted to their mistake, it would engender far more confidence and credibility than pretending it does not exist.
> the mess they made with Python 3
What mess, exactly?
> what was an originally bad idea.
What makes Python 3 a "bad idea"?
Breaking compatibility with a huge code-base for incremental features.
People have had plenty of time to work on porting their code from 2.x to 3.x. Honestly, I think it's remarkable the devs kept maintaining 2.x as long as they did. I mean, look at Swift, where they had breaking changes year after year, and yet their user base has fairly exploded since the language's release not so long ago.
Just... get on with it, you know? Move to Python 3.x and be done with it. There's no particularly good reason to stick to 2.x forever.
I'm not particularly excited about a lot of other new features though.
I don't see a real reason for type annotations from my perspective (if I wanted typed I'd use a typed language, if I wanted types indicated I'd indicate in the doc strings).
Also f-strings which I haven't used it. They probably are better, but I'm barely getting used to `format` as opposed to `%s` (which admittedly `%s` does suck as I've said many time while counting items on the screen with a pencil).
I definitely get this perspective, but I actually use type annotations a lot. I do the vast majority of my Python development in PyCharm, and using type annotations greatly facilitates auto-complete features. It also helps me do some very simple debugging while writing, as the IDE will tell me "Hey! I don't think these types match up!", which is often enough to save me the headache of debugging.
Realistically, I just want Python but with a static type system. Which is why I'm implementing that as my own project haha.
> Also f-strings which I haven't used it.
Oh, dude, you gotta get on board with f-strings. They're soooo much simpler than the alternatives. Just skip learning `.format` and go straight to f-strings. I moved to f-strings when they first became available a few years ago and haven't looked back since.
Dataclasses are also great, IMO. I prefer algebraic data types in functional languages like Haskell or OCaml, but I think Python's dataclasses are a "good enough" solution for me to use in the meantime. I often define very simple data types and hate the default `__repr__` implementations and writing naive `__init__` functions. Dataclasses make all this much faster and, I think, more readable.
The unicode improvements in Python 3 are definitely a step in the right direction. But there were certainly good ways of improving unicode issues without breaking compatibility. Things like print are a joke, no one should care about its semantics.
> Just... get on with it, you know? Move to Python 3.x and be done with it.
Pretty easy to say. But try convincing product managers to take 5 devs off their current projects for 6 months to port a working production app to a newer programming language, with little concrete evidence that it will have a meaningful impact on the project (other than introducing bugs).
If you're interested it's worth a watch: https://www.youtube.com/watch?v=Oiw23yfqQy8
He was wrong. It is far harder than he anticipated, and it did not "need" to happen to grow the language.