[Tutor] classes and names [__function__(arg)]

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Sun, 23 Dec 2001 13:36:08 -0800 (PST)


On Sat, 22 Dec 2001, Kevin McCormick wrote:

> Greetings all!  I am writing a module of spreadsheet type date
> functions and I would like to do some things with the function names.  
> Some of the functions are written really for supporting the actually
> useful functions and I don't quite understand the __function__(arg),

The '__function__(arg)' stuff are "magical" functions --- there are a
preset number of them, described here:

    http://www.python.org/doc/current/ref/specialnames.html

The reason they're magical is because they get called when we do unusual
stuff.  For example, let's say that we're trying to represent
Red/Green/Blue color triples in some program:

###
>>> class Color:
...     def __init__(self, r, g, b):
...         self.r, self.g, self.b = r, g, b
...     def add(self, other):
...         return Color(self.r + other.r,
...                      self.g + other.g,
...                      self.b + other.b)
... 
>>> c1 = Color(0, 0, 0)
>>> c2 = Color(3, 4, 7)
>>> c3 = c1.add(c2)
>>> c3.r, c3.g, c3.b
(3, 4, 7)
###

It might be nice to be able to say something like:

###
>>> c3 = c1 + c2
Traceback (innermost last):
  File "<stdin>", line 1, in ?
TypeError: __add__ nor __radd__ defined for these operands
###

but, as the error message suggests, Python doesn't know what it means to
'+' two things together... that is, until we define an __add__ method:

###
>>> class Color:
...     def __init__(self, r, g, b):
...         self.r, self.g, self.b = r, g, b
...     def add(self, other):
...         return Color(self.r + other.r,
...                      self.g + other.g,
...                      self.b + other.b)
...     def __add__(self, other): return self.add(other)
... 
>>> c1 = Color(1, 2, 3)
>>> c2 = Color(4, 5, 6)
>>> c3 = c1 + c2
>>> c3.r, c3.g, c3.b
(5, 7, 9)
###

And now it works!  "__function__(arg)" functions are hooks to make things
like addition look more natural.  Take a look at the documentation on the
link above, and you'll see a bunch of interesting stuff.  (In the C++
language, we're "overloading operators".)


> __function(arg), and _function(arg) conventions.

The single underscore '_function(arg)', is meant to tell someone reading
the code to not fiddle around with it too much --- a '_function(arg)'
implies to a person that it's supposed to be for internal use only by the
class.

For example, if we were to write a Fraction class:

###
def gcd(a, b):
    if b == 0: return a
    return gcd(b, a % b)

class Fraction:                                                             
    def __init__(self, n, d):
        self.n, self.d = n, d
        self._reduce()

    def __add__(self, other):
        return Fraction(self.n * other.d + other.n*self.d,
                        self.d * other.d)
    def _reduce(self):
        denom = gcd(self.n, self.d)
        self.n = self.n / denom
        self.d = self.d / denom
###

The single leading underscore in '_reduce' tells someone not to call
_reduce() explicitly; it's a small utility member of the Fraction class,
and we shouldn't have to call it ourselves when using a Fraction.

'__function(arg)" implies the same kind of protection, but doubly so: it
gets Python to mangle up the function names so that it becomes very hard
for the outside to directly call '__function(arg)'.  To tell the truth,
I've never reallly felt the need to use this.


Hope this helps!