Почему объект не получает атрибуты и методы класса после __new__ в Python?
Index объекты diskcahe имеют свойство _cache с объектом Cache внутри него. 'Cache' создается с аргументами, передаваемыми в Index. К сожалению, последний учитывает не все аргументы, среди которых нужные мне. Таким образом у меня было два выхода: либо редактировать пакет, что снизит универсальность моего кода, либо использовать метод fromcache у Index, в который я могу отправить Cache с нужными свойствами. Я решил попробовать последнее.
При этом я хотел бы добавить некоторые атрибуты и методы к этому объекту из Index.fromcache() и сделать его тип GeoCache. Я сконструировал следующий класс для этой цели:
from __future__ import annotations
from typing import *
from collections.abc import *
import re
import geocoder
from geocoder.arcgis import ArcgisResult
from geocoder.yandex import YandexResult
from diskcache import Index, Cache
class GeoCache(Index):
CYRILLIC_LETTERS_PATTERN = re.compile(r"[А-Я]")
def __new__(cls, *args, **kwargs):
return Index.fromcache(Cache(*args, **kwargs))
def __init__(self, *args, **kwargs):
try:
self.request_map = self.cache["request-address"]
except KeyError:
self.request_map = self.cache["request-address"] = {}
def __getitem__(self, key):
try:
return super().__getitem__(self.request_map[key])
except KeyError:
query = self.getGeocodeData(key).current_result
GeoCache.request_map[key] = query.address
self[query.address] = query
return query
@classmethod
def getGeocodeData(cls, address: str) -> geocoder.api.ArcgisQuery | geocoder.api.YandexQuery:
n = 10
def call() -> geocoder.api.ArcgisQuery | geocoder.api.YandexQuery:
try:
if "Russia" in address:
return geocoder.yandex(location=address, key=YANDEX_APIKEY, lang="en_RU")
if re.search(cls.CYRILLIC_LETTERS_PATTERN, address):
return geocoder.yandex(location=address, key=YANDEX_APIKEY, lang="ru_RU")
return geocoder.arcgis(location=address)
except:
raise Exception(address)
for _ in range(n):
response = call()
if response.ok:
return response
raise Exception(f"I got error {n} times in row with {address}")
def close(self) -> NoReturn:
self.cache["request-address"] = self.request_map
super().cache.close()
Но он выдаёт только объект Index. Например, вызов close дает AttributeError: 'Index' object has no attribute 'close'. Почему объект не принимает методы и атрибуты класса после __new__?
Ответы (2 шт):
Объект вызывает метод __init__ и получает все свойства класса только в том случае, если его класс при выходе из __new__ совпадает с блоком класса, в котором __new__ прописан. Таким образом, проблему можно решить через instance.__class__ = cls.
class GeoCache(Index):
...
@classmethod
def fromcache(cls, *args, **kwargs):
instance = super().fromcache(*args, **kwargs)
if cls.__init__ is not super().__init__:
instance.__init__()
Но, я бы не рекомендовал использовать __init__, библиотека этого явно не позволяет. По сути Ваш код начинает зависеть на внутреннюю реазилацию библиотеки, и есть большая вероятность, что все сломается даже при смене минорной версии библиотеки.
Поэтому лучше использовать свой, кастомный метод для дополнительной инициализации обьекта:
class BaseGeoCache(Index):
@classmethod
def fromcache(cls, *args, post_init=None, **kwargs):
cache = Cache(*args, **kwargs)
instance = super().fromcache(cache)
instance.post_init(**(post_init or {}))
return instance
def post_init(self, **kwargs):
...
class GeoCache(BaseGeoCache):
def post_init(self, default_request_address):
try:
self.request_map = self.cache["request-address"]
except KeyError:
self.request_map = self.cache["request-address"] = default_request_address
Теперь Вы полностью контролируете инициализацию обьекта:
obj = GeoCache.fromcache(..., post_init={"default_request_address": {}})