【问题标题】:How to Elegantly Pass All Attributes of a Class as Arguments in a Function?如何优雅地将类的所有属性作为参数传递给函数?
【发布时间】:2018-09-15 20:41:57
【问题描述】:

我有一个有点复杂的类Thing,和一个相关的mixin IterMixin(使类可迭代)......以及代码库中其他地方的funky 方法,它接收我的类的实例作为参数.

事实上,我试图将一堆参数捆绑为单个对象,以传递给 多个 外部函数,而不是下面的funky 函数。各种参数对象设计模式...

class IterMixin():
    def __iter__(self):
        for attr, value in self.__dict__.items():
            yield attr, value

class Thing(IterMixin):
    def __iter__(self, foo=None, bar=None, baz=999):

        if foo is None:
            self.foo = {}
        else:
            self.foo = foo

        if bar is None:
            self.foo = {}
        else:
            self.bar = bar

        self.baz = baz

    @property
    def foo(self):
        return self._foo

    @foo.setter
    def foo(self, data)
        self._foo = self.parser(data)

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def bar(self, more_data)
        self._bar, self.baz = self.another_parser(more_data)

    def parser(self, data):
        ...do stuff...
        return foo

    def another_parser(self, more_data):
        ...do add'l stuff...
        return bar, baz

关于funky函数,在一个完全不同的模块中,通过Thing类,我想将Thing的属性(foobarbaz)传递给funky 函数作为一个参数...就像这样:

    thing_args = Thing()

    def funky(*thing_args):
        ...do stuff...
        ...expecting to manipulate keys from things_arg
        ...
        return whatever

问题:

如果我不为属性foobar private(例如,通过self._foo)设置setters--即顺便说一句一个下划线 - 然后我在类初始化期间唤起无限递归......因为__init__和这些属性的设置器一遍又一遍地循环并反复调用自己。为避免这种情况,我使用了@property 装饰器,并在设置它们时“私有化”了 foo 和 bar。

但是,当我传递 Thing 类的实例,并通过 splat 或星号将其属性解压缩为 funky 函数中的 args 时,如果我自省这些属性的结果键,我仍然会得到 @987654341 @ 和 _bar。我似乎无法摆脱下划线。 (换句话说,我得到了Thing 的“私有化”属性名称。)

funky 的 biz 逻辑需要解压后的值不能有任何下划线。

为什么会发生这种情况(拆包时的下划线)?我怎样才能解决这个问题?有没有更优雅的方法来初始化 foo 和 bar 属性而不私有化任何东西?或者也许是一种更 Pythonic 的方式来将 Thing 类中的所有属性传递给我的 funky 函数?

【问题讨论】:

  • 我觉得你真的是想太多了……不过看起来你玩得很开心,所以我不会抱怨。
  • 你为什么不直接传递“东西”?并使用它的属性?
  • 如果你想传递一个实例,就好像它是一个属性序列,为什么不把它变成一个序列,或者至少是一个可迭代的?有关示例,请参见 stdlib 中的 namedtuple(尽管您不需要构建任何复杂的东西)。
  • 同时,如果您正在寻找如何隐藏私有属性,即使是确定的内省,您只是使用了错误的语言。 Python“私有”只是意味着“按约定私有”。
  • 你是说Thing类的instances的属性吧?

标签: python function class properties setter


【解决方案1】:

首先,您遇到了一个重大问题,您甚至无法看到您寻求帮助的问题:您的 Thing 类定义了一个 __iter__ 方法,它没有 super,并且没有不要yieldreturn 任何东西。希望那部分只是一些错字,并且您知道如何修复它以执行您真正想要的任何操作。

不,关于你要问的问题:

class IterMixin():
    def __iter__(self):
        for attr, value in self.__dict__.items():
            yield attr, value

尝试打印出您的实例的__dict__。或者,更好的是,像这样的最小示例的实例:

class Thing:
    @property
    def foo(self):
        return self._foo
    @foo.setter
    def foo(self, data):
        self._foo = data

t = Thing()
t.foo = 2
print(t.__dict__)

输出是{'_foo': 2}

您试图通过给属性提供私有名称并将它们放在属性后面来隐藏属性,但随后您绕过属性的背后,直接查看了真正的属性所在的__dict__

还有什么可能?您的实际_foo 必须存储在每个实例上某处。另一方面,foo 并不是一个真正的值,它是一个使用该私有属性的 getter/setter,因此它不会存储在任何地方。

如果您真的想使用反射来查找实例上的所有“公共值”,您可以执行以下操作:

for attr, value in inspect.getmembers(self):
    if not attr.startswith('_') and not callable(value):
        yield attr, value

但是,我认为不要反思会更好。更简单、更简洁的选项包括:

  • 添加一个_fields = 'foo', 'bar', 'baz' 并让基类迭代_fields_
  • 编写一个注册属性的装饰器,并让基类迭代该注册表。
  • 构建一些东西,让您以更声明的方式指定属性并为您编写样板文件。请参阅 namedtupledataclassattrs 以获得一些灵感。
  • 只需使用 attrs(或者,如果您不是 OP,而是从未来阅读这篇文章并且可以依赖 3.7+ 的人,dataclass)为您完成这项工作。
  • 重新考虑您的设计。一个实例迭代其公共属性的名称-值对的类首先是奇怪的。一个“参数对象”,其作用类似于用于关键字喷溅的映射;一个像普通迭代一样的行为可能是有用的;作为名称-值对的可迭代对象,除了传递给dict 构造(此时它再次更简单地成为映射)之外,它对任何东西都是无用的。另外,mixin 并不能帮助你完成困难的部分。 Whatever you actually need to do, ask for help on how to do that, instead of how to make this code that shouldn't work work anyway.

【讨论】:

  • 谢谢!超级有帮助。不熟悉 v3.7+ 中的数据类和 attrs。后者通过 attrs_post_init 方法成功了。另外...感谢您将我转向 X,Y 问题。很高兴能用词汇来识别一个普遍存在的问题……我自己偶尔也会表现出来。
猜你喜欢
  • 2018-04-02
  • 1970-01-01
  • 1970-01-01
  • 2017-06-04
  • 1970-01-01
  • 2011-08-15
  • 2014-09-17
  • 1970-01-01
  • 2014-10-18
相关资源
最近更新 更多