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)
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With