Home > Mobile >  Why does object not get attributes & methods of the class after __new__ in python?
Why does object not get attributes & methods of the class after __new__ in python?

Time:04-07

Index objects of diskcahe have the property _cache with Cache object inside it. Cache is created with arguments from those in Index. Unfortunately, it takes into account not all arguments, some needed for me is amongst them. I had two choices: either editing the package that will decrease universality of my code or using the method fromcache wherein I can put Cache with necessary properties. I chose the latter.

I would like to add some attributes & methods to 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

CodePudding user response:

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.

  • Related