51

I have a problem with return self

class Fib: 
    def __init__(self, max):
        self.max = max
    def __iter__(self): 
        self.a = 0
        self.b = 1
        return self
    def __next__(self):
        fib = self.a
        if fib > self.max:
            raise StopIteration
        self.a, self.b = self.b, self.a + self.b
        return fib

I have already seen this question return self problem but I can't understand what the benefit is of return self?

Mathias711
  • 6,568
  • 4
  • 41
  • 58
AmrElgendy
  • 571
  • 1
  • 4
  • 9

2 Answers2

91

Returning self from a method simply means that your method returns a reference to the instance object on which it was called. This can sometimes be seen in use with object oriented APIs that are designed as a fluent interface that encourages method cascading. So, for example,

>>> class Counter(object):
...     def __init__(self, start=1):
...         self.val = start
...     def increment(self):
...         self.val += 1
...         return self
...     def decrement(self):
...         self.val -= 1
...         return self
...
>>> c = Counter()

Now we can use method cascading:

>>> c.increment().increment().decrement()
<__main__.Counter object at 0x1020c1390>

Notice, the last call to decrement() returned <__main__.Counter object at 0x1020c1390>, which is self. Now:

>>> c.val
2
>>>

Notice, you cannot do this if you did not return self:

>>> class Counter(object):
...     def __init__(self, start=1):
...         self.val = start
...     def increment(self):
...         self.val += 1
...         # implicitely return `None`
...     def decrement(self):
...         self.val -= 1
...         # implicitely return `None`
...
>>> c = Counter()
>>> c.increment().increment()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'increment'
>>> c
<__main__.Counter object at 0x1020c15f8>
>>> c.val
2
>>>

Notice, not everyone is a fan of "method cascading" design. Python built-ins do not tend do this, so, list for example:

>>> x = list()
>>> x.append(1).append(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'append'
>>>

The one place you do often see this is when your class implements the iterator protocol, where iter on an iterator returns self by convention, although this is suggested by the docs:

Having seen the mechanics behind the iterator protocol, it is easy to add iterator behavior to your classes. Define an __iter__() method which returns an object with a __next__() method. If the class defines __next__(), then __iter__() can just return self:

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

Notice, this in effect makes your iterator only useful for a single pass (as it should be to properly follow the iterator protocol):

>>> x = [1, 2, 3, 4]
>>> it = iter(x)
>>> list(it)
[1, 2, 3, 4]
>>> list(it)
[]
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • 1
    @AmrElgendy you're welcome. I have added one more bit of information at the end, so check out the latest edit. – juanpa.arrivillaga Apr 12 '17 at 22:19
  • I'd imagine it's possible to modify the code and make your iterator reusable. Reset `index` to `len(data)` just before you raise StopIteration. – Timir Jul 25 '18 at 02:30
  • @Timir you probably wouldn't want to do that though, then it would become an infinite iterator. In general, iterators *should not be re-usable*. – juanpa.arrivillaga Aug 15 '18 at 16:04
  • Are we talking about method cascading or method chaining ? Apparently, in the wikipedia link you sent, these two things are different. – Marine Galantin Nov 27 '20 at 22:04
  • 1
    @MarineGalantin I think there is some ambiguity, from the link, "Cascading can be implemented in terms of chaining by having the methods return the target object (receiver, this, self)." although some might only consider it cascading if you support a particular kind of sugar. – juanpa.arrivillaga Nov 27 '20 at 22:33
2

This is needlessly complex code. Pay little attention to it. There's no reason on earth to implement it this way.

That being said, what it does is this:

class Fib: 

    """Implements the Fibonacci sequence."""

    def __init__(self, max_):
        self.max = max_

    def __iter__(self):
        """Initializes and returns itself as an iterable."""

        self.a = 0
        self.b = 1
        return self

    def __next__(self):
        """What gets run on each execution of that iterable."""

        fib = self.a
        if fib > self.max:
            raise StopIteration
        self.a, self.b = self.b, self.a + self.b  # increment
        return fib

This is all much easier to express as:

def fib(max_):
    a, b = 0, 1
    while b <= max_:
        out = a
        a, b = b, a+b
        yield out

Examples:

>>> fib_obj = Fib(20)
>>> for n in fib_obj:
...     print(n)

>>> for n in Fib(20):
...     print(n)

>>> for n in fib(20):
...     print(n)
# all give....
0
1
1
2
3
5
8
13
Adam Smith
  • 52,157
  • 12
  • 73
  • 112
  • Is that mean `self.a = 0 self.b = 1` Will be implemented for one time ?? – AmrElgendy Apr 12 '17 at 21:48
  • @AmrElgendy `__iter__` is run once to initialize an iterator (that is, an object with a `__next__` method) and `self.a` and `self.b` are used in that `__next__` method. It's a silly way to implement the object. – Adam Smith Apr 12 '17 at 21:53
  • sorry , this is last question What I understand is `__iter__` implement for one time when calling it from `__next__` that's right ?? – AmrElgendy Apr 12 '17 at 22:02
  • @AmrElgendy no, but you're digging way deeper into the language than you probably need to be. If you're interested, read the official docs. – Adam Smith Apr 12 '17 at 22:07