【问题标题】:If one of the attribute of my class is a dictionary, how to define the set property for it?如果我的类的属性之一是字典,如何为它定义 set 属性?
【发布时间】:2020-10-04 03:59:31
【问题描述】:

我对自己构建的课程有疑问。这个类有很多属性,其中一个是字典。我之所以转向拥有字典,是因为我想根据情况在我的类中添加参数,我不知道更好的方法。我知道有可能使用“setattr”动态创建类的新属性,但我读到这不一定是一个好习惯。

class MyClass:
    def __init__(self, a, b, params_dict={"p1": 1, "p2":-0.136}):
        self.a = a
        self.b = b
        self.params_dict = params_dict
    
myobject = MyClass(a=0, b=4, params_dict={"p1": 1, "p2":-0.136, "p3":89})

但现在我被卡住了,因为我想为 params_dict 定义属性。它适用于 getter,但不适用于 setter:

class MyClass:
    def __init__(self, a, b, params_dict={"p1": 1, "p2":-0.136}):
        self.a = a
        self.b = b
        self._params_dict = params_dict

    def _get_params_dict(self):
        return self._params_dict
    def _set_params_dict(self, params_dict):
        # print("Hello")
    params_dict = property(_get_params_dict, _set_params_dict)

myobject = MyClass(a=0, b=4, params_dict={"p1": 1, "p2":-0.136, "p3":89})
print(myobject.params_dict["p1"]) # returns "1", so that is fine
myobject.params_dict["p2"] = 10 # this modifies the dictionary myobject.params_dict but do not print "Hello"

我的理解是没有调用setter,因为我没有更改字典,我只是修改了它的一个键。但是当我只改变它的一个键时如何让它工作呢?

如果您认为还有其他方法可以解决该问题并仍然在那里设置属性,我非常愿意采用更智能的解决方案来即时添加属性。

感谢您的帮助!


编辑:我现在明白,当我执行 myobject.params_dict["p2"] = 10 时,调用的是 getter,而不是 setter,因为它不会将 ["p2"] 识别为 params_dict 的修改。

【问题讨论】:

  • 如果你想用myobject.params_dict["p1"]访问和设置它,你为什么需要这个属性?为什么不直接访问字典?即:self.params_dict = params_dict
  • 是的,是因为我想在修改字典的时候触发类内部的一个方法,并且禁止在构造对象后添加更多的键。

标签: python class dictionary properties attributes


【解决方案1】:

您的代码myobject.params_dict["p2"] = 10 中发生的情况是:

您访问一个属性params_dict,它返回了实例的_params_dict...然后您将10 分配给它的项目p2。二传手从不来玩,也不能……不像这样。如果你想使用属性设置器,你可以:

class MyClass:
   ...
    def _set_params_dict(self, params_dict):
        self._params_dict.update(params_dict)

...
myobject.params_dict = {"p2": 10}

当您分配给属性时,使用更新字典的设置器。

这应该解释发生了什么以及为什么。如何“正确”地做到这一点......取决于你究竟追求什么。


旁注:使用这​​个:

def __init__(self, a, b, params_dict={"p1": 1, "p2":-0.136})
    self._params_dict = params_dict

意味着当你的类被创建时,一个字典对象被创建并分配为构造函数的默认参数...然后被分配给实例的一个属性。

这反过来意味着,所有使用默认params_dict(未提供值)的实例共享_params_dict 的同一个字典对象。


解决您的评论:这不是(我的)意见问题。您可以按照您在设置项目时建议执行自定义操作的方式获得完全可用的界面,但您必须修改 _params_dict 类型才能做到这一点,例如:

class ParamsDict(dict):
    def __setitem__(self, key, value, /):
        print(f"Special action setting {key}")
        super().__setitem__(key, value)

class MyClass:
    def __init__(self, a, b, params_dict=(("p1", 1), ("p2", -0.136))):
        self.a = a
        self.b = b
        self.params_dict = ParamsDict(params_dict)

myobject = MyClass(a=0, b=4, params_dict={"p1": 1, "p2":-0.136, "p3":89})
myobject.params_dict["p2"] = 20

如果这对您的用例有意义,您也可以考虑将 params_dict 属性的项目公开为 MyClass 的项目:

class MyClass:
    def __init__(self, a, b, params_dict=None):
        if params_dict is None:
            param_dict = {"p1": 1, "p2": -0.136}
        self.a = a
        self.b = b
        self.params_dict = params_dict

    def __getitem__(self, key):
        return self.params_dict[key]

    def __setitem__(self, key, value):
        print(f"Special action setting {key}")
        self.params_dict[key] = value

myobject = MyClass(a=0, b=4, params_dict={"p1": 1, "p2":-0.136, "p3":89})
myobject["p2"] = 20
print(myobject["p2"])

但是如果没有更多的上下文和理解为什么(所有这些标题在哪里)到目前为止,最简单(并且可能是最干净)的解决方案通常是不理会上述任何一个,而只是定义一个方法来做到这一点 - 设置一个参数值并执行您需要的任何其他操作,例如:

class MyClass:
    def __init__(self, a, b, params_dict=None):
        if params_dict is None:
            param_dict = {"p1": 1, "p2": -0.136}
        self.a = a
        self.b = b
        self.params_dict = params_dict

    def set_param(self, key, value):
        print(f"Special action setting {key}")
        self.params_dict[key] = value

myobject = MyClass(a=0, b=4, params_dict={"p1": 1, "p2":-0.136, "p3":89})
myobject.set_param("p2", 20)
print(myobject.params_dict["p2"])

更新下一条评论:好吧,您还需要让 ParamDict 实例知道包含它的 MyClass 实例(或其需要调用的方法),例如:

class ParamsDict(dict):
    def __init__(self, container, *args, **kwargs):
        self._setparam_callback = container.setparam_callback
        super().__init__(*args, **kwargs)

    def __setitem__(self, key, value, /):
        self._setparam_callback(key, value)
        super().__setitem__(key, value)

class MyClass:
    def __init__(self, a, b, params_dict=(("p1", 1), ("p2", -0.136))):
        self.a = a
        self.b = b
        self.params_dict = ParamsDict(self, params_dict)

    def setparam_callback(self, key, value):
        print(f"Special action setting {key} to {value}")

myobject = MyClass(a=0, b=4, params_dict={"p1": 1, "p2":-0.136, "p3":89})
myobject.params_dict["p2"] = 20

如果您想保存一组键/值对(并且您可能不知道它们是什么),dict 听起来只是适合这项工作的一种类型。我怀疑你可能过于复杂了,简单:

myobject.set_param("p2", 20)
myobject.get_param("p2")

可能看起来微不足道(?),但可以完成工作。这也很明显,这通常是一件好事。

属性对于您所描述的内容并不会真正帮助您。通过重载 getattr(和 setattr 进行赋值)可以实现公开数据和“动态”处理属性访问的类似外观,但同样,简单通常是好的。

【讨论】:

  • 谢谢,这很有帮助。这个 update() 很整洁,我不知道。但是你认为没有办法像我写的那样更新字典中的值? myobject.params_dict["p2"] = 10。因为我需要它对用户来说尽可能简单。当字典的值发生变化时,我需要在 setter 中触发某个方法,这就是为什么我把头撞到墙上的原因。然后我需要能够对类中的这些特殊“属性”(字典的键)进行 for 循环。
  • 感谢您的编辑。我喜欢 ParamDict Class 选项,唯一的事情是我需要在修改 params_dict 时触发 MyClass 中的一个方法,因此在 ParamDict 中拥有该属性对我来说并不完全有效。您使用“set_param”方法的最后一个选项确实是最干净的,但是如何防止用户仅使用 myobject["p2"] = 20 对其进行修改?
  • 很抱歉,如果这一切看起来我没有提供足够的信息来完全查看我正在寻找的内容。我试图尽可能地简化(可能太多)我需要的东西,这基本上是:只有在我创建对象时才在我的类中动态添加参数(我不知道字典是否是最好的),并且当这些参数被修改时触发动作(我认为应该通过携带这些参数的字典上的属性)。另一种选择是动态创建属性,我知道该怎么做,但是如何为每个属性动态创建属性?
  • 很棒的 Ondrej,非常感谢您的大力帮助!我也喜欢你关于良好做法的建议,这正是我喜欢学习的。
【解决方案2】:

1) 我认为setattr 比这种方法更好,您可以避免在两者之间添加这个额外的层。

2) 我会写:

def set_parameter(self, key, value):
   print("Hello") # hi !
   self._params_dict[key] = value

【讨论】:

  • 感谢您的回答。 2)不起作用。我想这是因为当我执行 myobject.params_dict["p2"] = 10 时没有调用 setter。关于 1),我可以猜测,但是我需要动态添加的每个单独属性的属性,是有可能吗?我不喜欢这个 1) 是我从文本编辑器收到警告,告诉我在运行代码之前该属性不存在。
猜你喜欢
  • 1970-01-01
  • 2015-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-21
  • 1970-01-01
  • 2015-09-01
相关资源
最近更新 更多