使用Python @ property作为propreties和方法

时间:2017-09-16 20:31:17

标签: python properties

我有一个用例,我希望Python @property在被调用为方法(即末尾括号)时的行为与在没有括号的情况下调用它时的行为不同。这样的事情是可能的。

class Sequence:

    @property
    def first(self):
        return self._first

    @first.setter
    def first(self, v):
        self._first = v

    # This won't work
    @first.method
    def first(self):
        # Do something different than the setter and the getter since 
        # `first` is being called as a method.
        return 4321


seq = Sequence()
seq.first = 1234

# Setting and getting the first property works fine
assert seq.first == 1234

# Calling the first property as a function fails
assert seq.first() == 4321

3 个答案:

答案 0 :(得分:1)

好吧,你可以让getter返回一个Proxy,它的行为类似于从getter返回的值,或者就像你调用它时的行为一样。如果你从getter中返回可以调用的东西,那将是不明确的,但在你的情况下你会返回整数(不可调用)。它仍然是不可取的,但你可以让它工作(基于the Python descriptor how-to中提到的property模拟器):

def make_callable_proxy(val, call_func):
    class CallableProxy(type(val)):  # subclass the class of value
        __call__ = call_func

    return CallableProxy(val)

class CallableProperty(object):
    def __init__(self, fget=None, fset=None, fdel=None, fcall=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.fcall = fcall
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        # The following implements the "callable part".
        if self.fcall is None:
            return self.fget(obj)
        value = make_callable_proxy(self.fget(obj), self.fcall)
        return value

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.fcall, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.fcall, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.fcall, self.__doc__)

    def method(self, fcall):
        return type(self)(self.fget, self.fset, self.fdel, fcall, self.__doc__)

class Sequence(object):

    @CallableProperty
    def first(self):
        return self._first

    @first.setter
    def first(self, v):
        self._first = v

    # THIS WILL WORK NOW
    @first.method
    def first(self):
        # Do something different than the setter and the getter since 
        # `first` is being called as a method.
        return 4321


seq = Sequence()
seq.first = 1234

# Setting and getting the first property works fine
assert seq.first == 1234

# Calling the first property as a function fails
assert seq.first() == 4321

这可以通过使用真实的代理类(如wrapt.ObjectProxy)而不是CallableProxy类来进一步细化。但这取决于此类软件包的可用性。如果你有wrapt这就是它的样子:

from wrapt import ObjectProxy

def make_callable_proxy(val, call_func):
    class CallableProxy(ObjectProxy):
        __call__ = call_func
        __repr__ = val.__repr__  # just for a nicer representation

    return CallableProxy(val)

答案 1 :(得分:0)

您需要了解装饰器语法如何去除:

@dec
def foo():
    pass

变为

def foo():
    pass
foo = dec(foo)

,而

@dec(1)
def foo():
    pass

变为

d = dec(1)
def foo():
    pass
foo = d(foo)

在第一种情况下,装饰器是一个用装饰函数作为参数调用的函数。在第二个中,使用1作为参数调用装饰器,返回一个将装饰函数作为参数的函数。

换句话说,您在询问是否有办法定义函数dec,其行为取决于其返回值的使用方式。这个功能看起来不太可能。

答案 2 :(得分:0)

我认为没有办法让@property以这种方式工作,但根据您的使用情况,一种可能的替代方法是让property返回一些类型为ducktypes的东西,但实际上是一个可调用函数返回函数的值:

class CallableWrapper:
    def __init__(self, value):
        self._value = value

    def __call__(self):
        return 4321

    def __eq__(self, other):
        return self._value == other

    def __getattr__(self, name):
        return getattr(self._value, name)


class Sequence:
    @property
    def first(self):
        return CallableWrapper(self._first)

    @first.setter
    def first(self, v):
        self._first = v


seq = Sequence()
seq.first = 1234

# Setting and getting the first property works fine
assert seq.first == 1234

# Calling the first property
assert seq.first() == 4321

您可以采用额外的魔法来获得与您为将__call__()方法分配给包装器所描述的语法非常相似的语法。只有当你愿意让属性返回一个不同类型的包装器而不是原始值本身时,这才有用。

也就是说,尝试重载此类属性可能不是一个好主意,因为它可能会使您的代码更难以阅读和理解。