Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a constructor to a subclassed numeric type?

Tags:

python

I want to subclass a numeric type (say, int) in python and give it a shiny complex constructor. Something like this:

class NamedInteger(int):
    def __init__(self, value):
        super(NamedInteger, self).__init__(value)
        self.name = 'pony'

    def __str__(self):
        return self.name

x = NamedInteger(5)
print x + 3
print str(x)

This works fine under Python 2.4, but Python 2.6 gives a deprecation warning. What is the best way to subclass a numeric type and to redefine constructors for builtin types in newer Python versions?

Edit: Spotted in comments that this works without a super() line, so it could be like this:

class NamedInteger(int):
    def __init__(self, value):
        self.name = 'pony'

    def __str__(self):
        return self.name

x = NamedInteger(5)
print x + 3
print str(x)

I believe that this works because int is immutable type and has only __new__ method. However I would be glad to know a correct way of subclassing, so I could build a class with behaviour like this:

x = NamedInteger(5, 'kitty')

Second edit:

The final version now looks like this:

class NamedInteger(int):
    def __new__(cls, value, name='pony'):
        self = super(NamedInteger, cls).__new__(cls, value)
        self.name = name
        return self

    def __str__(self):
        return self.name

x = NamedInteger(5)
y = NamedInteger(3, 'kitty')
print "%d %d" % (x, y)
print "%s %s" % (x, y)

Answers below also gave very interesting links to Abstract Base Classes and numbers modules.

like image 309
abbot Avatar asked Apr 28 '10 18:04

abbot


2 Answers

You have to use __new__ instead of __init__ when you subclass immutable built-in types, e.g. :

class NamedInteger(int):

    def __new__(cls, value, name='pony'):
        inst = super(NamedInteger, cls).__new__(cls, value)
        inst.name = name
        return inst

    def __str__(self):
        return self.name

x = NamedInteger(5)
print x + 3                 # => 8   
print str(x)                # => pony
x = NamedInteger(3, "foo")  
print x + 3                 # => 6
print str(x)                # => foo
like image 118
Luper Rouch Avatar answered Oct 18 '22 05:10

Luper Rouch


As of Python 2.6, the preferred way to extend numeric types is not to directly inherit from them, but rather to register your class as a subclass of the Number abstract base class. Check out the abc module for documentation of the Abstract Base Class concept.

That module's documentation links to the numbers module, which contains the abstract base classes you can choose to declare yourself part of. So basically you'd say

import numbers
numbers.Number.register(NamedInteger)

to indicate that your class is a type of number.

Of course, the problem with this is that it requires you to implement all the various handler methods such as __add__, __mul__, etc. However, you'd really have to do this anyway, since you can't rely on the int class' implementation of those operations to do the correct thing for your class. For example, what's supposed to happen when you add an integer to a named integer?

My understanding is that the ABC approach is intended to force you to confront those questions. In this case the simplest thing to do is probably to keep an int as an instance variable of your class; in other words while you will register your class to give it the is-a relationship with Number, your implementation gives it a has-a relationship with int.

like image 30
Eli Courtwright Avatar answered Oct 18 '22 06:10

Eli Courtwright