'Why does returned object not get attributes & methods of the class after __new__?
Index objects of diskcahe have the property _cache with Cache object inside it. Cache is created with few arguments from those in Index. Unfortunately, it can take into account not all arguments, that is necessary for me. I had two options: either editing the package that will decrease universality of my code or using the method fromcache wherein I can put Cache with necessary arguments. I chose the latter.
I would like to add some useful attributes & methods to an object from Index.fromcache(), & to make its type GeoCache. I made the next class for this aim:
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 = {}
def __getitem__(self, key):
try:
return super().__getitem__(self.request_map[key])
except KeyError:
query = self.getGeocodeData(key).current_result
self.request_map[key] = query.address
if query.address not in self.request_map.values():
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()
But it produces only Index object. For instance, calling close gives AttributeError: 'Index' object has no attribute 'close'. Why does object not take class methods & attributes after __new__?
The example of using:
geocache = GeoCache(GEOCACHE_PATH, size_limit=10*(1<<30)) #Creates the cache in specified directory & with necessary size limit
geocache["п. Костино, Рыбновский р-н, Рязанская обл"] #Geocode a request, cache & return the result
geocache["п. Костино, Рыбновский р-н, Рязанская обл"] #Return the result from the cache
geocache.close() #Save map "request : real address" & close the cache
Solution 1:[1]
So, the problem is fundamentally that your GeoCache.__new__ doesn't return a GeoCache instance, it returns an Index instance.
geocache = GeoCache(GEOCACHE_PATH, size_limit=10*(1<<30))
print(isinstance(geocache, GeoCache)
will print False.
And of course, instances of parent classes do not have access to child-class namespaces. You wouldn't expect index = Index(whatever) to be able to access a method you only defined in a subclass, would you?
Furthermore, for the instance attributes, __init__ is called only if __new__ returns an instance of that class.
One hack you can do to work around this is just "fix" the type of your instance by changing to to GeoCache, so:
class GeoCache(Index):
...
def __new__(cls, *args, **kwargs):
instance = Index.fromcache(Cache(*args, **kwargs))
instance.__class__ = GeoCache
return instance
Note, if Index is a built-in class or defined as a C-extension, then this probably won't work. If it is defined in python, then it could work.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | juanpa.arrivillaga |
