I didn't use Python much, just one smaller project ( more about it ) and assignments for second part of Stanford's algorithms class on Coursera. Not even that: when the push came to shove at TSP size 25, I had to resort to more familiar C++, which turned out to be significantly faster for this kind of programming. Minutes versus hours. I like the language, especially it's dynamic declarations of everything. C and C++ have limited notion of functions as first-class citizens and stuff like that was pretty new to me. It was interesting to me, how I couldn't seem to find good use for Python features, that are unavailable in C++, where I come from :) This is how you know you're thinking in-the-box. At the same time, this is why it is useful to learn another language. I wanted to learn basics of Python and I did. I must repeat the disclaimer for all Python fans out there: I like it :) This is merely a list of things that I had problems with as a newbie in the language.
1) It's cool and all to define sub-blocks of code with indentation, but it's inconvenient at times. For example, in production code you want to use try/except to gracefully manage problems, but when debugging you want it to fail with an error message. Every time I commented out try keyword, I had to correct indentation of whole block of code, to match the program without try. I started using if 1 == 1 in such cases, just so that indentation could stay.
2) Why do I need to include self in class' members arguments list? It is evident that I want to use class' data in it's member functions. Coupling data with operations is one of the main features of OO. You might ask, how could it be possible, considering Python's dynamic nature, to assign member function to some other, possibly unrelated context, and calling it from there, if there were no self in function signature? That is a good quesion, but think about it. If I already have an object instantiated from this class, why not calling the member function directly on itself? And if I don't have it, I can't call the function anyway, because I don't have all the required function arguments.
Furthermore, in function body I can only access class member through this self variable. That could also be the default for class members. It seems unnecessary to reference each one with self.member, instead of just referring to them as member. Python interpreter could use scope as a hint about which variable did programmer want to use. Maybe it's for readability, but different teams might feel different about what is more and what is less readable.
3) Why is UTF-8 not the default codeset in a language, which is famous for it's text manipulation functionality? Specifying "utf8" on each and every IO operation seems redundant. It's also ASCII compatible.
4) Functions' arguments are all by reference, even if you don't want to. Except when they are not. Strings, for instance, cannot be passed by reference for no immediately obvious reason. Moreover, the very terminology of "by reference" and "by value" is inappropriate here. Generally, if you want to pass argument by value, which is what you often want, you will have problems. And if you come from byref/byval world, you will first have problems even understanding the behaviour.
5) Why is ":" symbol necessary at the end of branching clauses? All is already said with indentations. Is it just for bothering me with errors when I forget it? I'v seen some suggestions it's for editors, so that they can easier recognize when they need to suggest indentation to programmer, but I believe this is more of an IDE problem, not so much the problem of the language.
6) It is valid grammar to use "not", "in", "not in", or "is", but you can't use "not is". Am I forever ruined from that little Visual Basic experience I have from when I was young and experimenting in college, or is that grammar inconsistancy in Python?
7) Built-in constants begin with big letters, while all reserved words begin with small letters. Wouldn't it be more consistent if all were completely small-caps?
8) Classes cannot contain private members and Python is missing some useful object-oriented features because of that. Like encapsulation by hiding information and preventing access. Improving the quality of large legacy codebase by incrementally enforcing encapsulation comes to my mind.
9) Inheritance is limited to using virtual functions only. I therefore can't rely on a fact, that a class member function called from within this class, will remain intact after someone inherits my class. I'd need to use specific naming options to try to avoid making these types of mistakes, but I can't prevent them.
10) If I take the following two valid options for declaring two-dimensional array:
array = [[0 for x in range(3)] for y in range(3)] #option a
array = [*(3),]*3 #option b
then try running the following test code:
print(repr(array)) array = 1 print(repr(array))
In case array was declared as option a, then the test script outputs expected:
[[0, 0, 0], [0, 0, 0], [0, 0, 0]] [[0, 1, 0], [0, 0, 0], [0, 0, 0]]
In case array was declared as option b, it outputs:
[[0, 0, 0], [0, 0, 0], [0, 0, 0]] [[0, 1, 0], [0, 1, 0], [0, 1, 0]]
The case b obviously used the same object in each array in the second dimension, but not in first in spite the fact that both were created using *3. I didn't even research why is that so or what purpose could fake 2D array possibly serve. There are a lot behind the scenes stuff you need to understand when programming in Python.
11) It is famous for it's platform independence, but it isn't completely. For instance, signal.alarm(time) function is only available on Unix systems. Without this operation on Windows, you're out of luck if you want to set some function to stop executing after certain amount of time, as I needed.
12) The language is changing from version to version, but backward compatibility isn't guaranteed. I worked with Python 3.3 interpreter. Many examples on the internet for older versions didn't work, a lot of forum advice and 3rd party documentation wasn't applicable anymore. Even for fairly recent releases, like 2.7. How do they maintain large projects in Python? Do they rewrite large parts of code for each new released version of interpreter? For instance: sys.maxint doesn't exist anymore in 3.3 version. Granted, this is easily replaceable with sys.maxsize, but I take into account the limited experience I have with Python, so who knows what surprises it hides when you really dive into it. Another example: I needed BeautifulSoup library, but it didn't work in 3.3, unless it was fixed first. If it wasn't for Vladislav's fix I'd need to spend much more time on the project.