The experience has been enjoyable, but there have been dubious aspects to it.
Definitely on the good side is the inclusion of lists, dictionaries, and tuples as first-class constructs within the language. It is an unalloyed joy to have these constructs available to the programmer as built-in, highly capable features of the language. I can't say enough good things about them. In the last few years I've become a great fan of maps/dictionaries as a fundamental approach to solving a variety of software development issues, and Python's comprehensive support for these and related constructs simplifies so many things.
List comprehensions are another valuable member of the Python toolkit. As I came up to speed with them I was reminded of Fortran's "Implied Do Loop" that I learned about way back in my college computer science classes.
List comprehensions are certainly a very powerful language feature for constructing kernels of concise, effective code.
The personal project I chose to implement using Python was a configuration file builder for which I wanted a graphical user interface. Having a fair amount of past experience with GTK+, I decided to go with PyGTK. This worked out very well. The integration of GTK with Python is extremely well-done, very flexible and easy to use. Many of the issues I had to deal with in the past regarding transferring information between the core app and the GTK API pertaining to data formatting and management just aren't there. Definite kudos to the PyGTK team. So Python and (Py)GTK is a Big Win in my book.
Now although this is rather a minor feature of Python, I've found the ability to return multiple results from a function extraordinarily valuable as it eliminates the need to define and manage parameters that are used for nothing more than passing back two or more results that are produced within a method.
I've done very little with the functional programming features of Python, other than lifting some quick little functions from tutorials or examples that I've needed to do some special-purpose task, like simple filtering. I know the functional programming features are very powerful, and from reading up on them I can certainly see their use, so I look forward to investigating exploiting them in the near future.
Now here's the part where I've yet to see the value.
AIEIEE!! Not the static vs dynamic typing argument!!
I won't get into a full-blown argument, just going to point out where this keeps biting me, which causes me to question whether its value is worth the cost. (And keep in mind that I come from a hard-core strong/static typing background--Ada--so that obviously distorts my perspective :-)
The benefits of dynamic typing seem to revolve around the programmer not having to worry about types, therefore compilation and link errors are reduced so one can get to executing code faster, that when there's a bug manifesting itself during run-time there's more info available for debugging, it's easier to change types to adapt to changing requirements, and you get things like late binding and duck typing.
Okay, those are all true, but a) my code blows up a lot more often, and b) with no explicit type information in the code finding out why it's blowing up takes time, where with static typing the damn thing wouldn't have compiled in the first place--and therefore not have blowed up real good at all.
Here's a method spec:
def setupEventDefinitionsList(self, availableLogEntries,
eventPrereqEvent, eventEntries, eventNames):
Some of these parameters are dictionaries, some are lists, some are class instances. Which are which? If I change the implementation of this method such that, say, eventLogEntries, is now a dictionary, I won't know that I possibly missed making the corresponding adjustment to the argument in an invoking method until I actually execute that method. Which, if the caller is in a little-used part of the application, could be a long time coming.
Similarly, if I haven't looked at this code for six months, how do I quickly refresh myself with what each of these parameters is, and is for? Maybe I was a conscientious programmer and documented each parameter, but maybe not (and comments tend to rot anyway). If in a new app I want to call this function I can't just look at the signature and tell in what form I need to provide the arguments, I have to review the code itself, either in the body of the method or in others callers, so as to derive what type of argument is expected.
Versus putting my cursor on the type indicator and right-clicking "Show declaration".
(Format_Kind : File_Format.File_Format_Kinds;
Filename : String;
Timestamp : Timestamps.Time_Formats;
Timestamp_Kind : Timestamps.Time_Kinds;
Supplementary_Data : Supplementary_Generator_Info)
Maybe it's simply a matter that I'm not yet thinking "Pythonic" enough, and that with more experience in the language the benefits of dynamic typing will become more obvious to me. Maybe I've not yet immersed myself deeply enough in the language that dynamic typing reveals itself as being critical to certain software development practices. I don't know, for me the jury is still out on this aspect of Python, and so most likely for the rest of the dynamically typed languages.
There's certainly enough clearly beneficial strengths to Python that I will continue to use it for various projects, as I mentioned above it's been very pleasant to use for creating my configuration file builder.
For the really hard-core, more expansive projects though, I'm going to be sticking with Ada or other strongly/statically typed languages (yes, not least because I know them very well) because I find tracking down type-mismatch problems occurring during run-time that could've been trivially caught at compile time very tedious.