【问题标题】:How can I write reusable @property getters/setters?如何编写可重用的@property getter/setter?
【发布时间】:2020-07-01 18:28:12
【问题描述】:

我正在寻找一种不重复使用 @property 装饰器的结构相同的 getter/setter 定义的方法。一个例子:

class foo:
    def __init__(self,length):
        self.length=length
    # length
    @property
    def length(self):
        return self._length

    @length.setter
    def length(self, val):
        # do some checks here (e.g. for units, etc.) (same as in the class below)
        self._length = val


class bar:
    def __init__(self,width):
        self.width=width
    # width
    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, val):
        # do some checks here (e.g. for units, etc.)(same as in the class above)
        self._width = val

类 bar 中的属性宽度和类 foo 中的长度在结构上具有相同的设置器,因此我想定义一个通用设置器,我可以为这些属性应用。 但是,我不确定如何实现所需的行为?也许与二级装饰师?

非常感谢您的帮助:)

【问题讨论】:

  • 不幸的是,这样做很乏味,但出于以下几个原因建议这样做:它是明确的,因此阅读您的代码的人可以确切地知道发生了什么;它与继承一起工作,这可能是一件非常棘手的事情。
  • 如果你想避免代码重复,最好的方法是在 say foo 中编写一个类方法,然后在 bar 和 foo 中调用它。我的意思只是用于设置器中的检查单元部分。或使其成为全局检查功能。
  • 是的,我一开始是用重复的,但是当你想在未来的某个时候改变一些东西时,你就有麻烦了。因此,我想知道是否有标准解决方案。毕竟,我还在学习 Python,并在这些问题出现时寻找解决这些问题的好方法 =)

标签: python python-3.x


【解决方案1】:

最终@property 只是返回一个实现描述符协议的对象,因此您可以轻松实现这样的事情:

class ReusableProperty:
   def __set_name__(self, owner, name):
       self.name = name

    def __get__(self, obj, objtype=None):
       return obj.__dict__.get(self.name)

    def __set__(self, obj, value):
       # do tests
       obj.__dict__[self.name] = value

class Foo:
    bar = ReusableProperty()

f = Foo()
print(f.bar)
f.bar = 'baz'
print(f.bar)

这是一个最小的示例,您需要熟悉 descriptor protocol 和可能的装饰器以使其更完整,并可能以更可重用的方式对其进行修饰,但只要您将属性设置为对象实现__get____set__,您可以根据自己的喜好自定义它。请参阅https://stackoverflow.com/a/17330273/476 以获得良好的概述。

请注意,__set_name__ 仅在 Python 3.6 之后可用;在旧版本中,您需要通过__init__ 显式传递名称,例如bar = ReusableProperty('bar')

【讨论】:

  • 谢谢,我同时以类似的方式解决了这个问题,但仍然使用属性类。
【解决方案2】:

也许你可以使用 python 描述符协议来实现你想要的

class Unit(object):
    def __set_name__(self, owner, name):
        self.name = name

    def __set__(self, instance, value):
        # put validation here
        instance.__dict__[self.name] = value

    def __get__(self, instance, owner):
        if instance is None:
            # allow access to the descriptor as class attribute
            return self
        return instance.__dict__.get(self.name)


class Container:
    length = Unit()
    width = Unit()

    def __init__(self):
        self.length = 100
        self.width = 200


def main():
    t = Container()
    print(t.length)
    print(t.width)
    t.length = 3
    t.width = 15
    print(t.length)
    print(t.width)

【讨论】:

  • 感谢您的快速回答!我认为这会奏效。我将不得不在我的工作环境中看看它:)
  • –1 这是描述符协议的错误使用(状态泄漏)。如果您输入t1 = Foo(10),然后输入t2 = Foo(20),那么您将看到t1.length 现在的值不正确。
  • 你是对的@wim 和固定的代码示例。总的来说,我的想法是指出有这样的解决方案,这可能会有所帮助:)
  • 是的 - 现在看起来好多了。我同意使用描述符是一个很好的建议。
【解决方案3】:

所以我想我可以更新一下我最终得到的结果。所以我寻找这样的解决方案的原因也是,我想通过它们的设置器为我的属性合并单元跟踪,并能够解析单元值数据。


import varCheck #modified parsing functions for my specific data
from astropy import units as u
########################################################################################
# length
########################################################################################
def make_length(internalVarName, description):
    def _get_length(self):
        return getattr(self, internalVarName)

    def _set_length(self, val):
        setattr(self, internalVarName, varCheck.valueUnitPair(val))
        # assert that the unit is actually of type length e.g. 'm' by
        # calling the astropy unit conversion function within varCheck.valueUnitPair
        # NOTE: this will not convert the unit specified in the input!
        assert getattr(
            self, internalVarName).to(u.m).unit, "val not of type 'length'!"

    def _del_length(self):
        del self.__dict__[internalVarName]

    return property(_get_length, _set_length, _del_length, str(description))



class foo:
    length = make_length('_length', 'the yardstick')

def __init__(self,length):
    self.length = length

对我来说,这个解决方案似乎相当简洁,但也许我正在监督一些事情...... 如果您对此有任何意见,请随时发表评论:)

【讨论】:

    猜你喜欢
    • 2011-01-09
    • 2019-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多