May 20, 2009

Everything's an Object

"Everything's an object" is a truism for truly object-oriented languages, and Python is truly object-oriented. You may meet some purists who try to tell you that only [their favorite language] can really be called object-oriented, because Python doesn't have this feature or that feature, but don't take any notice of them. Religious zealots are everywhere, and their only interest is to convert you to their faith. In the Python world we don't tend to hold with religious zeal, and much prefer irreverent comedy sketches and making fun of things. Particularly religious zealots.

Newcomers to the language are sometimes surprised to find that you can pass all kinds of things as arguments to functions, and use them quite naturally inside the functions. The classic example is functions themselves. Let's write a program containing an innocuous little function that takes a function as its first argument and returns the result of calling the function on its second and third arguments.
def caller(f, a, b):
return f(a, b)

def adder(x, y):
return x+y

print(caller(adder, "abc", "def"))
print(caller(adder, 123, 456))

class MyCls:
def __init__(self, first, second):
self.first = first
self.second = second
def method(self):
return self.first*self.second

obj = caller(MyCls, "=", 10)
print(obj, obj.method())
The adder function adds two arguments, and pass that as the first argument to caller a couple of times. If all has gone to plan the first two lines of output will look like this:
abcdef
579
The first argument to adder isn't constrained to be a function. It just has to be something that can be called.We can take advantage of the fact that a call to a class creates an instance of the class to have caller create the instance for us. The MyCls class has an __init__() method that takes two arguments (as well as the ubiquitous self that the zealots will try and persuade you isn't necessary).

The call to caller returns an instance of MyCls. When that instance's method() is called it uses the remembered arguments to print out a string of ten equals signs:

==========


The generality that this demonstrates can be difficult to get used to if you haven't come across anything like it before. Once you appreciate it, though, it gives you a flexibility that is hard to match in many other languages. That's one of the reasons why the Python world often talks about "callables": we don't care whether it's a function or a class, we only care that it can be called.

This is one aspect of polymorphism, one of the foundations of object-oriented programming.

[The code will run under Python 2, but the output shown here was produce by Python 3. The output from Python 2 will differ slightly].

3 comments:

Dan Villiom Podlaski Christiansen said...

I'm sorry, but your claim that everything is an object in Python isn't entirely accurate. For instance, keywords aren't objects. I haven't checked, but integers and so on may not be either. I find that what makes Python so appealing is that it's a well designed, practical language; and it is so to an extent that makes it beautiful, in my opinion. As a language, it consistently consistently avoids purity, no matter what you consider pure; perhaps that's the only purity it possesses?

Steve said...

@Dan: sorry if I seem to be being pedantic, but if you are prepared to regard keywords as "things that aren't objects" then why not also list the punctuation? Colons aren't objects, and neither is the whitespace.

I think you will find that if you try to pass a keyword as an argument to a function you will get a syntax error. This is the interpreter's way of telling you that what you wrote isn't actually Python.

Integers, longs, floating point numbers, lists, tuples, dicts, functions, modules, etc. are all objects. In that respect Python is much more like SmallTalk than Java, which has the distinction between (for example) integer and Integer to confuse new users.

Aficionados of the "Zen of Python" will doubtless be muttering "practicality beats purity" to themselves after reading your words. I think you have successfully identified Python's attraction to most of its users.

rgz said...

We also have a fair control over keywords and punctuation too. At least we can control indexation, calling and bitwise boolean operators. We can't control regular boolean operators but we can control the boolean value of arbitrary objects. metaclasses let you control much of the effect of the class keyword, and decorators let you control the result of the def keyword. print can be substituted by a function and we can control all but the simplest assignment statements*.

So not everything you see in a python script is an object but pretty much so.

But what Steve really is talking about is that all objects have equal standing. You can pass a class as an argument to a function and you can pass a function as a base class!

Oddly enough there is one domain where python consistently denies you control and that is name binding.