views:

728

answers:

6

Why doesn't the following work (Python 2.5.2)?

>>> import datetime
>>> class D(datetime.date):
        def __init__(self, year):
            datetime.date.__init__(self, year, 1, 1)
>>> D(2008)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function takes exactly 3 arguments (1 given)

I wanted to create a class that was just like datetime.date, but with a different __init__ function. Apparently my function never gets called. Instead the original datetime.date.__init__ is called and fails because that expects 3 arguments and I am passing in one.

What's going on here? And is this a clue?

>>> datetime.date.__init__
<slot wrapper '__init__' of 'object' objects>

Thanks!

+3  A: 

Here's the answer, and a possible solution (use a function or strptime instead of subclassing)

http://www.mail-archive.com/[email protected]/msg192783.html

Vinko Vrsalovic
Thank you, the link to the problem is right on the money, but there is not an explanation __why__ it didn't work. What's the root of the problem and can it be overcome?
scrible
A: 

You should probably use a factory function instead of creating a subclass:

def first_day_of_the_year(year):
  return datetime.date(year, 1, 1)
orip
Yeah, that's cool, but I am curious as to why the subclassing does not work (how it being an extension class makes the difference) and whether this can be overcome...
scrible
+3  A: 

You're function isn't being bypassed; Python just never gets to the point where it would call it. Since datetime is implemented in C, it does its initialization in datetime.__new__ not datetime.__init__. (Note that this could change sometime.) You could presumably get around this by overriding __new__ instead of __init__. But as other people have suggested, the best way is probably not subclassing datetime at all.

Benjamin Peterson
@Benjamin: Please check my answer, and consider correcting yours, since it's the most voted so far; basically, only your last sentence could be considered helpful; the others are misinformed/ing. Also, please fix your "it's"​→"its" and "(Not"​→"(Note".
ΤΖΩΤΖΙΟΥ
A: 

You can wrap it and add extended functionality to your wrapper.

Here is an example:

class D2(object):
    def __init__(self, *args, **kwargs):
        self.date_object = datetime.date(*args, **kwargs)

    def __getattr__(self, name):
        return getattr(self.date_object, name)

And here is how it works:

>>> d = D2(2005, 10, 20)
>>> d.weekday()
3
>>> dir(d)
['__class__', '__delattr__', '__dict__', '__doc__', '__getattr__',
 '__getattribute__', '__hash__', '__init__', '__module__', '__new__',
 '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__',
 '__weakref__', 'date_object']
>>> d.strftime('%d.%m.%Y')
'20.10.2005'
>>>

Note that dir() doesn't list datetime.dates attributes.

muhuk
@ΤΖΩΤΖΙΟΥ: You are right. It is actually subclassable. I will go back to docs and find out where I made this mistake. Meanwhile I'm fixing the answer. Thank you.
muhuk
+8  A: 

Regarding several other answers, this doesn't have anything to do with dates being implemented in C per se. The __init__ method does nothing because they are immutable objects, therefore the constructor (__new__) should do all the work. You would see the same behavior subclassing int, str, etc.

>>> import datetime
>>> class D(datetime.date):
        def __new__(cls, year):
            return datetime.date.__new__(cls, year, 1, 1)


>>> D(2008)
D(2008, 1, 1)
Coady
I hope you won't mind a little emphasis I added.
ΤΖΩΤΖΙΟΥ
Great answer, I was really unclear on this before but now it makes perfect sense.
Kiv
+2  A: 

Please read the Python reference on Data model, especially about the __new__ special method.

Excerpt from that page (my italics):

__new__() is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customize class creation.

datetime.datetime is also an immutable type.

PS If you think that:

  • an object implemented in C cannot be subclassed, or
  • __init__ doesn't get called for C implemented objects, only __new__

then please try it:

>>> import array
>>> array
<module 'array' (built-in)>
>>> class A(array.array):
    def __init__(self, *args):
     super(array.array, self).__init__(*args)
     print "init is fine for objects implemented in C"

>>> a=A('c')
init is fine for objects implemented in C
>>>
ΤΖΩΤΖΙΟΥ