Velocity Reviews > Method default argument whose type is the class not yet defined

# Method default argument whose type is the class not yet defined

Jennie
Guest
Posts: n/a

 11-10-2012
What is the best solution to solve the following problem in Python 3.3?

import math
>>> class Point:

.... def __init__(self, x=0, y=0):
.... self.x = x
.... self.y = y
.... def __sub__(self, other):
.... return Point(self.x - other.x, self.y - other.y)
.... def distance(self, point=Point()):
.... """Return the distance from `point`."""
.... return math.sqrt((self - point).x ** 2 + (self - point).y ** 2)
....
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in Point
NameError: name 'Point' is not defined

I propose three solutions. The first one:

>>> class Point:

.... def __init__(self, x=0, y=0):
.... self.x = x
.... self.y = y
.... def __sub__(self, other):
.... return Point(self.x - other.x, self.y - other.y)
.... def distance(self, point=None):
.... p = point if point else Point()
.... return math.sqrt((self - p).x ** 2 + (self - p).y ** 2)
....
>>> p = Point()
>>> p.distance()

0.0
>>> p.distance(Point(3, 4))

5.0

The second one:

>>> class Point:

.... def __init__(self, x=0, y=0):
.... self.x = x
.... self.y = y
.... def __sub__(self, other):
.... return Point(self.x - other.x, self.y - other.y)
....
>>> def distance(self, point=Point()):

.... return math.sqrt((self - point).x ** 2 + (self - point).y ** 2)
....
>>> Point.distance = distance
>>> p = Point()
>>> p.distance(Point(3, 4))

5.0

The last one:

>>> class Point:

.... def __init__(self, x=0, y=0):
.... self.x = x
.... self.y = y
.... Point.distance = distance
.... def __sub__(self, other):
.... return Point(self.x - other.x, self.y - other.y)
....
>>> def distance(self, point=Point()):

.... return math.sqrt((self - point).x ** 2 + (self - point).y ** 2)
....
>>> p = Point()
>>> p.distance(Point(3, 4))

5.0

Is there a better solution?
--
Jennie

Chris Angelico
Guest
Posts: n/a

 11-10-2012
On Sun, Nov 11, 2012 at 6:33 AM, Jennie <(E-Mail Removed)> wrote:
> ... def distance(self, point=None):
> ... p = point if point else Point()

I'd go with this one. Definitely not the third one, which mutates the
class according to a current global every time a Point is instantiated
- could be *extremely* confusing if the name distance were ever
rebound. You could also fiddle with the default args:

>>> class Point:

def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
def distance(self, point="Point()"):
return math.sqrt((self - p).x ** 2 + (self - p).y ** 2)

>>> Point.distance.__defaults__=Point(), # has to be a tuple

ChrisA

Terry Reedy
Guest
Posts: n/a

 11-10-2012
On 11/10/2012 2:33 PM, Jennie wrote:
> What is the best solution to solve the following problem in Python 3.3?
>
> import math
> >>> class Point:

> ... def __init__(self, x=0, y=0):
> ... self.x = x
> ... self.y = y
> ... def __sub__(self, other):
> ... return Point(self.x - other.x, self.y - other.y)
> ... def distance(self, point=Point()):
> ... """Return the distance from `point`."""
> ... return math.sqrt((self - point).x ** 2 + (self - point).y ** 2)
> ...
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> File "<stdin>", line 5, in Point
> NameError: name 'Point' is not defined
>
> I propose three solutions. The first one:
>
> >>> class Point:

> ... def __init__(self, x=0, y=0):
> ... self.x = x
> ... self.y = y
> ... def __sub__(self, other):
> ... return Point(self.x - other.x, self.y - other.y)
> ... def distance(self, point=None):
> ... p = point if point else Point()
> ... return math.sqrt((self - p).x ** 2 + (self - p).y ** 2)
> ...
> >>> p = Point()
> >>> p.distance()

> 0.0
> >>> p.distance(Point(3, 4))

> 5.0

What I do not like about this one is that it creates a new 0 point each
time one is needed. Two solutions:

change Point() to point0 in the distance function and create
point0 = Point()
after the class.

-or-
px,py = point.x, point.y if point else 0.0, 0.0

> The second one:
>
> >>> class Point:

> ... def __init__(self, x=0, y=0):
> ... self.x = x
> ... self.y = y
> ... def __sub__(self, other):
> ... return Point(self.x - other.x, self.y - other.y)
> ...
> >>> def distance(self, point=Point()):

> ... return math.sqrt((self - point).x ** 2 + (self - point).y ** 2)
> ...
> >>> Point.distance = distance
> >>> p = Point()
> >>> p.distance(Point(3, 4))

> 5.0

my first thought

> The last one:
>
> >>> class Point:

> ... def __init__(self, x=0, y=0):
> ... self.x = x
> ... self.y = y
> ... Point.distance = distance
> ... def __sub__(self, other):
> ... return Point(self.x - other.x, self.y - other.y)
> ...
> >>> def distance(self, point=Point()):

> ... return math.sqrt((self - point).x ** 2 + (self - point).y ** 2)
> ...
> >>> p = Point()
> >>> p.distance(Point(3, 4))

> 5.0
>
> Is there a better solution?

--
Terry Jan Reedy

Jennie
Guest
Posts: n/a

 11-10-2012
On 11/10/2012 09:29 PM, Terry Reedy wrote:

> On 11/10/2012 2:33 PM, Jennie wrote:
>>
>> I propose three solutions. The first one:
>>
>> >>> class Point:

>> ... def __init__(self, x=0, y=0):
>> ... self.x = x
>> ... self.y = y
>> ... def __sub__(self, other):
>> ... return Point(self.x - other.x, self.y - other.y)
>> ... def distance(self, point=None):
>> ... p = point if point else Point()
>> ... return math.sqrt((self - p).x ** 2 + (self - p).y ** 2)

> What I do not like about this one is that it creates a new 0 point each
> time one is needed. Two solutions:
>
> change Point() to point0 in the distance function and create
> point0 = Point()
> after the class.
>
> -or-
> instead of p = line,
> px,py = point.x, point.y if point else 0.0, 0.0

Thanks, I like the second one

--
Jennie

Jennie
Guest
Posts: n/a

 11-10-2012
On 11/10/2012 09:29 PM, Terry Reedy wrote:

> On 11/10/2012 2:33 PM, Jennie wrote:
>>
>> I propose three solutions. The first one:
>>
>> >>> class Point:

>> ... def __init__(self, x=0, y=0):
>> ... self.x = x
>> ... self.y = y
>> ... def __sub__(self, other):
>> ... return Point(self.x - other.x, self.y - other.y)
>> ... def distance(self, point=None):
>> ... p = point if point else Point()
>> ... return math.sqrt((self - p).x ** 2 + (self - p).y ** 2)

> What I do not like about this one is that it creates a new 0 point each
> time one is needed. Two solutions:
>
> change Point() to point0 in the distance function and create
> point0 = Point()
> after the class.
>
> -or-
> instead of p = line,
> px,py = point.x, point.y if point else 0.0, 0.0

Thanks, I like the second one

--
Jennie

Dave Angel
Guest
Posts: n/a

 11-10-2012
On 11/10/2012 03:51 PM, Jennie wrote:
> On 11/10/2012 09:29 PM, Terry Reedy wrote:
>
>> On 11/10/2012 2:33 PM, Jennie wrote:
>>>
>>> I propose three solutions. The first one:
>>>
>>> >>> class Point:
>>> ... def __init__(self, x=0, y=0):
>>> ... self.x = x
>>> ... self.y = y
>>> ... def __sub__(self, other):
>>> ... return Point(self.x - other.x, self.y - other.y)
>>> ... def distance(self, point=None):
>>> ... p = point if point else Point()
>>> ... return math.sqrt((self - p).x ** 2 + (self - p).y ** 2)

>
>> What I do not like about this one is that it creates a new 0 point each
>> time one is needed. Two solutions:
>>
>> change Point() to point0 in the distance function and create
>> point0 = Point()
>> after the class.
>>
>> -or-
>> instead of p = line,
>> px,py = point.x, point.y if point else 0.0, 0.0

>
> Thanks, I like the second one
>

I like the first, once you fix the minor inefficiency in it; add the
qualifier "is None"

.... def distance(self, point=None):
.... p = point if point is None else Point()
.... return math.sqrt((self - p).x ** 2 + (self - p).y ** 2)

The advantage it then has over the second one is that the whole class is
defined inside the class.

--

DaveA

Steven D'Aprano
Guest
Posts: n/a

 11-11-2012
On Sat, 10 Nov 2012 20:33:05 +0100, Jennie wrote:

[...]
> I propose three solutions. The first one:
>
> >>> class Point:

> ... def __init__(self, x=0, y=0):
> ... self.x = x
> ... self.y = y
> ... def __sub__(self, other):
> ... return Point(self.x - other.x, self.y - other.y)

Don't do this, because it breaks subclassing. Your instance should
dynamically get it's own class, not hard-code the name of Point.

return self.__class__(self.x - other.x, self.y - other.y)

That way, when you subclass Point, you can do arithmetic on the subclass
instances and they will do the Right Thing.

Note: Python's builtin numeric types don't do this, and it is a damned
nuisance:

py> class MyInt(int):
.... pass
....
py> a, b = MyInt(23), MyInt(42)
py> assert type(a) is MyInt and type(b) is MyInt
py> type(a + b)
<type 'int'>

> ... def distance(self, point=None):
> ... p = point if point else Point()
> ... return math.sqrt((self - p).x ** 2 + (self - p).y ** 2)

Almost but not quite. I assume that, in a full Point class, you would
want Point(0, 0) to count as false in a boolean context. (A "falsey"
value, like None, [], 0.0, etc.) So change the test to an explicit test
for None, not just any falsey value:

if point is None:
point = self.__class__() # Allow subclassing to work.

> The second one:
>
> >>> class Point:

> ... def __init__(self, x=0, y=0):
> ... self.x = x
> ... self.y = y
> ... def __sub__(self, other):
> ... return Point(self.x - other.x, self.y - other.y)
> ...
> >>> def distance(self, point=Point()):

> ... return math.sqrt((self - point).x ** 2 + (self - point).y ** 2)
> ...
> >>> Point.distance = distance

Cute, but ugly and messy. You can inject methods into a class, of course,
but that's an awfully big hammer to crack this tiny little nut. Your
first solution is better.

Here is a variation which, according to your tastes, may count as more or
less ugly: inject the default value into the method:

class Point:
def distance(self, other=None): # None is a placeholder
delta = self - other
return math.sqrt(delta.x ** 2 + delta.y ** 2)

Point.distance.__defaults__ = (Point(),)
# In Python 2, use:
# Point.distance.__func__.func_defaults = (Point(),)

--
Steven

Oscar Benjamin
Guest
Posts: n/a

 11-11-2012
On 10 November 2012 19:33, Jennie <(E-Mail Removed)> wrote:
> What is the best solution to solve the following problem in Python 3.3?
>
> import math
>>>> class Point:

> ... def __init__(self, x=0, y=0):
> ... self.x = x
> ... self.y = y
> ... def __sub__(self, other):
> ... return Point(self.x - other.x, self.y - other.y)
> ... def distance(self, point=Point()):
> ... """Return the distance from `point`."""
> ... return math.sqrt((self - point).x ** 2 + (self - point).y ** 2)
> ...
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> File "<stdin>", line 5, in Point
> NameError: name 'Point' is not defined

I would use namedtuple and make it so that an ordinary tuple could be
used as in place of a Point instance:

>>> import math
>>> from collections import namedtuple
>>> class Point(namedtuple('Point', ['x', 'y'])):

.... def distance(self, other=(0, 0)):
.... (myx, myy), (theirx, theiry) = self, other
.... return math.sqrt((myx - theirx) ** 2 + (myy - theiry) ** 2)
....
>>> p = Point(3, 4)
>>> p.distance()

5.0
>>> p2 = Point(4, 5)
>>> p.distance(p2)

1.4142135623730951

Oscar

Chris Angelico
Guest
Posts: n/a

 11-11-2012
On Sun, Nov 11, 2012 at 12:13 PM, Steven D'Aprano
<(E-Mail Removed)> wrote:
> Almost but not quite. I assume that, in a full Point class, you would
> want Point(0, 0) to count as false in a boolean context. (A "falsey"
> value, like None, [], 0.0, etc.)

I would not assume that. The origin is a point, just like any other.
With a Line class, you could deem a zero-length line to be like a
zero-element list, but Point(0,0) is more like the tuple (0,0) which
is definitely True. In any case, this would not even matter, beyond
unnecessary work; the bug would occur only if you seek the distance to
Point(0,0), at which point[1] the code would throw out the incoming
Point and go with the default of 0,0. So it'd result in the same
distance.

ChrisA

[1] Sorry, couldn't resist

Ian Kelly
Guest
Posts: n/a

 11-11-2012
On Sat, Nov 10, 2012 at 7:13 PM, Chris Angelico <(E-Mail Removed)> wrote:
> I would not assume that. The origin is a point, just like any other.
> With a Line class, you could deem a zero-length line to be like a
> zero-element list, but Point(0,0) is more like the tuple (0,0) which
> is definitely True.

It's more like the number 0 than the tuple (0,0).

0 is the origin on a 1-dimensional number line.
(0,0) is the origin on a 2-dimensional number plane.

In fact, it might be pointed out that Point(0, 0) is a generalization
of 0+0j, which is equal to 0.