0

What is the difference between using a special method and just defining a normal class method? I was reading this site which lists a lot of them.

For example it gives a class like this.

class Word(str):
    '''Class for words, defining comparison based on word length.'''

    def __new__(cls, word):
        # Note that we have to use __new__. This is because str is an immutable
        # type, so we have to initialize it early (at creation)
        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')] # Word is now all chars before first space
        return str.__new__(cls, word)

    def __gt__(self, other):
        return len(self) > len(other)
    def __lt__(self, other):
        return len(self) < len(other)
    def __ge__(self, other):
        return len(self) >= len(other)
    def __le__(self, other):
        return len(self) <= len(other)

For each of those special methods why can't I just make a normal method instead, what are they doing different? I think I just need a fundamental explanation that I can't find, thanks.

Tim Peters
  • 67,464
  • 13
  • 126
  • 132
Amon
  • 2,725
  • 5
  • 30
  • 52
  • check out this question, http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python – Asterisk Oct 22 '13 at 04:47

5 Answers5

4

It is a pythonic way to do this:

word1 = Word('first')
word2 = Word('second')
if word1 > word2:
    pass

instead of direct usage of comparator method

NotMagicWord(str):
    def is_greater(self, other)
        return len(self) > len(other)

word1 = NotMagicWord('first')
word2 = NotMagicWord('second')
if word1.is_greater(word2):
    pass

And the same with all other magic method. You define __len__ method to tell python its length using built-in len function, for example. All magic method will be called implicitly while standard operations like binary operators, object calling, comparision and a lot of other. A Guide to Python's Magic Methods is really good, read it and see what behavior you can give to your objects. It similar to operator overloading in C++, if you are familiar with it.

Anton Egorov
  • 1,174
  • 1
  • 11
  • 21
2

A method like __gt__ is called when you use comparison operators in your code. Writing something like

value1 > value2

Is the equivalent of writing

value1.__gt__(value2)
aychedee
  • 24,871
  • 8
  • 79
  • 83
1

Special methods are handled specially by the rest of the Python language. For example, if you try to compare two Word instances with <, the __lt__ method of Word will be called to determine the result.

jwodder
  • 54,758
  • 12
  • 108
  • 124
1

The magic methods are called when you use <, ==, > to compare the objects. functools has a helper called total_ordering that will fill in the missing comparison methods if you just define __eq__ and __gt__.

Because str already has all the comparison operations defined, it's necessary to add them as a mixin if you want to take advantage of total_ordering

from functools import total_ordering

@total_ordering
class OrderByLen(object):
    def __eq__(self, other):
        return len(self) == len(other)
    def __gt__(self, other):
        return len(self) > len(other)


class Word(OrderByLen, str):
    '''Class for words, defining comparison based on word length.'''

    def __new__(cls, word):
        # Note that we have to use __new__. This is because str is an immutable
        # type, so we have to initialize it early (at creation)
        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')] # Word is now all chars before first space
        return str.__new__(cls, word)


print Word('cat') < Word('dog')         # False
print Word('cat') > Word('dog')         # False
print Word('cat') == Word('dog')        # True
print Word('cat') <= Word('elephant')   # True
print Word('cat') >= Word('elephant')   # False
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
1

"Magic methods" are used by Python to implement a lot of its underlying structure.

For example, let's say I have a simple class to represent an (x, y) coordinate pair:

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

So, __init__ would be an example of one of these "magic methods" -- it allows me to automatically initialize the class by simply doing Point(3, 2). I could write this without using magic methods by creating my own "init" function, but then I would need to make an explicit method call to initialize my class:

class Point(object):
    def init(self, x, y):
        self.x = x
        self.y = y
        return self


p = Point().init(x, y)

Let's take another example -- if I wanted to compare two point variables, I could do:

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

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

This lets me compare two points by doing p1 == p2. In contrast, if I made this a normal eq method, I would have to be more explicit by doing p1.eq(p2).

Basically, magic methods are Python's way of implementing a lot of its syntactic sugar in a way that allows it to be easily customizable by programmers.

For example, I could construct a class that pretends to be a function by implementing __call__:

class Foobar(object):
    def __init__(self, a):
        self.a = a

    def __call__(self, b):
        return a + b

f = Foobar(3)
print f(4)  # returns 7

Without the magic method, I would have to manually do f.call(4), which means I can no longer pretend the object is a function.

Michael0x2a
  • 58,192
  • 30
  • 175
  • 224
  • Okay I'm understanding more, I like seeing those comparisons. Most sites just use them without telling us why. Also, when using the magic methods, do they always `return` the result? Could they print it? Or do something else? – Amon Oct 22 '13 at 06:07
  • @Amon -- In general, they usually return something, so that whoever called the function can do whatever they want with the result. The caller might want to write the information to a file instead of printing it, for example. However, note that people can do (and have done) all sorts of crazy things with magic methods, so there's always exceptions to the rule. – Michael0x2a Oct 22 '13 at 06:13
  • Okay thanks, and for your second example. If we were comparing two points: `p1` and `p2` those two points would first have to be initialized right? I'd first need say `p1(2,3)` and `p2(5,6)` for example? Then I could do `p1 == p2` – Amon Oct 22 '13 at 22:36
  • @Amon -- yep, that's correct! Magic methods, like normal functions, really only work between *instances* of an object. – Michael0x2a Oct 23 '13 at 01:32
  • Hey sorry I was just looking over this again and was confused about something. In this line: `self.x == other.x and self.y == other.y` what are `other.x` and `other.y` doing? I tried removing them so the expression would just be `self.x == self.y` and the output would always be `false` as a result – Amon Oct 29 '13 at 03:18
  • @Amon -- so, the `__eq__` function compares two points and returns `True` if they're equal. It currently takes in two parameters -- `self`, which represents the current point object, and `other`, which represents the separate point object you're comparing it to. If we did `p1 == p2`, then `p1` would be `self`, and `p2` would be other (if we did `p2 == p1`, it'd be the opposite). When we do `self.x == other.x`, we are asking if the x coordinates of the two points are equal. If we were to do `self.x == self.y`, then we are asking if the x and y coordinates of just `p1` are equal. – Michael0x2a Oct 29 '13 at 03:52
  • Oh okay, I didn't know that the whole instance was a parameter. I was looking at the individual integers and thinking they were the arguments. So for `Point(2,3)` I thought that `2` was `self` and `3` was `other`. I looked at it the same way as I did for `__init__`. So does that mean I can only compare instances to other instances? Could I compare an instance to variable? – Amon Oct 29 '13 at 04:15
  • 1
    @Amon -- The thing you have to realize is that you can plug in or compare whatever variable you want. If I do `Point(2, 3)`, then the `__init__` function is being called. The `self` parameter is _always_ the current object, and the `x` and `y` parameters can be whatever you want. Doing `p1 = Point("hello", "world")` will work (although it'll probably break your code!), and `p1.x` will return `"hello"`. When you're doing `a == b`, Python is (under the covers), transforming that into `a.__eq__(b)`. So, `p1 == p2` is just `p1.__eq__(p2)`. Doing `p1 == 3` would translate to `p1.__eq__(3)`. – Michael0x2a Oct 29 '13 at 07:35
  • Okay great I think I got it, sorry one last question, would it make sense to try to compare instances of different classes? – Amon Oct 29 '13 at 23:48
  • 1
    @Amon -- sometimes. For example, it might be useful to compare an `int` class to a `float` class to check for equality. I'm sure there are loads of other cases where you want to compare two different classes that could be seen as similar in some way. – Michael0x2a Nov 03 '13 at 22:01