What are nolocal and global keywords used for?

These two keywords are used to change the scope of a previously declared variable. nolocal is often used when you need to access a variable in a nested function:

def func1():
    x = 5
    def func2():
        nolocal x

global is a more straightforward instruction. It makes a previously declared variable global. For example, consider this code:

x = 5
def func1():
> 5

Since x is declared before the function call, func1 can access it. However, if you try to change it:

x = 5
def func2():
    x += 3
> UnboundLocalError: local variable 'c' referenced before assignment

To make it work, we need to indicate that by x we mean the global variable x:

x = 5
def func2():
    global x
    x += 3

What is the difference between classmethod and staticmethod?

Both of them define a class method that can be called without instantiating an object of the class. The only difference is in their signature:

class A:
    def func1():
    def func2(cls):

As you can see, the classmethod accepts an implicit argument cls, which will be set to the class A itself. One common use case for classmethod is creating alternative inheritable constructors.

What is GIL and what are some of the ways to get around it?

GIL stands for the Global Interpreter Lock and it is a mechanism Python is using for concurrency. It is built deep into the Python system and it is not possible at the moment to get rid of it. The major downside of GIL is that it makes threading not truly concurrent. It locks the interpreter, and even though it looks like you are working with threads, they are not executed at the same time, resulting in performance losses. Here are some ways of getting around it:

  • multiprocessing module. It lets you spawn new Python processes and manage them the same way you would manage threads
  • asyncio module. It effectively enables asyncronous programming and adds the async/await syntax. While it does not solve the GIL problem, it will make the code way more readable and clearer.
  • Stackless Python. This is a fork of Python without GIL. It’s most notable use is as a backend for the EVE Online game.

What are metaclasses and when are they used?

Metaclasses are classes for classes. A metaclass can specify certain behavior that is common for many classes for cases when inheritance will be too messy. One common metaclass is ABCMeta, which is used to create abstract classes.

Metaclasses and metaprogramming in Python are huge topics and feel free to read this if you are interested in it.

What are type annotations? What are generic type annotations?

While Python is a dynamically typed language, there is a way to annotate types for clarity purposes. These are the built-in types:

  • int
  • float
  • bool
  • str
  • bytes

Complex types are available from the typing module:

  • List
  • Set
  • Dict
  • Tuple
  • Optional
  • etc.

Here is how you would define a function with type annotations:

def func1(x: int, y: str) -> bool:
    return False

Generic type annotations are annotations that take another type as a parameter, allowing you to specify complex logic:

  • List[int]
  • Optional[List[int]]
  • Tuple[bool]
  • etc.

Note that these are only used for warnings and static type checking. You will not be guaranteed these types at runtime.

What are generator functions? Write your own version of range

Generator functions are functions that can suspend their execution after returning a value, in order to resume it at some later time and return another value. This is made possible by the yield keyword, which you use in place of return. The most common generator function you have worked with is the range. Here is one way of implementing it (only works with positive step, I will leave it as an exercise to make one that supports negative steps):

def range(start, end, step):
    cur = start
    while cur > end:
        yield cur
        cur += step

What are decorators in Python?

Decorators in Python are used to modify behaviours of functions. For example, if you want to log all calls to a particular set of functions, cache its parameters and return values, perform benchmarks, etc.

Decorators are prefixed with the @ symbol and placed right before function declaration:

def func1():

If you want to learn how to write your own decorators, read this article.

What is pickling and unpickling in Python?

Pickling is just the Python way of saying serializing. Pickling lets you serialize an object into a string (or anything else you choose) in order to be persisted on storage or sent over the network. Unpickling is the process of restoring the original object from a pickled string.

Pickle is not secure. Only unpickle objects from trusted sources

Python docs

Here is how you would pickle a basic data structure:

import pickle

cars = {"Subaru": "best car", "Toyota": "no i am the best car"}
cars_serialized = pickle.dumps(cars)
# cars_serialized is a byte string

new_cars = pickle.loads(cars_serialized)

What are *args and **kwargs in Python functions?

These deal closely with unpacking. If you put *args in function’s parameter list, all unnamed arguments will be stored in the args array. **kwargs works the same way, but for named parameters:

def func1(*args, **kwargs):

func1(1, 'abc', lol='lmao')
> [1, 'abc']
> {"lol": "lmao"}

What are .pyc files used for?

.pyc files contain Python bytecode, the same way as .class files in Java. Python is still considered an interpreted language, though, since this compilation phase occurs when you run the program, while in Java these a clearly separated.

How do you define abstract classes in Python?

You define an abstract class by inheriting the ABC class from the abc module:

from abc import ABC

class AbstractCar(ABC):
    def drive(self):

To implement the class, just inherit it:

class ToyotaSupra(AbstractCar):
    def drive(self):
        print('bbbbb ssssss')

Closing notes

Thank you for reading and I wish you all the best in your next interview. Let me know in the comments if there are any questions you think I should add!