Fred Brooks had a huge influence on me as a young adult—both as a software engineer and as a manager. But today, looking back at everything he said (and I believed)—it seems to me there’s another way. I can’t claim that it’s better, him being Fred Brooks and all; but I really think its foundations are more humanistic, and it’s more in-line with the future.

How I Agreed

The first book I read on management was Peopleware. Its premise was simple: managers should guide and enable their employees, who will do wonderful job just by virtue of being smart, wonderful people in an autonomous, wonderful environment. It was a horrible failure; more due to me and to the team being young and inexperienced than to Peopleware being wrong, but still—it made me naturally gravitate towards the opposite school of thought, where I discovered the Mythical Man-Month.

“Whereas Peopleware says managers should be like kindergarten teachers,” I would passionately explain, indulging myself with a dash of demagogy, “The Mythical…


This story is all about imports, modules and packages: rgw underlying structure of all things Python.

Finally, we’ve arrived at modules and packages: the last digivolve of Python. At first, it might surprise you to hear there’s a lot to say about them: after all, in C++, #include is just a preprocessor directive that literally copies over the header, and in Java, import is just a hint on how to link the bytecode. But in Python, like always, a module is an object—and as always, it opens up a new horizon of possibilities.

Importing Goods

Let’s start with the basics: import. Already, there are quite a few different objects it returns:

>>> import sys
>>> sys
<module 'sys'…


This time, we’ll talk about object-oriented miscellanea that didn’t fit anywhere else: hashing, subclassing native types, and exceptions.

We’re nearing the end of our journey: from stuff as basic as scopes, conditions and loops, through objects, classes and metaclasses — we’re ready to talk about our final topic: modules and packages. Before we do, I’d like to take a moment to address all the object-oriented stuff that I skipped, whether because it didn’t fit the narrative, or I just didn’t want to get bogged down by (even more) details.

Hash, Little Baby

There’s a very important special method I skipped: __hash__. It’s pretty basic on one hand, and a bit complicated on the other, so it didn’t fit in any of…


This time, we’re going to talk about classes as objects, and their genesis: metaclasses—and see it’s not all that complicated.

So far, we’ve been talking about objects: what they are, how they behave, methods and attributes, descriptors, context management and creation. All objects are defined in classes, so we’ve been actually talking about them, too, all along—but classes are interesting in and of themselves; and, at least in my experience, vastly misunderstood.

The Curious Case of the Class in Python

Other languages, like C++, don’t actually have a notion of a “class” in their code—it’s mainly directives for the compiler as to how it should lay out and wire objects of this type. This way, all the attributes and methods are linked to the same values and code…


This time, we’ll see how objects can be used to manage contexts—and how exactly an object is born, lives, and dies.

Last time, we talked about the awesome power of descriptors; now, getting back to objects, we still have a few behaviors to cover: namely, context management and creation. After that, I’ll add another article for the ones that got away—but in the meantime:

Bossy, but Sensitive

Let’s start with a background story for motivation, again. You probably did this in the past:

>>> fp = open(path)
>>> data = fp.read()
>>> fp.close()

And that’s OK; the question is, whether you did it like so:

>>> fp = open(path)
>>> try:
... data = fp.read()
... finally:
... fp.close()

The reason being, fp occupies…


This time, we’re going to use descriptors for all sorts of cool stuff, from properties and class methods to cached and typed attributes.

Last time, we saw how to use descriptors to implement basic Python functionality—namely, methods. This time, we’ll expand our scope to implement more advanced features like properties, class methods, and some cool tricks that aren’t available by default.

Proper Properties

Let’s start with properties; but first, let’s come up with a background story for motivation. Say we’re developing a class that encapsulates a 2-dimensional point, and we start with an implementation that uses cartesian coordinates:

class Point:
def __init__(self, x, y):
self.x = x
self.y = y

Anyone who’s ever programmed C++ or Java would tell you this is a bad idea—you’re…


This time, we cover attribute access and method resolution, and see how methods work under the hood using descriptors.

Last time, we said that everything in Python is an object—integers, strings, functions, instances, and even classes. We said each object has three defining properties: a unique identifier, a type, and a value. The type is the important part: it defines that object’s structure and behavior under different circumstances—whether it’s testing for equality, doing arithmetics, or iterating over it. The value is just the object’s state, which parametrizes this behavior.

What’s Left to Say

Curiously, the most sophisticated behavior is also the most underrated one: attribute access. It looks deceivingly simple, and all objects have it—but in fact, there’s a lot to be said…


This time, we go over many more behaviors that objects have to offer, and see just how powerful Python can get.

Last time, we saw that objects comprise of an ID, a type and a value—and that the type is by far the most interesting. We’ve covered display, equality and comparison, so it’s time for some more exciting tricks.

Truth or Dare

Any object can have a boolean value—its state being either “on” or “off”. To make this value easily accessible—for example, to use the object as-is in an if statement—you’d have to implement the __bool__ method (unfortunately named __nonzero__ in Python 2):

>>> class A:
... def __init__(self, x):
... self.x = x


This time, we cover objects—which includes everything, so we focus on the abstraction—IDs, types and values—and some basic behaviors.

In Python, everything is an object. Well, almost everything—keywords like if are not; but everything else is: numbers, strings, functions, instances—even classes! Each object has a unique ID, a type that defines its behavior, and a value that parametrizes it—but contrary to popular belief, the most important (and interesting) part of this trinity is the type. In this article, we’ll understand why, and see some of the fascinating behaviors we can imprint unto custom objects.

But first thing first: let’s talk about object-oriented programming, or OOP. So far, we’ve dealt with procedural programming: in this paradigm, abstractions are encapsulated as…


This time, we evolve once again—unto generators, from basic iteration to full-fledge coroutines.

The next stop after functions (and beyond, and internals 1 and 2) is generators: functions with a state. The premise is very simple—you write a regular function with yield statements instead of return statements, and when you run the function—even as it’s yielded a value, you can resume it from there on, harvesting the fruit of its gradual execution. The use-cases, however, are many—and fascinating, so let’s get to it.

Yielding Control

Generators are often presented as a sophisticated way to encapsulate an iteration, even though they’re much more than that. Even so, let’s start with that:

>>> def gen():

Dan Gittik

Lecturer at Tel Aviv university. Having worked in Military Intelligence, Google and Magic Leap, I’m passionate about the intersection of theory and practice.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store