In the Stack Overflow Developer Survey for 2018, Python sits comfortably in the first place as the most wanted programming language, with JavaScript and Go coming second and third. Python also ranks third in the “most loved” category.

Despite the age (20 years and counting), Python's popularity keeps growing, and for good reasons. It's reliable, flexible, easy to learn, open-source, and cross-platform since the beginning. It also helps that, over time, it enjoys a robust and active developer community and incredibly rich eco-system of free libraries supporting all kind of usages: Web applications and services, desktop apps, scientific computing, scripting - you name it.

Surprisingly enough, not many .NET developers know that their favorite development tool, Visual Studio, offers superb support for Python. In this article, you'll see how you can leverage your hard-acquired Visual Studio skills to work immediately and efficiently with this fantastic language.

On the Relationship Between Python and Visual Studio

Nowadays, the Visual Studio brand encompasses several different products. There is Visual Studio for Windows, Visual Studio for Mac, and then the cross-platform Visual Studio Code editor. As you know, despite their names, these are entirely different products, with different prerequisites and feature-sets. Python support is available in Visual Studio for Windows. On Mac and Linux, and of course in Windows, you can count on a grand Python experience in Visual Studio Code.

Presently, Visual Studio for Mac offers no support for Python, and frankly, I wouldn't bet on something like that happening any time soon. Anecdotal fact: One year ago, someone opened a feature request ticket on UserVoice. Since then, the ticket has received many votes (mine included) and comments, but no feedback from the team. At its core, VSMac is MonoDevelop with many new extensions added to support new workloads (.NET Core, Azure Deployment, Unity). According to Miguel De Icaza (founder of the GNOME, Mono, and Xamarin projects), the internals are being progressively replaced with Visual Studio code when applicable. So yes, it's a different beast. Interestingly though, if you look at the MonoDevelop feature matrix, you'll find that Python 2 bindings are available for Linux. Wait and see, I guess.

In this article, I'll cover the flagship product, Visual Studio for Windows. The story between Python and VS has been going on for a long time, and I can be very precise about that as the project is and always has been open source (https://github.com/Microsoft/PTVS), something that allowed me to clone the repository and play with it a little bit. As it turns out, the first commit date is March 8, 2011 (Figure 1).

Figure 1: Exploring the open source repository, I discover that the first commit dates was in 2011.
Figure 1: Exploring the open source repository, I discover that the first commit dates was in 2011.

In the dev team, there are Microsoft employees who are, or have been, Python core developers. These include Steve Dower, Eric Snow, Dino Viehland, and Brett Cannon, who is now leading the VSCode Python extension. Project maturity and team composition, I think, offer a clear view of Microsoft commitment to the language. Python in Visual Studio is real. So real, in fact, that today Python ships as an integral part of the product.

In the dev team, there are Microsoft employees who are, or have been, Python core developers.

Installing Python in Visual Studio

Both VS2017 and VS2015 installers allow you to add Python as an option, either on the first the install or later. There are small differences, however. VS2017 is capable of installing the language interpreter if needed, whereas with VS2015, you have to install the interpreter separately if it's missing in the target system. Also, in VS2017 (15.2 or later), Python comes as a standard workload. With the VS2015 installer, you need to enable the Python Tools for Visual Studio package from the list of available languages instead.

If, for some odd reason, you're stuck with VS2013, VS2012, or even VS2010, well, good news! You can still add Python manually by downloading and installing the appropriate version of the stand-alone installer (PTVS 2.2 for VS2013; PTVS2.1 for VS2012 and VS2010).

Long story short, you can have the best Python experience in Visual Studio 2017. You can still enjoy Python with previous VS versions, but seriously, you should upgrade as soon as possible, and of course not just because of Python. VS2017 is better (and much more performant) in so many ways. For the remainder of this article, I'll refer to Visual Studio 2017 for Windows.

I assume you already have Visual Studio installed. Run the Installer, then select the Modify option. In the Workloads tab, select the Python development workload (Figure 2).

Figure 2: Make sure that the Python development workload is selected.
Figure 2: Make sure that the Python development workload is selected.

When you select the workload, you can fine-tune what gets installed in the right-side pane. I suggest that you go with the default. Just make sure that Web support is checked, as well as Python 3 64-bit. If you're just starting out, skip Python 2. Support for Python 2 ends January 1, 2020, and Python 3 is much better in so many ways. Unless you have to deal with some grumpy old library, Python 2 just isn't worth it, and you can always come back and add it later if needed.

Once the workload installation is complete, open Visual Studio and hit Alt+I, or click Tools/Python/Python Interactive Window to open, you guessed it, the Python interactive window. If it opens, success! Python is sitting right there, at your fingertips. To test it, enter something pythonic and very original, as I did in Figure 3.

Figure 3: If it doesn't work, go back and check your steps.
Figure 3: If it doesn't work, go back and check your steps.

Working with Python in Visual Studio

The TL;DR (too long; didn't read) version of this article is: you work with Python as you would with any other .NET language. Everything works as you would expect. Allow me to elaborate a little bit.

Project Templates

In the New Project window, pick Installed/Other Languages, then Python. Notice how Visual Studio proposes a list of project templates including (if you opted-in for Web support) Web applications built with well-known frameworks such as Flask and Django. These framework templates include a starter site with some pages and static files, just like the ASP.NET templates we all know. They provide all the code and assets needed to run and debug the server locally and, eventually, deploy to Azure. Support for Python virtual environments is also built-in (I'll get to virtual environments in a minute). In Figure 4, you can see that I also have an Azure Cloud Service template. That's because on install, I checked the Azure Cloud Services core tools option. For this first run, pick the most straightforward option: an empty Python Application.

Visual Studio proposes a list of project templates, including Web applications built with well-known frameworks such as Flask and Django.

Figure 4: Python support in Visual Studio offers a number of project templates; Flask is my favorite.
Figure 4: Python support in Visual Studio offers a number of project templates; Flask is my favorite.

Solution Explorer

As you would expect, a new solution appears in the Solution Explorer. It holds a single Python project (PythonApplication1.pyproj) with a single Python file (PythonApplication1.py - all Python files use the .py suffix). Notice that in addition to the well-known References node, you also have Python Environments, which, unless you are already familiar with Python, is new. When expanded, you can see the Python interpreters that are available to you. Expand an interpreter node to see the libraries installed into that environment (Figure 5).

Figure 5: Solution Explorer looks familiar yet different. Python Environments node is new. The project file (.pyproj) is a Visual Studio artifact.
Figure 5: Solution Explorer looks familiar yet different. Python Environments node is new. The project file (.pyproj) is a Visual Studio artifact.

I'll get to virtual environments in a moment. Let's write some code first.

IntelliSense, Code Completions, Type Signatures, Etc.

As you know, IntelliSense provides code completion, type signatures, quick identifier info, code coloring, and probably more cool features. To the .NET developer, the IntelliSense features set is a no-brainer, but the thing is, stuff like code completion and type signatures are somewhat of a challenge in Python, which is strongly typed but has dynamic semantics.

Until not too long ago, every time you installed or updated a Python package, Visual Studio took its time to scan it and then update an internal cache, also known as “completion DB.” Depending on the number of packages installed and their size, the process could become (and, in fact, was) slow and inefficient. Not exactly the best user experience. But since Visual Studio 15.6, things have changed. It now performs a lightweight analysis of Python modules as you import them into the code (and not at install time). The problem with this new technique is that not all packages provide the metadata required to perform an efficient, lightweight yet in-depth analysis. The result is that for some packages, you get a better IntelliSense experience; for others, well, things are still a bit clumsy. Currently, it's possible to fall back to the old method, although you have to do it explicitly. The plan is to abandon the completion DB approach sometime in the future (Figure 6).

For some packages, you get a better IntelliSense experience; for others, well, things are still a bit clumsy

Figure 6: Starting with VS 15.6, database-based IntelliSense is disabled, but you can reactivate it if needed. Despite the caption, it's also effective in the standard release (non-Preview).
Figure 6: Starting with VS 15.6, database-based IntelliSense is disabled, but you can reactivate it if needed. Despite the caption, it's also effective in the standard release (non-Preview).

My advice is to keep going with the default settings. As more packages include the necessary metadata (namely, .pyi type hint files), the IntelliSense experience improves. The dev team is actively working on this feature. With the next release (15.7), we're getting improved type hints, which are capable of issuing warnings for mismatched types, something not available at this time.

Armed with this knowledge, type print('Hello, CODE!') in the code editor (single and double quotes are both valid for strings, although the convention is single quotes). Notice how code completion and signature hints work fine. Running the program is a simple matter of hitting Ctrl+F5, as you would do with any other .NET language (Figure 7).

Figure 7: Type Python code and then run it, as you would do with any other .NET language. IntelliSense works too.
Figure 7: Type Python code and then run it, as you would do with any other .NET language. IntelliSense works too.

Now, before you replace the code with something new and more exciting, let's dig into Python virtual environments and package management.

Virtual Environments and Package Management

“Dependency hell is a colloquial term for the frustration of some software users who have installed software packages that have dependencies on specific versions of other software packages.” ?Michael Jang, 2006, "Linux Annoyances for Geeks."

We've all had our share, right? Python's solution to this problem is virtual environments. A virtual environment is an isolated environment, usually (but not necessarily) used by a single project. A project can comfortably sit, along with its dependencies, in its isolated environment, regardless of what dependencies every other project has. To reduce overhead, a virtual environment usually (but again, not necessarily) uses the global interpreter and standard library (think the BCL) but maintains its package store in a private folder.

A project can comfortably sit, along with its dependencies, in its isolated environment, regardless of what dependencies every other project has.

Visual Studio offers full support for global and virtual environments through the Python Environments window. To open it, right-click on your project's Python Environments node in the Solution Explorer, and then click on View All Python Environments. On a new project, the first time you do this, you get a list of the global Python interpreters detected by Visual Studio in your system. The one in bold is the default interpreter used by new projects.

Of course, you don't want to work in a global environment. You first want to create an isolated environment for your project. So, go back to the Solution Explorer and right-click on Add Virtual Environment. A dialog pops up asking for two fundamental things: the location of the environment (by default a project sub-folder), and the base interpreter. Visual Studio is smart enough to figure out the most recent, suitable Python interpreter in the system and select it for you. Once you hit the Create button, the new environment appears in the Solution Explorer. Expanding it reveals a couple of pre-installed packages: pip and setuptools. These are used to install and upgrade project dependencies (Figure 8).

Figure 8: You created a virtual environment for the project. It uses the global interpreter (Python 3.6 64-bit) and comes with two pre-installed packages.
Figure 8: You created a virtual environment for the project. It uses the global interpreter (Python 3.6 64-bit) and comes with two pre-installed packages.

You could add more virtual environments to the project and then switch among them as needed, maybe to experiment with different versions of specific dependencies. The active environment is always in bold.

How do you install packages in the environment? The Python equivalent to NuGet is the Python Package Index (PyPI), “a repository of software for the Python programming language.” It predates NuGet by a long shot. Over time, the community has developed and registered over 130K packages on PyPI (https://pypi.org). That's huge, yes. To install a package, right-click the desired environment in the Solution Explorer, then click Install Python Package. In the search box, type matplotlib, then click on pip install matplotlib from PyPI. Once installed, the package appears in the Python Environments window. Matplotlib is a plotting package. It has a few dependencies that are also downloaded and installed for us.

Back to the code editor. Replace the previous print statement with this code:

from math import radians
import numpy as np # installed with matplotlib
import matplotlib.pyplot as plt

def main():
    x = np.arange(0, radians(1800), radians(12))
    plt.plot(x, np.cos(x), 'b')
    plt.show()

main()

On the first line, you're importing the radians function from the math module, which is part of the standard library. On the second line, you import from numpy, a matplotlib dependency. Finally, you import the pyplot function from matplotlib and rename it as plt. You then define a plotting function and, on the last line, execute it.

The snippet above comes from the official documentation (https://docs.microsoft.com/visualstudio/python). The project recently got a dedicated technical writer, and it shows. The revamped documentation is top-notch and it's open to public contribution. I contributed a few irrelevant fixes myself, something that hopefully didn't lower the overall quality.

If you run the code, a window with a nice-looking graph should open up.

Interactive Debugger and the Python REPL

Python developers traditionally spend a lot of their time at a terminal or command prompt, switching back and forth from their editor. I know because I do that all the time in my command line/Vim environment (one day we should talk about the advantages of using vim key-bindings in Visual Studio, but this is not this day).

One of the coolest features in Visual Studio is the interactive debugger. Wouldn't it be cool if you could leverage that with Python too? Well, rejoice! The complete VS debugging experience you're used to when working with .NET is also available with Python. You can run code step-by-step or add breakpoints, even conditional ones. You can, of course, examine the entire program state and change the value of variables, or examine the call stack (Figure 9).

Figure 9: The breakpoint I set triggered a pause in code execution. I'm now having fun inspecting the locals.
Figure 9: The breakpoint I set triggered a pause in code execution. I'm now having fun inspecting the locals.

Speaking of debugging, keep in mind that Visual Studio can also launch and debug Python applications on a remote Windows computer. It can also debug on a different remote operating system, device, or Python implementation (the standard Python implementation is CPython). Remote debugging on a different operating system is possible thanks to ptvsd, or Visual Studio remote debugging server for Python, a Microsoft-maintained package currently in its alpha stage.

The complete VS debugging experience you're used to when working with .NET is also available with Python.

One great feature of interpreted languages is the read-evaluate-print-loop (REPL) experience they offer. The VS interactive window provides all the capabilities of the Python REPL. You used the interactive window briefly before. It's handy when you want to try out some ideas or test some code before committing to it. Alternatively, you can send code from the editor to the interactive window, so that you can try it out right away; move the cursor over a line, then press Ctrl+Enter (or right-click and select Send to interactive).

When you do that, the line content goes straight to the interactive window and in the editor, the cursor moves to the next line, which allows you to continue sending lines to the interactive window as needed. Even better, you can select a block of code in the editor and then hit Ctrl+Enter to send it all in one move (Figure 10).

Figure 10: When you send a code block to the interactive window, lines are executed at once, sequentially, as they hit the REPL.
Figure 10: When you send a code block to the interactive window, lines are executed at once, sequentially, as they hit the REPL.

Now, this is a REPL, so keep in mind that every line is executed at once, even when you send a whole block of code. In Figure 10, I sent the whole little program over to the REPL. The result is that when the last line hits the REPL, the main() function executes and the graph window opens.

Profiling and Unit Testing

Unit testing is critical, even more so with dynamic languages such as Python. Visual Studio can discover, execute, and debug unit tests as seamlessly as it does with .NET languages.

In Python, the convention is that test method names begin with “test.” Test classes are subclasses of unittest.TestCase. Visual Studio follows the convention. Add a New Python Unit Test Item to your Python project (Ctrl+Shift+A). What you get is a test1.py file with the following default code:

import unittest

class Test_test1(unittest.TestCase): def test_A(self): self.fail("Not implemented")

if __name__ == '__main__': unittest.main()

A quick glance at the code reveals that a typical Python test is not very different from a C# test. You import the unittest module from the standard library and then declare a class that inherits from the base test class. The class only holds one test method: test_A.

Now open the Test Explorer (Test/Windows/Test Explorer). Your project should already sit there as the root node, with test1.py as a child. Expand the child node to verify that the only test available in the code, test_A, has been discovered and is listed. If you run the test by double-clicking it in the Text Explorer, it fails, as expected given the current code.

Now go back to PythonApplication1.py and modify it to look like this:

from math import radians
import numpy as np # installed with matplotlib
import matplotlib.pyplot as plt
import sys

my_var = 'CODE Magazine'  # we want to test this

def main():
    x = np.arange(0, radians(1800), radians(12))
    plt.plot(x, np.cos(x), 'b')
    plt.show()

if __name__ == '__main__': main()

There are two additions. You added a new global variable my_var; then you added an If statement before the last line. The global variable my_var is going to be the target of your soon-to-be-updated test. That If statement does look weird though, doesn't it? When a Python script runs as the main program, all of the code that's at indentation level 0 gets executed. Functions and classes that are defined are, well, defined, but none of their code gets executed. Unlike other languages, there is no explicit main() function that gets run automatically; implicitly, the main() function is all the code that sits at the top level.

After the edit, all the top-level code that's left is your newly added If statement and, of course, your new variable that's declared and set to a string value. The built-in variable __name__ evaluates to the name of the current module. If a module runs as the main program, then __name__ is set to __main__. Thus, you can test whether your script is being run directly or imported by another module by testing:

if __name__ == '__main__':
    # code executed only when the module is run directly (not imported)
    ...

All that your If statement does is make sure that code in its block does not run when another module imports your script. So now when you launch test_A and test1.py imports PythonApplication1.py, you don't get the graph to show up. Notice that you have the same test in test1.py, and for the same reason. With that out of the way, let's edit the test to make it pass.

Modify test1.py to look like this:

import unittest
from PythonApplication1 import my_var

class Test_test1(unittest.TestCase):
    def test_A(self): self.assertEqual('CODE Magazine', my_var)

if __name__ == '__main__': unittest.main()

Now save all the changes (yes, you do have to save your changes manually), then run test_A and enjoy watching it go all green (Figure 11)! You can, of course, also debug unit tests by setting breakpoints, inspecting locals, and all the other debug features you know.

Figure 11: Visual Studio can discover, execute, and debug Python unit tests as it does with any other .NET language.
Figure 11: Visual Studio can discover, execute, and debug Python unit tests as it does with any other .NET language.

You can also profile your code. Go to Analyze/Launch Python Profiling. The Profile Settings dialog opens. Click Start, which launches your program. Once execution ends, a performance report opens in Visual Studio (Figure 12).

Figure 12: Visual Studio can profile your code. Obviously, in this case, most of the time is spent plotting and not in our code.
Figure 12: Visual Studio can profile your code. Obviously, in this case, most of the time is spent plotting and not in our code.

Fun with Visual Studio, Python, and C/C++

Python and the C language have a strong relationship. For starters, Python core is written in C, hence its name, CPython. It's quite easy to add new C/C++ built-in modules to Python. You typically do that for performance. Remember, Python is an interpreted language, so it makes sense to write performance-critical pieces in C/C++. After all, the main reason why Python is so widely adopted in the scientific field is that it combines the excellent readability intrinsic to the language with the high performance offered by its scientific libraries, which are written in C and wrapped in Python.

In Visual Studio, you can write both Python and C++ code and, what is even more exciting, you can write extension modules for CPython. You need to have both the C++ and Python workloads installed, or you can select the Python native development tools option for the Python workload in the Visual Studio Installer. If you're interested in this topic, I suggest that you visit the official documentation site. Over there, you'll find an excellent tutorial on writing a C++ extension for Python.

Calling .NET from Python

Besides CPython, .NET developers have another intriguing option at their disposal: IronPython. IronPython is a different implementation of Python. Like CPython, it's open source. It runs on the .NET DLR (Dynamic Language Runtime) and allows it to call .NET libraries from Python. That's right. With IronPython, you can happily consume the .NET eco-system from your Python code.

Figure 13: Calling .NET from Python, courtesy of IronPython
Figure 13: Calling .NET from Python, courtesy of IronPython

In Figure 13, you can see how Visual Studio offers direct support for IronPython. A few IronPython project templates are also available. You can create a new WPF or WinForms Python application. How weird is that?! Keep in mind that the official IronPython distribution is an implementation of Python 2.7. A Python 3 effort, conveniently called IronPython3, is ongoing. You can build and use it (instructions are available on GitHub), but you can't find a reference (or a download link, for that matter) to it on the official website, and for a good reason. At the time of this writing, the IronPython3 build fails on CI.

Being able to consume the .NET libraries from Python (and vice-versa!) opens for some interesting use-cases. I've seen IronPython used as a powerful, embedded script engine, for example. Got you interested? Head over to the IronPython website at http://ironpython.net for the full rundown.

Summary

The story between Python and Windows has always been complicated. Python comes pre-installed with all Linux distributions and on MacOS, not on Windows. Until not too long ago, even installing Python on Windows was quite an uncomfortable experience. Traditionally, Windows developers prefer to work with rich, full-featured IDEs and aren't prone to leaving their comfort zone to work with a different language. These factors alone probably didn't help with making Python very popular in the Windows eco-systems, which is unfortunate considering how popular the language is on the other platforms.

Python for Visual Studio fills the gap. As .NET developers, we get a seamless installation and development experience right in our preferred development environment. On the other hand, Python folks get a fantastic development environment on Windows, even more so with the Community Edition of Visual Studio, which also supports the Python workload.

I'm a firm believer that the effort required to become a polyglot programmer is worth it. It pays big dividends in the long run. Embracing different technologies, stacks, and programming cultures made me a much better developer than I was. Like many others, to achieve that goal, I had to take the plunge and move far away from my comfort zone to become proficient with oh-so-many new things: Linux, bash, Python, open source. Today, thanks to the new workloads in Visual Studio, becoming proficient with new languages (did you know that there is a Node workload too?) is easier than ever.

You should take the chance.