Sunday, April 3, 2016

Private variables in Python (single and double underscore)

Private variables and methods do not exist in Python. There is no way to strictly restrict access to variables or methods from outside the classes where they were defined. If you come from another object-oriented language such as Java or C++ this may sound strange, to say the least. However, this behavior is intended and there are good reasons for it which, for the sake of brevity, this post will not address.

There are, however, ways to emulate private variables and methods in Python. You can hamper access from outside their classes by applying the following conventions outlined in PEP 8:

Single leading underscore "_" 

Prefixing a variable or method name with "_" (single underscore) indicates its intended for internal use and should not be accessed from outside the class in which it was defined.

class MyClass(variable):

    def __init__(self):
        self._meditation = 'zazen'  # Notice the single leading underscore

This is just a convention and will not actually stop us from accessing the variable or method from outside. However, it's advisable to follow this convention as ignoring it can cause a lot of problems.

>>> obj = MyClass()
>>> obj._meditation
'zazen'

Notice that the _meditation attribute is accessible because it was only hidden by convention.

Caveats:
  • Functions that provide internationalization and localization, used to prefix strings that need translation, are frequently named "_". Avoid confusing this with the "private" variables discussed in this post.

Double leading underscore "__"

Python has a feature called name mangling. When variables are prefixed with "__" (double underscore), their names are automatically changed into _<className><variableName>, so they are not easily accessible from outside their classes.

class MyClass(variable):
    def __init__(self):
        self.__meditation = 'zazen'  # Notice the double leading underscore

Now let's try to access it from outside the class:

>>> obj = MyClass()
>>> obj.__meditation
AttributeError: 'MyClass' variable has no attribute '__meditation'

t.__meditation was not found because its name was automatically changed (mangled) to _MyClass__meditation. We could still access it with the syntax below:

>>> obj._MyClass__meditation
'zazen'

The example above is used to illustrate how "private" variables or methods can be accessed outside their classes. This does not mean we should do it as it will most likely wreak all sorts of havoc.

Another example:

class Glass:
    def __init__(self):
        self.__status = None  # Notice the double leading underscore

    def fill(self):
        self.__status = 'filled'

    def empty(self):
        self.__status = 'empty'

    def print(self):
        print(self.__status)

In the example above, the __status attribute of the Glass class can only be set by the empty() and fill() methods. Let's test it:

>>> g = Glass()
>>> g.print()
None
>>> g.fill()
>>> g.print()
filled
>>> g.empty()
>>> g.print()
empty

In real code, "private" variables like these are used mostly to avoid clashes of names defined within classes with names defined within their subclasses.

Caveats:
  • Python's "magic" variables or attributes are also prefixed with double underscore (e.g., __init__, __import__ or __file__). In this context, the double underscore prefix has no "privatizing" effect. Avoid confusing this with the "private" variables discussed in this post.
Thank you for reading.

Saturday, April 2, 2016

Python function arguments, *args and **kwargs

If you are new to Python, you have probably stumbled upon *args and **kwargs in someone else's code, on a forum or tutorial. This post explains what they are.

Types of function arguments in Python

Python functions accept 2 kinds of arguments: positional arguments and keyword arguments. With a few exceptions (explained below), when you define a function, you don't specify whether the arguments are positional or keyword. This is defined at the time the function is called, depending on the syntax used to pass the arguments.

Positional arguments

These arguments are identified based on the position they are supplied when calling the function.
def divide(dividend, divisor):
  return dividend / divisor

When called, the above function outputs:
divide(10,2)
5.0

It knows 10 is the dividend and 2 is the divisor because of the order the arguments were provided.

Keyword arguments

Now let's suppose we want to call the above function inverting the argument order. We can do that using keyword arguments.
divide(divisor=2, dividend=10)
5.0
Notice that the argument order was inverted but they were passed by name, so they were correctly identified.

In Python 3, by inserting "*" as a first argument, we force all following arguments to be called by name (as keyword arguments).
def divide(*, dividend, divisor):
  return dividend / divisor
The function above outputs:
divide(10,2)
TypeError: divide() takes 0 positional arguments but 2 were given

divide(dividend=10, divisor=2)
5.0

What are *args and **kwargs?

*args and **kwargs allows you to pass an arbitrary number of arguments to a function without knowing them in advance.

The relevant syntax is the * and **. The names "args" and "kwargs" are only by convention. You could use *foo and **bar and have the same result. However, sticking to conventions makes it easier for us to read each other's code.

*args example

def print_args(*args):
  for arg in args:
    print(arg)
Running the above function with an arbitrary number of arguments results in:
print_args('foo', 'bar', 5)
foo
bar
5
There is no limit to the number of positional arguments you can provide.

**kwargs example

def print_kwargs(**kwargs):
  for name, value in kwargs.items():
    print('{0} = {1}'.format(name, value))
If you are not familiar with the items() method, it's explained in this post.

Running the above function with an arbitrary number of arguments results in:
print_kwargs(meditation = 'zazen', minutes = 40)
meditation = zazen
minutes = 40


Combining fixed arguments, *args and **kwargs

def print_args(fixed_arg, *args, **kwargs):
  print(fixed_arg)
  print(args)
  print(kwargs)
Running the above code outputs:
print_args('foo', 'bar', 'baz', meditation = 'zazen', minutes = 40)
foo
('bar', 'baz')
{'meditation': 'zazen', 'minutes': 40}
Notice that:
  • fixed_arg is a fixed positional argument 
  • args is a tuple of positional arguments 
  • kwargs is a dictionary of keyword arguments

Friday, April 1, 2016

Quick Reference: How to iterate over keys and values at once on a Python 3 dictionary

On Python 3, we use the items() method to iterate over a dictionary's keys and values simultaneously.
dictionary = {"name": "John", "age": "32"}

for key, val in dictionary.items():
    print(key, val)
When the above code is executed, it produces the following result:
age 32
name John