Странное поведение при создании подклассов 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)