替换属性以获得性能增益

发布于 2024-12-04 07:00:57 字数 1142 浏览 1 评论 0原文

情况

类似于这个问题,我想要更换财产。与这个问题不同,我不想在子类中覆盖它。为了提高效率,我想在 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 函数。它尝试调用未定义的属性的 setter。

潜在的解决方案

我知道我可以通过稍微不同的方式实现相同的目标,如下所示:

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 检查和函数调用),效率会高多少?

Situation

Similar to this question, I want to replace a property. Unlike that question, I do not want to override it in a sub-class. I want to replace it in the init and in the property itself for efficiency, so that it doesn't have to call a function which calculates the value each time the property is called.

I have a class which has a property on it. The constructor may take the value of the property. If it is passed the value, I want to replace the property with the value (not just set the property). This is because the property itself calculates the value, which is an expensive operation. Similarly, I want to replace the property with the value calculated by the property once it has been calculated, so that future calls to the property do not have to re-calculate:

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

Problem

The above code does not work because doing self.someVar = does not replace the someVar function. It tries to call the property's setter, which is not defined.

Potential Solution

I know I can achieve the same thing in a slightly different way as follows:

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

This will be marginally less efficient as it will have to check for None every time the property is called. The application is performance critical, so this may or may not be good enough.

Question

Is there a way to replace a property on an instance of a class? How much more efficient would it be if I was able to do this (i.e. avoiding a None check and a function call)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

网白 2024-12-11 07:00:57

您正在寻找的是Denis Otkidach的优秀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

注意访问< code>foo.bar 随后的时间不会调用getter函数。 (计算 self.bar 不会被打印。)

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

foo.__dict__ 中删除 foo.bar 会重新公开 Foo 中定义的属性
因此,再次调用 foo.bar 会再次重新计算该值。

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

demo_cache()

该装饰器发布在 Python Cookbook 中,也可以在 活动状态

这是高效的,因为虽然该属性存在于类的 __dict__ 中,但在计算之后,会在实例的 __dict__ 中创建同名的属性。 Python 的属性查找规则优先考虑实例的 __dict__ 中的属性,因此类中的属性会被有效地覆盖。

What you are looking for is Denis Otkidach's excellent 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

It can be used like this:

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

Notice that accessing foo.bar subsequent times does not call the getter function. (Calculating self.bar is not printed.)

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

Deleting foo.bar from foo.__dict__ re-exposes the property defined in Foo.
Thus, calling foo.bar again recalculates the value again.

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

demo_cache()

The decorator was published in the Python Cookbook and can also be found on ActiveState.

This is efficient because although the property exists in the class's __dict__, after computation, an attribute of the same name is created in the instance's __dict__. Python's attribute lookup rules gives precedence to the attribute in the instance's __dict__, so the property in class becomes effectively overridden.

怪我闹别瞎闹 2024-12-11 07:00:57

当然,您可以在类实例的私有字典中设置属性,该属性在调用属性函数 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']

Sure, you can set the attribute in the private dictionary of the class instance, which takes precedence before calling the property function foo (which is in the static dictionary 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

If you want to reset again to work on the property, just del self.__dict__['foo']

两仪 2024-12-11 07:00:57
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

这与 Denis Otkidach 的 CachedAttribute 基本相同,但稍微更健壮,因为它允许:

@MaskingProperty
def spam(self):
    ...

@MaskingProperty()     # notice the parens!  ;)
def spam(self):
    ...
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

This is basically the same as Denis Otkidach's CachedAttribute, but slightly more robust in that it allows either:

@MaskingProperty
def spam(self):
    ...

or

@MaskingProperty()     # notice the parens!  ;)
def spam(self):
    ...
森罗 2024-12-11 07:00:57

您可以通过替换函数的 更改函数的代码 >__code__ 对象与来自另一个函数的 __code__ 对象。

这是我创建的一个装饰器函数来为您完成此任务。请随意修改您认为合适的内容。但要记住的重要一点是,这两个函数需要具有相同数量的“自由变量”才能像这样交换。通过使用 nonlocal 来强制执行此操作可以轻松完成(如下所示)。

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

You can change what code a function has by replacing the functions's __code__object with the __code__ object from another function.

Here is a decorator function that I created to do just that for you. Feel free to modify it as you see fit. The big thing to remember though is that the both functions need to have the same number of 'free variables' to be swapped like this. This can easily be done by using nonlocal to force it (as shown below).

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文