Дизайн Python - инициализация, установка и получение атрибутов класса

У меня есть класс, в котором метод должен сначала проверить наличие атрибута и в противном случае вызвать функцию для его вычисления. Затем, гарантируя, что атрибут не None, он выполняет некоторые операции с ним. Я вижу два немного разных варианта дизайна:

class myclass():
    def __init__(self):
        self.attr = None

    def compute_attribute(self):
        self.attr = 1

    def print_attribute(self):
        if self.attr is None:
            self.compute_attribute()
        print self.attr

А также

class myclass2():
    def __init__(self):
        pass

    def compute_attribute(self):
        self.attr = 1
        return self.attr

    def print_attribute(self):
        try:
            attr = self.attr
        except AttributeError:
            attr = self.compute_attribute()
        if attr is not None:
            print attr

В первом проекте мне нужно убедиться, что все атрибуты класса установлены в None заранее, что может стать многословным, но и прояснить структуру объекта.

Второй вариант кажется наиболее широко используемым. Однако для моих целей (научные вычисления, связанные с теорией информации) использую try except везде блоки могут быть немного излишними, учитывая, что этот класс на самом деле не взаимодействует с другими классами, он просто берет данные и вычисляет кучу вещей.

2 ответа

Во-первых, вы можете использовать hasattr чтобы проверить, есть ли у объекта атрибут, он возвращает True если атрибут существует.

hasattr(object, attribute) # will return True if the object has the attribute

Во-вторых, вы можете настроить доступ к атрибутам в Python, вы можете узнать больше об этом здесь: https://docs.python.org/2/reference/datamodel.html

По сути, вы отменяете __getattr__ Способ достижения этого, так что-то вроде:

класс myclass2 (): def init(self): pass

def compute_attr(self):
    self.attr = 1
    return self.attr

def print_attribute(self):
    print self.attr

def __getattr__(self, name):
    if hasattr(self, name) and getattr(self, name)!=None:
        return getattr(self, name):
    else:
        compute_method="compute_"+name; 
        if hasattr(self, compute_method):
            return getattr(self, compute_method)()

Убедитесь, что вы используете только getattr чтобы получить доступ к атрибуту в __getattr__ или вы закончите бесконечной рекурсией

Основываясь на ответе Джоншарпа, я предлагаю третий вариант дизайна. Идея здесь заключается в том, что никакая специальная условная логика вообще не требуется ни клиентами MyClass или по коду в MyClass сам. Вместо этого декоратор применяется к функции, которая выполняет (гипотетически дорогое) вычисление свойства, и затем этот результат сохраняется.

Это означает, что дорогостоящие вычисления выполняются лениво (только если клиент пытается получить доступ к свойству) и выполняются только один раз.

def lazyprop(fn):
    attr_name = '_lazy_' + fn.__name__

    @property
    def _lazyprop(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))
        return getattr(self, attr_name)

    return _lazyprop


class MyClass(object):
    @lazyprop
    def attr(self):
        print('Generating attr')
        return 1

    def __repr__(self):
        return str(self.attr)


if __name__ == '__main__':
    o = MyClass()
    print(o.__dict__, end='\n\n')
    print(o, end='\n\n')
    print(o.__dict__, end='\n\n')
    print(o)

Выход

{}

Generating attr
1

{'_lazy_attr': 1}

1

редактировать

Применение ответа Cyclone к контексту OP:

class lazy_property(object):
    '''
    meant to be used for lazy evaluation of an object attribute.
    property should represent non-mutable data, as it replaces itself.
    '''

    def __init__(self, fget):
        self.fget = fget
        self.func_name = fget.__name__

    def __get__(self, obj, cls):
        if obj is None:
            return None
        value = self.fget(obj)
        setattr(obj, self.func_name, value)
        return value


class MyClass(object):
    @lazy_property
    def attr(self):
        print('Generating attr')
        return 1

    def __repr__(self):
        return str(self.attr)


if __name__ == '__main__':
    o = MyClass()
    print(o.__dict__, end='\n\n')
    print(o, end='\n\n')
    print(o.__dict__, end='\n\n')
    print(o)

Вывод идентичен приведенному выше.

Другие вопросы по тегам