如何优雅地将 Class 的所有属性作为参数传递给函数?
How to Elegantly Pass All Attributes of a Class as Arguments in a Function?
我有一个有点复杂的 class Thing
和一个相关的混入 IterMixin
(使 class 可迭代)...和一个 funky
代码库中其他地方的方法,它接收我的 class 的实例作为参数。
事实上,我试图将一堆参数捆绑为单个对象,以传递给下面 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
class,我想传递Thing
的属性(foo
、bar
和 baz
) 到 funky
函数作为 一个参数 ...像这样:
thing_args = Thing()
def funky(*thing_args):
...do stuff...
...expecting to manipulate keys from things_arg
...
return whatever
问题:
如果我不为属性 foo
和 bar
private 设置 setters(例如,通过 self._foo
)——即通过下划线——然后我在 class 初始化期间引发无限递归...因为这些属性的 __init__
和设置器一遍又一遍地循环并反复称呼自己。为了避免这种情况,我在设置它们时使用了 @property
装饰器和 "privatized" foo 和 bar。
但是,当我传递 Thing
class 的实例并通过 splat 或星号将其属性解压缩为 funky
函数中的参数时,如果我反省结果这些属性的键,我仍然得到 _foo
和 _bar
。我似乎无法摆脱下划线。 (换句话说,我得到了 Thing
的 "privatized" 属性名称。)
funky
的业务逻辑需要解压后的值不包含任何下划线。
为什么会这样(解压后的下划线)?我怎样才能解决这个问题?有没有更优雅的方法来初始化 foo 和 bar 属性而不私有化任何东西?或者也许是一种更 Pythonic 的方式将 Thing
class 中的所有属性传递给我的 funky
函数?
首先,你遇到了一个严重的问题,它甚至会阻止你看到你寻求帮助的问题:你的 Thing
class 定义了一个 __iter__
方法没有 super
,也没有 yield
或 return
任何东西。希望那部分只是一些打字错误,你知道如何修复它来做你真正想做的事。
不,关于你问的问题:
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,因此它不会存储在任何地方。
如果你真的想使用反射来查找实例上的所有 "public values",你可以这样做:
for attr, value in inspect.getmembers(self):
if not attr.startswith('_') and not callable(value):
yield attr, value
但是,我认为不反思性地做这件事会好得多。更简单、更简洁的选项包括:
- 添加一个
_fields = 'foo', 'bar', 'baz'
并让基础 class 迭代 _fields_
。
- 编写一个注册 属性 的装饰器,并让基础 class 迭代该注册表。
- 构建一些可以让您更明确地指定属性并为您编写样板的东西。请参阅
namedtuple
, dataclass
, and attrs
以获取一些灵感。
- 只需使用
attrs
(或者,如果您不是 OP,而是未来可以依赖 3.7+ 阅读本文的人,dataclass
)为您完成这项工作。
- 重新考虑您的设计。 class 的实例迭代 name-value 对它们的 public 属性首先很奇怪。用作 keyword-splatting 的映射的 "parameter object" 可能很有用;一个表现得像一个普通的可迭代对象可能是有用的;充当 name-value 对的可迭代对象除了传递给
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.
我有一个有点复杂的 class Thing
和一个相关的混入 IterMixin
(使 class 可迭代)...和一个 funky
代码库中其他地方的方法,它接收我的 class 的实例作为参数。
事实上,我试图将一堆参数捆绑为单个对象,以传递给下面 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
class,我想传递Thing
的属性(foo
、bar
和 baz
) 到 funky
函数作为 一个参数 ...像这样:
thing_args = Thing()
def funky(*thing_args):
...do stuff...
...expecting to manipulate keys from things_arg
...
return whatever
问题:
如果我不为属性 foo
和 bar
private 设置 setters(例如,通过 self._foo
)——即通过下划线——然后我在 class 初始化期间引发无限递归...因为这些属性的 __init__
和设置器一遍又一遍地循环并反复称呼自己。为了避免这种情况,我在设置它们时使用了 @property
装饰器和 "privatized" foo 和 bar。
但是,当我传递 Thing
class 的实例并通过 splat 或星号将其属性解压缩为 funky
函数中的参数时,如果我反省结果这些属性的键,我仍然得到 _foo
和 _bar
。我似乎无法摆脱下划线。 (换句话说,我得到了 Thing
的 "privatized" 属性名称。)
funky
的业务逻辑需要解压后的值不包含任何下划线。
为什么会这样(解压后的下划线)?我怎样才能解决这个问题?有没有更优雅的方法来初始化 foo 和 bar 属性而不私有化任何东西?或者也许是一种更 Pythonic 的方式将 Thing
class 中的所有属性传递给我的 funky
函数?
首先,你遇到了一个严重的问题,它甚至会阻止你看到你寻求帮助的问题:你的 Thing
class 定义了一个 __iter__
方法没有 super
,也没有 yield
或 return
任何东西。希望那部分只是一些打字错误,你知道如何修复它来做你真正想做的事。
不,关于你问的问题:
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,因此它不会存储在任何地方。
如果你真的想使用反射来查找实例上的所有 "public values",你可以这样做:
for attr, value in inspect.getmembers(self):
if not attr.startswith('_') and not callable(value):
yield attr, value
但是,我认为不反思性地做这件事会好得多。更简单、更简洁的选项包括:
- 添加一个
_fields = 'foo', 'bar', 'baz'
并让基础 class 迭代_fields_
。 - 编写一个注册 属性 的装饰器,并让基础 class 迭代该注册表。
- 构建一些可以让您更明确地指定属性并为您编写样板的东西。请参阅
namedtuple
,dataclass
, andattrs
以获取一些灵感。 - 只需使用
attrs
(或者,如果您不是 OP,而是未来可以依赖 3.7+ 阅读本文的人,dataclass
)为您完成这项工作。 - 重新考虑您的设计。 class 的实例迭代 name-value 对它们的 public 属性首先很奇怪。用作 keyword-splatting 的映射的 "parameter object" 可能很有用;一个表现得像一个普通的可迭代对象可能是有用的;充当 name-value 对的可迭代对象除了传递给
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.