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