Заменить свойство для увеличения производительности - PullRequest
6 голосов
/ 12 сентября 2011

Положение

Аналогично этот вопрос , я хочу заменить свойство. В отличие от этого вопроса, я не хочу переопределять его в подклассе. Я хочу заменить его в init и в самом свойстве для эффективности, чтобы он не вызывал функцию, которая вычисляет значение при каждом вызове свойства.

У меня есть класс, у которого есть свойство. Конструктор может принимать значение свойства. Если ему передано значение, я хочу заменить свойство значением (а не просто установить свойство). Это связано с тем, что само свойство вычисляет значение, что является дорогой операцией. Точно так же я хочу заменить свойство значением, рассчитанным по свойству после его расчета, чтобы будущие вызовы свойства не приходилось пересчитывать:

class MyClass(object):
    def __init__(self, someVar=None):
        if someVar is not None: self.someVar = someVar

    @property
    def someVar(self):
        self.someVar = calc_some_var()
        return self.someVar

Задача

Приведенный выше код не работает, поскольку выполнение self.someVar = не заменяет функцию someVar. Он пытается вызвать установщик свойства, который не определен.

Потенциальное решение

Я знаю, что могу достичь того же самого, немного по-другому:

class MyClass(object):
    def __init__(self, someVar=None):
        self._someVar = someVar

    @property
    def someVar(self):
        if self._someVar is None:
            self._someVar = calc_some_var()
        return self._someVar

Это будет незначительно менее эффективно, так как при каждом вызове свойства будет проверяться None. Приложение критически важно для производительности, так что это может быть или не быть достаточно хорошим.

Вопрос

Есть ли способ заменить свойство в экземпляре класса? Насколько эффективнее было бы, если бы я мог это сделать (т.е. избежать проверки None и вызова функции)?

Ответы [ 4 ]

15 голосов
/ 12 сентября 2011

То, что вы ищете, это Денис Откидач * Отличный CachedAttribute :

class CachedAttribute(object):    
    '''Computes attribute value and caches it in the instance.
    From the Python Cookbook (Denis Otkidach)
    This decorator allows you to create a property which can be computed once and
    accessed many times. Sort of like memoization.
    '''
    def __init__(self, method, name=None):
        # record the unbound-method and the name
        self.method = method
        self.name = name or method.__name__
        self.__doc__ = method.__doc__
    def __get__(self, inst, cls):
        # self: <__main__.cache object at 0xb781340c>
        # inst: <__main__.Foo object at 0xb781348c>
        # cls: <class '__main__.Foo'>       
        if inst is None:
            # instance attribute accessed on class, return self
            # You get here if you write `Foo.bar`
            return self
        # compute, cache and return the instance's attribute value
        result = self.method(inst)
        # setattr redefines the instance's attribute so this doesn't get called again
        setattr(inst, self.name, result)
        return result

Может использоваться следующим образом:

def demo_cache():
    class Foo(object):
        @CachedAttribute
        def bar(self):
            print 'Calculating self.bar'  
            return 42
    foo=Foo()
    print(foo.bar)
    # Calculating self.bar
    # 42

Обратите внимание, что доступfoo.bar последующее время не вызывает функцию получения.(Calculating self.bar не печатается.)

    print(foo.bar)    
    # 42
    foo.bar=1
    print(foo.bar)
    # 1

Удаление foo.bar из foo.__dict__ повторно открывает свойство, определенное в Foo.Таким образом, вызов foo.bar снова пересчитывает значение снова.

    del foo.bar
    print(foo.bar)
    # Calculating self.bar
    # 42

demo_cache()

Декоратор был опубликован в Python Cookbook и также может быть найден в ActiveState .

Это эффективно, потому что, хотя свойство существует в __dict__ класса, после вычисления в экземпляре __dict__ создается атрибут с тем же именем.Правила поиска атрибутов в Python отдают приоритет атрибуту в __dict__ экземпляра, поэтому свойство в классе фактически переопределяется.

2 голосов
/ 12 сентября 2011

Конечно, вы можете установить атрибут в личном словаре экземпляра класса, который имеет приоритет перед вызовом функции свойства foo (которая находится в статическом словаре A.__dict__)

class A:
    def __init__(self):
        self._foo = 5
        self.__dict__['foo'] = 10

    @property
    def foo(self):
        return self._foo

assert A().foo == 10

Если вы хотите снова выполнить сброс для работы со свойством, просто del self.__dict__['foo']

1 голос
/ 13 сентября 2011
class MaskingProperty():
    def __init__(self, fget=None, name=None, doc=None):
        self.fget = fget
        if fget is not None:
            self.name = fget.__name__
        self.__doc__ = doc or fget.__doc__
    def __call__(self, func):
        self.fget = func
        self.name = func.__name__
        if not self.__doc__:
            self.__doc__ = func.__doc__
        return self
    def __get__(self, instance, cls):
        if instance is None:
            return self         
        if self.fget is None:
            raise AttributeError("seriously confused attribute <%s.%s>" % (cls, self.name))
        result = self.fget(instance)
        setattr(instance, self.name, result)
        return result

Это в основном то же самое, что и CachedAttribute Дениса Откидача, но немного более надежное в том смысле, что оно позволяет:

@MaskingProperty
def spam(self):
    ...

или

@MaskingProperty()     # notice the parens!  ;)
def spam(self):
    ...
0 голосов
/ 02 ноября 2018

Вы можете изменить код функции , заменив объект __code__ функции объектом __code__ из другой функции.

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

NULL = object()
def makeProperty(variable = None, default = NULL, defaultVariable = None):
    """Crates a property using the decorated function as the getter.
    The docstring of the decorated function becomes the docstring for the property.

    variable (str) - The name of the variable in 'self' to use for the property
        - If None: uses the name of 'function' prefixed by an underscore

    default (any) - What value to initialize 'variable' in 'self' as if it does not yet exist
        - If NULL: Checks for a kwarg in 'function' that matches 'defaultVariable'

    defaultVariable (str) - The name of a kwarg in 'function' to use for 'default'
        - If None: Uses "default"
        Note: this must be a kwarg, not an arg with a default; this means it must appear after *
    ___________________________________________________________

    Example Use:
        class Test():
            @makeProperty()
            def x(self, value, *, default = 0):
                '''Lorem ipsum'''
                return f"The value is {value}"

        test = Test()
        print(test.x) #The value is 0
        test.x = 1
        print(test.x) #The value is 1

    Equivalent Use:
        @makeProperty(defaultVariable = "someKwarg")
        def x(self, value, *, someKwarg = 0):

    Equivalent Use:
        @makeProperty(default = 0)
        def x(self, value):
    ___________________________________________________________
    """
    def decorator(function):
        _variable = variable or f"_{function.__name__}"

        if (default is not NULL):
            _default = default
        elif (function.__kwdefaults__ is not None):
            _default = function.__kwdefaults__.get(defaultVariable or "default")
        else:
            _default = None

        def fget(self):
            nonlocal fget_runOnce, fget, fset, _default #Both functions must have the same number of 'free variables' to replace __code__
            return getattr(self, _variable)

        def fget_runOnce(self):
            if (not hasattr(self, _variable)):
                fset(self, _default)

            fget_runOnce.__code__ = fget.__code__
            return getattr(self, _variable)

        def fset(self, value):
            setattr(self, _variable, function(self, value))

        def fdel(self):
            delattr(self, _variable)

        return property(fget_runOnce, fset, fdel, function.__doc__)
    return decorator
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...