Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

__new__ in fractions module

I have been trying to understand __new__ and metaprogramming. So I had a look at official python source code.

http://hg.python.org/cpython/file/2.7/Lib/fractions.py

Their __new__ function for Fractions looks like:

class Fraction(Rational):

    def __new__(cls, numerator=0, denominator=None):
        """Constructs a Fraction. ...        """

        self = super(Fraction, cls).__new__(cls)
        ...

        if isinstance(numerator, float):
                # Exact conversion from float
                value = Fraction.from_float(numerator)
                self._numerator = value._numerator
                self._denominator = value._denominator
                return self

    @classmethod
    def from_float(cls, f):
        """Converts a finite float to a rational number, exactly. ..."""
        # ...
        return cls(*f.as_integer_ratio())

Why do they return self, rather than

return Fraction.from_float(numerator)

I thought I understood it, but now I'm confused.

(Edit)

To support subclassing, would it make any difference to change to

return cls.from_float(numerator)
like image 343
Richard Avatar asked Sep 30 '22 22:09

Richard


1 Answers

The code in Fractions was a complicated place to start. It is best to build up an understanding step by step.

1) object.__new__ is the root tool for creating instances of new-style classes:

o = object()

2) A class that defines __new__ typically calls object.__new__ to do its work:

class A(object):
    def __new__(cls):
        return object.__new__(cls)

a = A()

3) A classmethod typically relies on the class's __new__ to do its work:

class A(object):

    def __new__(cls):
        return object.__new__(cls)

    @classmethod
    def from_something(cls):
        return cls.__new__()

With that understanding in place, you can see what the fractions module is doing:

1) from_float() calls cls(numerator, denominator) to delegate the instance creation to the Fraction.__new__() method.

2) The Fraction.__new__() method uses super() to call object.__new__() to do the actual instance creation.

Addenda

@Blckknght wants to know why value isn't returned directly (after it is created, its numerator and demoninator are copies into self before the value is throw-away. The answer is that Fraction.from_float creates an instance of Fraction rather than cls. In other words, the code is providing support for subclassing:

>>> from fractions import Fraction
>>> class MyFraction(Fraction):
        pass

>>> f = MyFraction(2, 3)
>>> f
Fraction(2, 3)
>>> f.__class__               # this MUST be MyFraction, not a Fraction
<class '__main__.MyFraction'>

Edit

@richard wants to know "why not use: return cls.from_float(numerator)"?

It's called the Open-Closed Principle. A subclasser should be able to override from_float() without unintentionally breaking __init__():

>>> class MyFraction(Fraction):
        def from_float(cls, *args):
        raise NotImplemented


>>> f = MyFraction(5.75)
>>> f
Fraction(23, 4)
like image 85
Raymond Hettinger Avatar answered Oct 04 '22 19:10

Raymond Hettinger