We all love integer-based milestones and celebrating them. We brave the cold to meet in city squares and count down to the new year every December 31st, we throw parties to celebrate when we turn another year old, and we all remember where we were and what we did during that amazing cross-over for the year 2000.

Let's take a moment and celebrate a fun milestone for Python.

Python has gone through changes, like all long-running technologies. It was released as v1 back in 1991. Minor improvements were made via Python 2 in 2000. By the mid-2000s, the core developers realized that Python had acquired several WATs (but not nearly as many as JavaScript, see https://www.destroyallsoftware.com/talks/wat). They began to think about introducing a few breaking changes into the language to address and remove these warts or WATs.

In the Python ecosystem, the language evolves out in public using what are known as PEPs or Python Enhancement Proposals (see https://www.python.org/dev/peps/). This new but slightly incompatible version of Python was proposed officially via PEP 3000 (https://www.python.org/dev/peps/pep-3000/) and went under the working name Python 3000. Work was completed and Python 3 released on December 8th, 2008. Now we're up to version 3.6.1.

This brings me to a little bit of Python code. Don't worry if you don't know Python well; I'm sure you can follow along. That's one of the benefits of Python: It's easy to read and learn.

FIGURE 1: How about that? Python 3000 is exactly 3000 days old!
FIGURE 1: How about that? Python 3000 is exactly 3000 days old!

Let's take a moment and look at this amazing language and version of Python. I know many readers are .NET developers who'll find the cross-pollination between C# and Python 3 intriguing. Even if you're not a Python developer, you'll find many amazing language features.

Container Iteration and Custom Type Iteration

C# and VB.NET have avoided a huge class of bugs by making the foreach loop the primary type of iteration, and so has Python. In fact, Python had this type of iteration way before C# came onto the scene. Check this out:

data = [1,1,2,3,5,8,13,21]
for f in data:
    print("Next fib is {}".format(f))

You can even do this by implementing a custom iteration. Just add an __iter__ method.

class MyType:
    data = [1,1,2,3,5,8,13,21]
    def __iter__(self):
        return data.__iter__()

object = MyType()
for f in object:
    print("Next fib is {}".format(f))

Tuple Tricks and Language Enhances

Tuples are just now making their way into the .NET world (although F# has had them for a while). Python has had tuples for decades and they enable some cool language features.

Here's a tuple representing a measurement:

m = (1.2, 2.5, 70, 'rapid')
x = m[0]
y = m[1]
# etc...

You can use this data type in interesting ways. For instance, you can unpack it into its constituent values directly, like this:

x, y, percent, mode =  m
# x is now 1.2, y is 2.5, etc

You can even do this partially with "_":

_, _, percent, mode =  m
# percent = 70, mode = rapid

Great. Now let's use this in a function and iteration.

If you have a function that returns a measurement tuple, you can give the appearance of multiple return values. It's just clever tuple unpacking.

x, y, per, mode = get_closest_measuremet()
# defines x, y, per, mode, assigns values.

And similarly, you can do this via iteration with a for loop with accompanying index:

for idx, val in enumerate(data):
    print('{}th fibonacci value: {}'.format(idx, val))

Here, you're using tuple unpacking on the return value of enumerate to initialize idx and val. Aren't tuples great?

Yield and Yield From

One of the most under sold/used features from C# is yield return. Python had this cool feature years before C# (see https://www.python.org/dev/peps/pep-0255/) and it's even cooler in Python 3. Let's see why.

You often work with sequences and series in your code. How does that look in Python?

collection = build_collection()
for thing in collection:
    work_with(thing)

Now, how does build collection work in Python? Usually it builds everything into a list of some sort. This could be returning data from a database, reading it from a file, or computing it as you go. The details don't matter. You'll be waiting until build_collection() returns and then you'll have the data in memory (all of it, whether it's 10 items or 10,000,000 items).

Using what's called a generator, you can introduce deferred execution for your application. Here's how that looks in code:

collection = build_collection()
for thing in collection:
    work_with(thing)

Yes, it's identical. But the execution is anything but the same.

Instead of blocking, build_collection() immediately returns a generator capable of generating the next item as a request (by the for loop in this case). Processing the loop results in only one thing in memory even if there are millions being processed.

The magic is in build_collection() in this generator case:

def build_collection():
    for line in file:
        yield parse_line(line)

That's crazy easy, right? In fact, the generator version is often simpler to write than the direct style, thanks to the yield keyword. But wait, there's more!

If you're using recursion (which is common any time you're processing a hierarchical data structure, like a file system), Python has one more feature to help out: yield from. Have a look:

def find_files(dir):

    for file in list_local_files(dir):
        yield parse_line(file)
    for subdir in get_dirs(dir)
        # no need to iterate and yield...
        # generators converted directly
        # via yield from
        yield from find_files(subdir)

Now that's taking yield and generators to a their full potential with yield, yield from, and the recursive call to find_files().

Keyword Arguments on Functions and Methods

Python functions have a wide variety of features and ways to describe their parameters. For example, you can pass data to functions using standard positional parameters, optional parameters (via default values), additional parameters, and keyword parameters.

Python functions and methods don't have method overloading but you'll rarely miss it because of the flexibility here.

Let's focus on keyword arguments. Let's look at this function that saves the data structure:

def save_profile(profile_data,
                filename, format,
                **additional_data):
    if format == Formats.JSON:
        save_json(filename, additional_data)
    elif format == Formats.CSV:
        save_csv(filename, additional_data)

That's probably straightforward except for that additional_data in the second line. The asterisks on the parameter are additional keyword arguments and they're passed to the method as a dictionary (of string, object).

Here's one way to call it:

save_profile(my_data, 'prof.json', Formats.JSON,
             indent=True,
             strip_comments=False)

Pretty cool, huh? You can add additional named parameters that (in this example) flow through to the underlying format (the save_json() method). Here, indent and strip_comments() are not part of the method's parameters but are sent along to save_json().

Dictionary Creation/Keyword Arguments **dict

Dictionaries are central to many things in Python. Let's see a few more cool language features following on the optional keyword argument example.

You create dictionaries somewhat like you create JavaScript object literals.

profile = {
    'name': 'Michael Kennedy',
    'email': 'michael@talkpython.fm'
}

Python 3.5 added some very interesting language features for building new dictionaries out of existing ones. Imagine that you're working in a Web app and are provided with a couple of dictionaries as part of a request (let's call them routing, post, and get for the three sources of data).

You could build a common dictionary with a unified view of all data passed into a single source using an adaptation of the ** modifier:

get = ... # Some dict
post = ... # Some dict
routing = ... # Some dict

unified = {**get, **post, **routing}

# unified is now a dictionary with all the
# entries merged, later ones overwrite previous
# ones (e.g., post has precedent over get)

You can also use this cool language feature to convert a dictionary into keyword arguments (required or optional).

Remember the method call with named parameters from earlier:

save_profile(my_data, 'p.json', Formats.JSON,
             indent=True,
             strip_comments=False)

If you had the data in a dictionary, you could do the exact same thing very concisely. Here, **data converts a dictionary into a keyword argument call:

data = {'indent'=True, 'strip_comments'=False}
save_profile(my_data, 'p.json', Formats.JSON, **data)

The async/await Features

In a hat-tip to C#'s excellent threading model, a recent release of Python (3.5) introduced the concept of async and await for blocking class:

async def ping_local():
    return await ping_server('192.168.1.1')

If you don't know this language feature (in either Python or C#), do yourself a favor and dig into it.

Don't Know Python? Should You Learn It?

You may be thinking that all this is interesting. But perhaps you don't know Python and you already have enough to keep up with. Let's pause for just a moment to highlight an opportunity for you.

You should learn Python.

  • Python is widely used in Data Science (a growing, high-paying field).
  • Python pays well (as high as $116k/year, on average in the US).
  • Demand for Python developers is high (and growing) because of the short syntax and multiple utility libraries.
  • Python saves time.
  • Python is beginner-friendly but is still useful enough for more skilled developers.
  • Many big-names companies use Python (YouTube, Pinterest, and Instagram run on Python).
  • Python has an amazing ecosystem, including a great standard library with built-in functionality, built-in unit testing, and plenty of frameworks and environments to leave you focusing on building your own app or website.

These are covered in “Why Learn Python? Here Are 8 Data-Driven Reasons” by Elena Ruchko (https://dbader.org/blog/why-learn-python) if you want to learn more.

Conclusion

You've seen that Python 3 is an elegant and powerful language. Often put into the bucket of scripting languages as if it were another flavor of bash, this simple language delivers far beyond many people's preconceived notions.