Method default argument whose type is the class not yet defined

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sat Nov 10 20:13:26 EST 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'>


Back to your class:

> ...     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



More information about the Python-list mailing list