【问题标题】:Overriding getters and setters for attributes from a list of strings覆盖字符串列表中属性的 getter 和 setter
【发布时间】:2018-09-11 17:30:54
【问题描述】:

目的是在列表中提供一些字符串作为类的属性。该类不仅应具有属性,还应具有相应的 getter 和 setter 方法。在其他一些继承自的类中,其中一些设置器需要被覆盖。

为此,我想出了以下内容。在字符串列表的循环中使用setattr,创建一个属性和相应的方法。关于第一部分,代码按预期工作。
但是我无法覆盖继承类中的设置器。

class Base():
    attributes = ["attr{}".format(i) for i in range(100)]

    def __init__(self):
        _get = lambda a: lambda : getattr(self, a)
        _set = lambda a: lambda v: setattr(self, a, v)

        for attr in self.attributes:
            setattr(self, attr, None)
            setattr(self, "get_"+attr, _get(attr))
            setattr(self, "set_"+attr, _set(attr))


class Child(Base):
    def __init__(self):
        super().__init__()
        #setattr(self, "set_attr4", set_attr4)

    # Here I want to override one of the setters to perform typechecking
    def set_attr4(self, v):
        print("This being printed would probably solve the problem.")
        if type(v) == bool:
            super().set_attr4(v)
        else:
            raise ValueError("attr4 must be a boolean")

if __name__ == "__main__":
  b = Base()
  b.attr2 = 5
  print(b.get_attr2())
  b.set_attr3(55)
  print(b.get_attr3())

  c = Child()
  c.set_attr4("SomeString")
  print(c.get_attr4())

这里的输出是

5
555
SomeString

然而,预期的输出是

5
555
This being printed would probably solve the problem.
ValueError("attr4 must be a boolean")

所以不知何故,set_attr4 方法永远不会被调用;我猜这是意料之中的,因为在读入类结构之后会调用__init__。但是我不知道如何覆盖这些方法。我尝试添加setattr(self, "set_attr4", set_attr4)(上面代码中的注释行)但无济于事。

或者更一般地说,propery 通常用于创建 getter 和 setter。但我认为我不明白如何在 getter 和 setter 是动态创建并且需要被孩子覆盖的情况下应用它。

有什么解决办法吗?


由于 cmets 的更新:有几个人指出,在 python 中使用 getter/setter 可能不是一种好的样式,并且通常可能不需要它们。虽然这绝对是要记住的事情,但这个问题的背景是我正在扩展一个旧的现有代码,该代码始终使用 getter/setter。因此,我不希望更改样式并让用户(这个项目总共只有大约 20 个用户,但仍然......)突然改变他们访问 API 中属性的方式。
然而,任何未来的读者可能会认为 getter/setter 方法至少是有问题的。

【问题讨论】:

  • 为什么要在all中创建getter和setter?
  • @juanpa.arrivillaga 感谢您指出这一点,我更新了问题,原因是我确实想在这里使用 getter/setter。

标签: python python-3.x class inheritance


【解决方案1】:

元类来救援!

class Meta(type):
    def __init__(cls, name, bases, dct):
        for attr in cls.attributes:
            if not hasattr(cls, attr):
                setattr(cls, attr, None)
                setattr(cls, f'get_{attr}', cls._get(attr))
                setattr(cls, f'set_{attr}', cls._set(attr))

class Base(metaclass=Meta):
    attributes = ["attr{}".format(i) for i in range(100)]

    _get = lambda a: lambda self: getattr(self, a)
    _set = lambda a: lambda self, v: setattr(self, a, v)

# the rest of your code goes here

这是不言自明的:制作attributes, _get, _set 类变量(这样您就可以在没有类实例化的情况下访问它们),然后让元类为您设置一切。

【讨论】:

  • 我接受这个解决方案,因为它非常紧凑并且直接适用于问题。
【解决方案2】:

__init__ 在创建子类后执行,因此它会覆盖那里指定的内容。

解决问题所需的最小更改是检查属性是否已设置:

class Base():
    attributes = ["attr{}".format(i) for i in range(100)]

    def __init__(self):
        _get = lambda a: lambda : getattr(self, a)
        _set = lambda a: lambda v: setattr(self, a, v)

        for attr in self.attributes:
            setattr(self, attr, None)
            if not hasattr(self, "get_"+attr):
                setattr(self, "get_"+attr, _get(attr))
            if not hasattr(self, "set_"+attr):
                setattr(self, "set_"+attr, _set(attr))

但是,我不认为要这样做。这是为Base每个实例 创建一个新的getter 和setter。我宁愿在课堂上创建它们。这可以通过类装饰器、元类、类本身或其他方式来完成。

例如,这很丑,但很简单:

class Base():
    attributes = ["attr{}".format(i) for i in range(100)]

    for attr in attributes:
        exec(f"get_{attr} = lambda self: self.{attr}")
        exec(f"set_{attr} = lambda self, value: setattr(self, '{attr}', value)")
    del attr

这样更好:

class Base:
    pass

attributes = ["attr{}".format(i) for i in range(100)]

for attr in attributes:
    setattr(Base, f"get_{attr}", lambda self: getattr(self, attr))
    setattr(Base, f"set_{attr}", lambda self, value: setattr(self, '{attr}', value))

【讨论】:

  • 是的,这是一个选项。但这将允许我覆盖该方法,即super().set_attr4(v) 不起作用。不过,在极限情况下,我可以将其更改为自行执行设置。如果没有更好的办法出现,这是一种解决方法。
  • @ImportanceOfBeingErnest 很多事情都行不通,你没有创建方法,你只是在每个实例上添加属性,而这些实例恰好是函数。要成为正确的方法,您需要将它们设置在 类对象上
【解决方案3】:

您对问题的看法是正确的。 Base 实例的创建发生在 Child 类定义 set_attr4 之后。由于Base 正在动态创建它的getter/setter,它只会在创建时超过Childs 版本。

另一种方法(除了其他答案之外)是动态创建Child 的getter/setter。这里的想法是采用“约定优于配置”,并且只需为您想要覆盖的方法添加前缀override_。这是一个例子:

class Child(Base):
    def __init__(self):
        super().__init__()
        overrides = [override for override in dir(self) if override.startswith("override_")]
        for override in overrides:
            base_name = override.split("override_")[-1]
            setattr(self, base_name, getattr(self, override))


    # Here I want to override one of the setters to perform typechecking
    def override_set_attr4(self, v):
        print("This being printed would probably solve the problem.")
        if type(v) == bool:
            super().set_attr4(v)
        else:
            raise ValueError("attr4 must be a boolean") # Added "raise" to this, overwise we just return None...

哪个输出:

5
55
This being printed would probably solve the problem.
Traceback (most recent call last):
  File ".\stack.py", line 39, in <module>
    c.set_attr4("SomeString")
  File ".\stack.py", line 29, in override_set_attr4
    raise ValueError("attr4 must be a boolean") # Added "raise" to this, overwise we just return None...
ValueError: attr4 must be a boolean

优势这里是Base 不必知道Child 类。在其他答案中,Base/Child 耦合非常微妙。也可能不希望接触Base(违反开放/封闭原则)。

缺点是“约定优于配置”以避免真正的继承机制有点笨拙和不直观。 override_ 函数仍然在 Child 实例上运行(您可能关心也可能不关心)。

我认为这里真正的问题是您试图以这种方式定义 getter 和 setter。我们通常甚至不需要 Python 中的 getter/setter。这绝对感觉像一个 X/Y 问题,但也许不是。你有很多代表,所以我不会给你一些迂腐的说法。即便如此,也许退后一步,想想你真正想做的事情并考虑替代方案。

【讨论】:

  • 这是一个有趣的解决方法。因为你提到它,我不认为这是一个 XY 问题,因为由于与我正在扩展的代码库的兼容性,我确实想使用 getter 和 setter。可能是几年前,在编写代码之前应该有人问过这个问题。我学习了 Java 编程,所以这些 getter 对我来说似乎很自然。关于代表,我想在提问或回答问题时这无关紧要;但很明显,我没有从回答核心 python 问题中获得 python 黄金批次 ;-)。
  • @ImportanceOfBeingErnest 如果这是遗留问题,我会感受到你的痛苦。我经历了各种各样的废话来使遗留代码工作或提高性能。对于遗留基线,由于它们中的大多数都是可操作的(否则我们会扔掉它,对吗?),总感觉一切都应该在昨天完成。祝你好运,有趣的问题。
【解决方案4】:

这里的问题是您在 Base 类的实例中创建“方法”(__init__ 仅在实例中运行)。

继承发生在您实例化您的类之前,并且不会查看实例。

换句话说,当您尝试覆盖该方法时,它甚至都没有被创建。

解决方案是在类中创建它们,而不是在__init__ 内的self 实例中:

def _create_getter(attr):
    def _get(self): 
        return getattr(self, attr)
    return _get

def _create_setter(attr):    
    def _set(self, value): 
        return setattr(self, attr, value)
    return _set    


class Base():
    attributes = ["attr{}".format(i) for i in range(100)]

for attr in Base.attributes:
    setattr(Base, 'get_' + attr, _create_getter(attr))
    setattr(Base, 'set_' + attr, _create_setter(attr))

那么继承就可以正常工作了:

class Child(Base):
    def set_attr4(self, v):
        print("This being printed would probably solve the problem.")
        if type(v) == bool:
            super().set_attr4(v)
        else:
            raise ValueError("attr4 must be a boolean")

if __name__ == "__main__":
  b = Base()
  b.attr2 = 5
  print(b.get_attr2())
  b.set_attr3(55)
  print(b.get_attr3())

  c = Child()
  c.set_attr4("SomeString")
  print(c.get_attr4())

您也可以不这样做 - 让您的 Base 类正常,并仅在子类中为您想要的属性创建设置器:

class Base:
    pass

class Child(Base):
    @property
    def attr4(self):
        return self._attr4

    @attr4.setter
    def attr4(self, new_v):
        if not isinstance(new_v, bool):
            raise TypeError('Not bool')
        self._attr4 = new_v

测试:

c = Child()

c.attr3 = 2 # works fine even without any setter
c.attr4 = True #works fine, runs the setter
c.attr4 = 3 #type error

【讨论】:

  • 这应该可以,但是从外部提供这些方法似乎很奇怪。那么在类之外创建属性是一种常见的做法吗?
  • @ImportanceOfBeingErnest 动态创建 setter 和 getter 并不常见。哎呀,即使使用 getter 和 setter 也不常见!这不是 java,您只在真正需要时才创建属性。什么都不做,只返回属性的 getter 是不受欢迎的。
  • @ImportanceOfBeingErnest 整个想法并不常见,听起来像是过度设计。
  • 是的,我确实需要一些二传手。但仅将它们用于某些属性,而不是所有属性会导致 API 混乱。
  • python 中不需要设置器——你可以使用属性——它们在设置属性时调用一个函数。而不是x.set_foo(5),您的 API 用户可以只执行 x.foo = 5 并且无论如何它都会调用您的方法 - 您只能针对需要它的属性执行此操作
猜你喜欢
  • 2018-11-08
  • 2021-11-06
  • 2011-03-21
  • 2019-02-28
  • 1970-01-01
  • 1970-01-01
  • 2011-08-25
  • 2014-04-08
  • 1970-01-01
相关资源
最近更新 更多