Есть ли уловка "перегрузить оператор точки"?

Я знаю, что вопрос немного странно сформулирован, но я не могу придумать другого способа сказать это. У меня есть приложение, которое имеет дело с большими объектами json, и я хочу просто сказать:

object1.value.size.whatever.attributexyz

вместо

object1.get('value').get('size').get('whatever').get('attributexyz')

Есть ли какой-нибудь умный способ поймать AttributeError, который будет поднят, и проверить внутри структуры данных, если этот атрибут соответствует любому из его значений?

Ответ 1

В определении класса object1

def __getattr__(self, key):
    return self.get(key)

Любая попытка разрешить свойство, метод или имя поля, которое фактически не существует на самом объекте, будет передано __getattr__.

Если у вас нет доступа к определению класса, т.е. что-то вроде словаря, оберните его в класс. Для словаря вы можете сделать что-то вроде:

class DictWrapper(object):
    def __init__(self, d):
        self.d = d
    def __getattr__(self, key):
        return self.d[key]

Обратите внимание, что KeyError будет поднят, если ключ недействителен; конвенция, однако, заключается в том, чтобы поднять AttributeError (спасибо, С. Лотт!). Вы можете повторно поднять KeyError как AttributeError, если хотите:

try:
    return self.get(key)
except KeyError as e:
    raise AttributeError(e)

Также помните, что если объекты, которые вы возвращаете из __getattr__, также являются, например, словарями, вам также нужно их обернуть.

Ответ 2

Оберните структуру в объект с установленным методом __getattr__(). Если у вас есть какой-либо контроль над структурой, вы можете определить ее собственный __getattr___(). Getattr делает именно то, что вы хотите - "улавливает" отсутствующие атрибуты и, возможно, возвращает некоторое значение.

Ответ 3

Просто чтобы дополнить приведенные выше ответы с помощью примера.

class A:
    def __init__(self, *args):
        self.args = args

    def dump(self, *args):
        print(self.args, args)
        return self.args, args

class Wrap:
    def __init__(self, c, init_args):
        self.c, self.init_args = c, init_args

    def __getattr__(self, key):
        inst = self.c(*self.init_args)
        return getattr(inst, key)

a = Wrap(A, (1, 2, 3))
a.dump(4, 5, 6)

b = Wrap(dict, ({1:2},))
print(b.get(1), b.get(3))

# This will fail                                                                                                                                                                                                                                  
print(b[1])

выходы,

$ python --version
Python 3.6.3
$ python wrap.py 
(1, 2, 3) (4, 5, 6)
2 None
Traceback (most recent call last):
  File "wrap.py", line 24, in <module>
    print(b[1])
TypeError: 'Wrap' object does not support indexing