Introduction
I’ve always thought of myself as a generalist language-wise. Although my first steps in software development started with Python, I was quickly involved with both C# and JavaScript, and my knowledge of the three languages built up more or less at a similar rate, with the emphasis changing as I took on new opportunities and different tasks. There is a certain beauty in rediscovering wider software engineering principles through the process of learning a new language, and there’s also an obvious practical benefit of becoming a polyglot from a professional point of view. So, with that in mind, I am happy to share this article, that aims to offer help and tailored insights to any curious C# developer wanting to have their first taste of Python.
Even though the title of the article states that the intention is to cover the very basics of this beautiful language, existing knowledge of general programming and software engineering is expected, especially of C#. For any reader potentially looking to learn how to code while also learning some basics about Python resources like this non-programmers guide or one of the many free online courses such as this one by CodeAcademy might potentially be more suitable (and it goes without saying that Python is a great choice to start your coding adventure!).
As a side note, you can try most of the code examples here (both C# and Python) without even installing anything in your computer thanks to virtual sketchpads such as REPL (https://repl.it). Feel free to try the examples and write your own code as we go!
Without further ado, we can start delving into Python by asking: what is it?
The Python Programming Language
Python is a full-fledged programming language originally developed by Guido Van Rossum and released in 1991. Python is:
- A high-level language, implemented (in its most popular form) on top of C (CPython).
- Interpreted (as opposed to compiled).
- Dynamically typed, so a runtime object’s type does not need to be declared before assignment and initialisation (unlike statically typed).
- Strongly typed, as types are defined and explicit conversion has to occur in order to cast types (as opposed to weak-typing).
- Syntactically simple, with the syntax of the language emphasising readability and cleanliness (thus getting rid of cluttering and redundant characters such as semicolons and braces).
- It is simple, light, and beginner-friendly, with a “batteries included” approach to programming offering a solid base library and tools for most programming tasks.
Its design philosophy is beautifully summarised in The Zen Of Python, that you can read below (or after entering import this in your Python console):
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one– and preferably only one –obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let’s do more of those!
A simple way to experiment this firsthand could be comparing how code with the same requirements look on both C# and Python, and what’s more traditional and straightforward than a “hello world”? This is your usual cup of C# to print our greeting in the console:
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, world!");
}
}
And this is Python’s:
print("Hello world!")
Okay, so far Python wins on brevity. Let’s try a slightly more complex example; we will define a function that accepts a list of strings, iterates through it, prints each list member, and finally returns the longest one (for simplicity’s sake we won’t worry about validation or other aspects but the most basic functionality).
Again let’s start with the C# version:
using System;
using System.Collections.Generic;
using System.Linq;
// Defining main class
class MainClass
{
// Main loop (entry point)
public static void Main (string[] args)
{
List<string> words = new List<string> { "Banana", "Pear", "Apple"};
// Our function call
string result = GetLongestWord(words);
Console.WriteLine(string.Format("Longest word is: {0}!", result));
}
// Our function definition
public static string GetLongestWord(List<string> words)
{
for (int i = 0; i < words.Count; i++) {
Console.WriteLine (string.Format("I've got {0}.", words[i]));
}
return words.OrderByDescending(s => s.Length).First();
}
}
And then Python’s (tested on 3.6.1):
# The function definition
def get_longest_word(words):
for item in words:
print("I've got {0}.".format(item))
return max(words, key=len)
# Calling our function
my_words = ["Banana", "Pear", "Apple"]
result = get_longest_word(my_words)
print("Longest word is: {0}!".format(result))
Take a look at both implementations and have a quick think about how they look in comparison. In the next section, we’ll see that even such a simple snippet of code is enough to highlight some of the basic features differencing Python and C#.
Style and Formatting
Probably the most obvious feature directly striking out of a side-to-side code comparison is the syntactic differences, specifically a reduced number of characters in Python. As you might have seen Python does not have any curly braces (“{” and “}“) delimiting code blocks. Instead, Python relies on indentation in order to group statements, and suddenly all of those braces and semicolons are gone. There is not a hard requirement for how many spaces the indentation must be comprised of, as long as it is consistent.
So this correct but weird-looking chunk of code will work perfectly as long as we have defined my_boolean:
if my_boolean:
print("Branch 1")
else:
print("Branch 2")
But this will cause an IndentationError: unexpected indent error.
print("This is fine.")
print("Whoops.")
Instead of opening and closing braces, a colon at the start of the code block and indentation itself do the trick. Bear in mind that unlike in many other languages, formatting and indentation is not just helpful for increased code readability; in Python it is mandatory! Within the same subject, you may have noticed the lack of semicolons. Python uses carriage returns to separate statements.
Moreover, did you notice the differences in naming conventions? Neither language requires you to name things in a specific way, but there are generally accepted conventions. CamelCase is the de facto standard for C# both for function and variable names, whereas all_lowercase_with_underscores is the best way to go with Python.
Not a formatting feature strictly but an access one, see how there are no accessors (public, internal, private…) to be found in Python. Everything is equally accessible, and it’s up to the developer to take an informed decision and how to access or not to access something, in what is reminiscent somewhat of the Unix approach of assuming that the users are savvy enough to know when they are getting into trouble (not necessarily the truth tho). Restricted access methods or variables are suggested in Python with an underscore preceding the variables or function name, as in _my_variable_name.
Finally, you can see that comments are preceded by “#”, instead of “//”.
Compilation and Referencing
Being a .NET developer you are more than used to the usual development workflow: save your work, build/compile, test, repeat. Moreover, if you happen to need a different library in a project, you normally add a reference to the library from the target project, thus creating a dependency between the projects/assemblies. Once the dependency has been set, it’s just a matter of declaring the dependency with the using directive, To allowing the use of the referenced types in a namespace.
However, this has some implications. To start with, this workflow creates a dependency on Visual Studio! Although .NET/C# development is possible without the assistance of Visual Studio (especially true after the appearance of the .NET Core tool ecosystem), it is certainly not the easiest nor most optimised way to do it. Working with .NET is almost a synonym of working with Visual Studio and/or other IDEs taking care of compilation and other required tasks.
Secondly, when it comes to referencing assemblies, we create a direct dependency. Any change in a chain of dependencies will require rebuilding one or more members, which can become a huge hassle in bigger projects due to compilation times.
As we mentioned before, Python’s standard implementation is interpreted, not compiled, which allows us to completely skip the compilation step. This enables a much leaner and agile development process, with virtually no loss of time due to building processes within iterations. And that doesn’t mean that we can not reuse our code in Python, it’s actually the opposite.
If C# is all about assemblies, Python is all about modules. Importing and using a module is trivial, as seen in the following example, in which we import the module requests, send a get request to Google, and then print the response’s status code and raw HTML:
import requests
response = requests.get("https://www.google.co.uk/?q=Banana")
print(response.status_code)
print(response.text)
“import module_name” is all you need. Of course, Python also needs to know where to look for said files and modules, which normally means either 1) placing your code in Python’s default module path or 2) setting those paths to point to your desired locations, or 3) download and install popular modules automatically with a package manager such as pip, in a similar way to what you have probably done with Nuget already. Feel free to read more about it here, and hopefully we will cover some popular packages and package management in a different blog post.
Handling the Unexpected
When it comes to handling exceptions and dealing with errors, again C# and Python end up approaching the subject with almost opposing philosophies.
Look Before You Leap (LBYL) is the C# way, where you assert that something is possible before trying to do it.
A typical case of potential error handling in C# could look like this:
using System;
class MainClass {
public static void Main (string[] args) {
var myArg = (args != null && myList.Count > 0) ? args[0] : null;
if (myArg != null) {
try {
// potentially tricky operation involving the arg, let's say call to an external service
} catch (Exception ex) {
// error handling
}
}
}
}
Firstly, we “look before we leap”, and even if we are expecting our argument to be the first one in the list of args, we ensure that we argument is present. Only afterwards we try to use it, and we only use proper exception handling when we are “expecting issues”, as in an exception likely to be thrown due to connection with a service, or other similar cases that the developer can anticipate. Exceptions handling is ideally to be minimised and used as a last resort, as it adds in an overhead that we’d prefer to avoid.
That’s not the pythonic way of doing things. Python exception handling is notably cheaper, and thus our behaviour changes; we use exception handling liberally, and assume that things will work, changing the flow through exceptions only when a potential problem arises. This design approach is summarised with the motto It’s Easier to Ask for Forgiveness than Permission (EAFP).
def do_stuff(args):
try:
call_to_service(args[0])
except IndexError:
# what if there was no such arg?
except OurClientError:
# what if the client itself had an issue?
So don’t feel bad and go all out with your exception handling if you want to do it the pythonic way; just make sure to be granular with your exception handling and try to catch the right exception (which should be done in C# anyway).
Other Features
Of course, we have only scratched the surface of that Python has to offer. A few other areas of interests or language quirks are listed below; we’ll cover more of these in further articles. Note that for any “missing features” in comparison with C# stated below, a “no” means “no out-of-the-box support”, and usually there are available modules to add that functionality.
- Emphasis in data structure manipulation through list comprehension.
- No interfaces, although abstract base classes and modules to that effect exist.
- Multiple inheritance for classes is possible.
- No constants.
- No switch/case.
- No enums.
- No arrays, but lists.
- Booleans are capitalised (True/False).
- No function overloading, but optional arguments and *args / **kwargs.
- Very efficient automatic garbage collection (memory leaks are rare) that maintains a reference count.
- Completely different toolset with normally more lightweight tools.
- Python 2 and Python 3, the most popular versions still in use, can be pretty different for some features.