>>> print "Hi, my name is Łukasz Langa.".lower()
hi, my name is Łukasz langa.
Whereas on Python 3: >>> "Hi, my name is Łukasz Langa.".lower()
'hi, my name is łukasz langa.'
Or even more basically, Python 2: >>> len("Hi, my name is Łukasz Langa.")
29
Python 3: >>> len("Hi, my name is Łukasz Langa.")
28
Sure, there's hard things about code points and graphemes that Python 3 also doesn't touch, like the example you gave (originally: characters outside of the BMP using surrogate pairs, later: using combining characters), or equality (where normalization might be needed to compare two strings that look alike), or sorting (where collation rules mean the same code points might sort differently depending on the language used). But there's tools to deal with those tricky situations and the default is much more robust.And sure, you can use the `unicode()` type in Python 2 to get the correct results but the problem is that most APIs will work with both types and silently do the wrong thing every now and then. Those include UnicodeDecodeErrors and UnicodeEncodeErrors that you get sometimes. In Python 3, without the magic type promotion, you'll get a sane TypeError every time.
Disclaimer: the name used in the example is mine.