简体   繁体   English

如何优雅地将类的所有属性作为函数中的参数传递?

[英]How to Elegantly Pass All Attributes of a Class as Arguments in a Function?

I have a somewhat complex class Thing , and an associated mixin IterMixin (to make the class iterable)...and a funky method elsewhere in the codebase which receives an instance of my class as an argument. 我有一个稍微复杂的类Thing ,以及一个关联的mixin IterMixin (使该类可迭代)...以及代码库中其他位置的funky方法,该方法接收我的类的实例作为参数。

In fact, I'm attempting to bundle up a bunch of parameters as single object to be passed to multiple external functions beyond the funky function below. 实际上,我正在尝试将一堆参数捆绑为一个对象,以传递给下面的funky函数之外的多个外部函数。 A parameter object design pattern of sorts... 各种参数对象设计模式...

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

With regard to the funky function, in a completely different module, via the Thing class, I want to pass Thing 's attributes ( foo , bar , and baz ) to the funky function as one argument ...like so: 关于funky函数,在一个完全不同的模块中,通过Thing类,我想将Thing的属性( foobarbaz )作为一个参数传递给funky函数,就像这样:

    thing_args = Thing()

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

PROBLEM: 问题:

If I do not make the setters for the attributes foo and bar private (for example, via self._foo )--ie, by way of an underscore--then I evoke infinite recursion during class initialization ...as the __init__ and setters for these attributes loop over and over and repeatedly call themselves. 如果我不将foobar属性的设置器 self._foo 私有 (例如,通过self._foo )(即通过下划线),那么我将在类初始化期间调用无限递归...就像__init__和setters一样这些属性会不断循环并反复调用自己。 To avoid that, I used the @property decorator and "privatized" the foo and bar while setting them. 为了避免这种情况,我在设置它们时使用了@property装饰器,并对foo和bar进行了“私有化”。

However, when I pass an instance of the Thing class, and unpack its attributes as args in the funky function via a splat or asterick, if I introspect the resultant keys for those attributes, I still get _foo and _bar . 但是,当我传递Thing类的实例并通过splat或asterick在funky函数中将其属性解压缩为args时,如果我对这些属性的结果键进行内省,则仍然会得到_foo_bar I can't seem to get rid of the underscores. 我似乎无法摆脱下划线。 (In other words, I get the "privatized" attribute names of Thing .) (换句话说,我得到Thing的“私有化”属性名称。)

The biz logic of funky needs the unpacked values to not have any underscores. funky的biz逻辑需要解包的值没有下划线。

Why is this happening (the underscores upon unpacking)? 为什么会发生这种情况(打开包装时要加下划线)? How can I fix this? 我怎样才能解决这个问题? Is there a more elegant way to either initialize the foo and bar attributes without privatizing anything? 有没有一种更优雅的方式来初始化foo和bar属性而不私有化任何东西? Or perhaps a more Pythonic way to pass all the attributes in the Thing class to my funky function? 还是将Python Thing类中的所有属性传递给我的funky函数的一种更Python化的方法?

First, you've got a major problem that will prevent you from even seeing the problem you've asked for help with: Your Thing class defines an __iter__ method that doesn't super , and doesn't yield or return anything. 首先,您遇到了一个主要问题,该问题将使您什至看不到您寻求帮助的问题: Thing类定义了一个__iter__方法,该方法不是super ,也不yieldreturn任何东西。 Hopefully that part is just some typo and you know how to fix it to do whatever you actually wanted there. 希望那部分只是一些错字,并且您知道如何对其进行修复以执行您实际想要的任何操作。

No, onto the problem you're asking about: 不,要解决的问题是:

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

Try printing out the __dict__ of your instances. 尝试打印实例的__dict__ Or, better, instances of a minimal example like this: 或者更好的是,像这样的最小示例的实例:

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__)

The output is {'_foo': 2} . 输出为{'_foo': 2}

You've tried to hide the attributes by giving them private names and putting them behind properties, but then you've gone around behind the properties' backs and looked directly into the __dict__ where the real attributes are. 您试图通过给它们提供私有名称并将其放在属性后面来隐藏属性,但是随后您又绕过了属性的背后,直接查看了真正属性所在的__dict__

And what else could be there? 而且还有什么可以呢? Your actual _foo has to be stored somewhere on each instance. 您实际的_foo必须存储在每个实例的某个位置 That foo , on the other hand, isn't really a value, it's a getter/setter that uses that private attribute, so it isn't stored anywhere. 另一方面, foo并不是一个真正的值,它是使用私有属性的getter / setter方法,因此它不存储在任何地方。

If you really want to use reflection to find all of the "public values" on an instance, you can do something like this: 如果您真的想使用反射来查找实例上的所有“公共值”,则可以执行以下操作:

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

However, I think it would be much better to not do this reflectively. 但是,我认为最好不要进行反思。 Simpler and cleaner options include: 更简单,更清洁的选项包括:

  • Add a _fields = 'foo', 'bar', 'baz' and have the base class iterate _fields_ . 添加_fields = 'foo', 'bar', 'baz'并让基类迭代_fields_
  • Write a decorator that registers a property, and have the base class iterate that registry. 编写一个装饰器,注册一个属性,并让基类迭代该注册表。
  • Build something that lets you specify the attributes more declaratively and writes the boilerplate for you. 构建一些内容,使您可以更声明性地指定属性,并为您编写样板。 See namedtuple , dataclass , and attrs for some inspiration. 请参阅namedtupledataclassattrs以获得一些启发。
  • Just use attrs (or, if you're not the OP but someone reading this from the future who can rely on 3.7+, dataclass ) to do that work for you. 只要使用attrs (或者,如果你不是OP但有人从未来谁可以依靠3.7+,阅读本dataclass ),这样做对你的工作。
  • Rethink your design. 重新考虑您的设计。 A class whose instances iterate name-value pairs of their public attributes is weird in the first place. 首先,一个类的实例对其公共属性的名称/值对进行迭代的类很奇怪。 A "parameter object" that acted like a mapping to be used for keyword-splatting could be useful; 像映射一样用于关键字喷涂的“参数对象”可能很有用; one that acted like a normal iterable could be useful; 一个像普通的迭代器一样的行为可能是有用的; one that acts as an iterable of name-value pairs is useless for anything except for passing to a dict construct (at which point it's, again, simpler to be a mapping). 充当名称-值对的可迭代对象的对象对任何东西都没有用,除了传递给dict构造(在这一点上,再次成为映射更简单)。 Plus, a mixin is really not helping you with the hard part of doing it. 另外,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. 无论您实际上需要做什么,都应寻求帮助,而不是如何使该代码无法正常工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM