The official Python implementation is dogshit slow; although you can argue that one can use implementations like PyPy, which is mostly a drop-in replacement with significantly better performance way better performance. Regardless, the few times I actually use Python (for e.g. quick and dirty data processing / visualization), there's is a surprising amount of friction and pitfalls despite it being recommended for beginners.
One of my biggest issues is the complete lack of type safety. I get why people might not want to worry about types, but for any project with >10 lines it's a pain in the ass. What is usually a quick glance at the variable definition becomes a tedious search for the type one is working with. Certain refactorings which are trivial in other languages where you can be certain that shit works if the compiler doesn't complain becomes a minefield where you have to test every single modified codepath for runtime errors.
To counter this problem, one can either use an excessive amount of comments or encode the type within the variable name. However, both of these approaches need to be maintained "manually", as Python itself simply does not care. Another """solution""" is to use "type hints", a feature which is so utterly bizarre and useless to me:
def testFunc(name: str) -> str:
print(name)
return 5
testFunc(42)
This feature provides the ability to annotate the type of
function parameters and return values. Now, you might think:
"Wait a moment, the types of this code snippet don't match at all.
Is there some type conversion happening?".
No. Not at all. The Python runtime completely ignores
these type annotations. The provided code snippet runs
without any errors or warnings. The official documentation
notes that they "can be used by third party tools", meaning
these are just glorified and standardized comments.
Requiring third party tools to make
certain language features even remotely useful
is certainly an interesting strategy.
How would you round 1.5
? What about 2.5
?
This might be a cultural thing, but Python's answer
completely threw me off:
round(0.5) # 0
round(1.5) # 2
round(2.5) # 2
round(3.5) # 4
The concept of "Banker's rounding" was
completely new to me, where people round halfway numbers to the nearest even number.
With experience in programming this one isn't too bad,
but beginner's which were not exposed to various rounding
methods would probably not expect
their bug to because round()
doesn't work as they expected.
The rational behind globals in Python makes sense if you don't care about shadowing variables. But for small scripts where you don't want to pass variables to every single function, this behaviour is a nasty one:
var = 5 # Global variable
def printGlobal():
print(var)
def modifyGlobal(value):
var = value
printGlobal() # 5
modifyGlobal(42)
printGlobal() # Still 5 :)
Python decided it would be a great idea
that global variables can be read from without issue,
but writing to them creates a local variable which shadows the global one.
If you want to modify the global variable, you have to write the function as follows:
def modifyGlobal(value):
global var
var = value
Writing global ...
in every single function where you want to modify
the global variable is incredibly tedious and error prone.
I suppose this is Python's way of discouraging the use of global
variables, but honestly: Fuck off.
I have to at least give them credit that runtime checks prevent accessing and editing global variables within the same function:
var = 5
def printAndModGlobal(value):
print(var)
var = value
printAndModGlobal(90) # "UnboundLocalError"
Some Python syntax choices really throw me off.
The global variable thing has burnt me often enough
that I know that I need to include the global
keyword somewhere.
A good friend of mine has recently made the following innocent mistake:
global var = 5
def modifyGlobal(value):
var = value
The global keyword in this context is literally useless,
it has no effect outside of a function.
modifyGlobal
still creates a local variable
which shadows the global. I'm confused why
you'd even allow the keyword to be used that way (maybe grep
-ability?)
as it just incudes a false sense of security ("no error,
that must have worked :)").
Other times, Python can be very concise, most of the
time to the detriment of readability, e.g.
return ... if ... else ...
.
But I can choose to simply not write my programs that way.
Some syntax choices such as //
as the integer division operator
or not supporting the logical operators &&
/ ||
/
!
in favor of and
/ or
/ not
feels very clumsy to me, but
that is probably just me not getting used to the language.
I get that you don't want to duplicate operators,
but then again, both ==
and and
exist,
which have a different runtime speed for some reason:
def testVariable(var):
if var == None:
print("Blub")
for i in range(10000000):
testVariable(5)
> time python ./python.py
________________________________________________________
Executed in 641.75 millis fish external
usr time 641.24 millis 167.00 micros 641.07 millis
sys time 0.01 millis 15.00 micros 0.00 millis
========================================================
========================================================
def testVariable(var):
if var is None:
print("Blub")
for i in range(10000000):
testVariable(5)
> time python ./python.py
________________________________________________________
Executed in 529.14 millis fish external
usr time 525.52 millis 0.00 micros 525.52 millis
sys time 3.19 millis 197.00 micros 2.99 millis
Or the fun lesson that Python uses "decorators to make class functions static", but if you try to save yourself writing a constructor you create static class members by accident:
class a:
var = 5 # Static member variable
class b:
def __init__(self):
self.variable = 5 # Non-static member variable
Python's construction of a class is fucky in general.
When calling a member function, the first parameter is implicitly the
class instance, so you get to write func(self, ...)
for every member function.
Then, it has very fucky access """restrictions""" on class functions - which don't really
restrict the access at all:
class A:
def __init__(self):
pass
# Public function
def funcA(self):
print("A")
# Public function, "but pls dont use" by convention
def _funcB(self):
print("B")
# """private""" function,
def __funcC(self):
print("C")
# Public function calling the private function
def funcD(self):
self.__funcC()
i = A()
i.funcA() # A
i._funcB() # B
#i.__funcC() # AttributeError
i.funcD() # C
i._A__funcC() # C
But out of all the random tricks, I cannot believe that Python does not support breaking out of nested loops. I'm serious. Online workarounds suggest putting the loops into a function you can return from or throwing and catching an exception instead. Wowie. It gets even worse though: There was a proposal for labeled break and continue which was rejected. This is already sad enough, but take a look at the given rational, which claims that
This straight up feels like gaslighting.