Странное поведение при создании подклассов datetime.timedelta
Для удобства я хочу создать подклассы datetime.timedelta. Идея состоит в том, чтобы определить класс как таковой:
class Hours(datetime.timedelta):
def __init__(self, hours):
super(Hours, self).__init__(hours=hours)
Таким образом, я могу быстро создавать timedeltas так:
x = Hours(n)
Однако приведенный выше код производит timedelta из n дней вместо n часов. В качестве примера рассмотрим следующую сессию ipython:
In [1]: import datetime
In [2]: class Hours(datetime.timedelta):
...: def __init__(self, hours):
...: super(Hours, self).__init__(hours=hours)
...:
In [3]: print(Hours(10))
Out[3]: 10 days, 0:00:00
Я не могу объяснить это. Есть кто-нибудь?
1 ответ:
Если вы используете
__new__, вместо__init__:import datetime as DT class Hours(DT.timedelta): def __new__(self, hours): return DT.timedelta.__new__(self, hours=hours) x = Hours(10) print(x)Выходы
10:00:00
Если вы переопределяете
__init__, но не__new__, тоDT.timedelta.__new__вызывается перед вашимHours.__init__. Примечаниеimport datetime as DT class Hours(DT.timedelta): def __init__(self, hours): print(self) x = Hours(10)Отпечатки
10 days, 0:00:00. Это показывает, чтоDT.timedelta.__new__уже установил timedelta на 10 дней, прежде чем вы даже получите возможность настроить его вHours.__init__.Более того,
DT.timedeltaявляется неизменяемым объектом-вы не можете изменитьdaysилиsecondsилиmicrosecondsпосле того, как объект был создан. Python создает неизменяемые объекты с помощью метода__new__, и обычно ничего не делает в методе__init__. Изменяемые объекты делают обратное: они настраивают объект в__init__и ничего не делают в__new__.
Per The docs :
При подклассовании неизменяемых встроенных типов, таких как числа и строки, и иногда в других ситуациях, статический метод
__new__приходит очень кстати.__new__является первым шагом в построении экземпляра, вызываемого до__init__. Метод__new__вызывается с классом в качестве его первый аргумент; его обязанность-вернуть новый экземпляр этого класс. Сравните это с__init__:__init__вызывается с экземпляром как его первый аргумент, и он ничего не возвращает; его ответственность заключается в инициализации экземпляра....Все это делается для того, чтобы неизменяемые типы могли сохранять свои неизменность при одновременном допуске подклассов.
(Если неизменяемые объекты выполняют конфигурацию в
__init__, то вы можете мутировать неизменяемый вызовомimmutable.__init__. Очевидно, мы этого не хотим, поэтомуimmutable.__init__обычно ничего не делает.)
Обратите также внимание, что если вы не планируете добавлять новые методы в свой класс
Hours, было бы проще, и поэтому лучше просто использовать функцию:def hours(hours): return DT.timedelta(hours=hours)