与this question类似,我想替换一个属性。与那个问题不同,我不想在子类中覆盖它。我想在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。该应用程序对性能至关重要,因此这可能也可能不够好。
有没有办法替换类实例上的属性?如果我能够做到这一点会有多高效(即避免无检查和函数调用)?
答案 0 :(得分:15)
您正在寻找的是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
请注意,后续访问foo.bar
不会调用getter函数。 (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__
中的属性,因此类中的属性会被有效覆盖。
答案 1 :(得分:2)
当然,您可以在类实例的私有字典中设置属性,该属性在调用属性函数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']
答案 2 :(得分:1)
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):
...
答案 3 :(得分:0)
您可以通过将功能的__code__
对象替换为另一个功能中的__code__
对象来change what code a function has。
这是我创建的装饰器函数,专门为您执行此操作。随时根据自己的喜好对其进行修改。不过要记住的一件大事是,两个函数都需要具有相同数量的“自由变量”才能像这样交换。可以使用非本地强制将其轻松完成(如下所示)。
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