Python: 用可变名称设置对象值的方法
我们来看看一个简单的类,这个类里面有一些值和设置这些值的方法:
class Object:
def __init__(self, id):
self.__id = id
self.__value1 = None
self.__value2 = None
... etc
def set_value1(self, value1):
self.__value1 = value1
def set_value2(self, value2):
self.__value2 = value2
... etc
我能不能把这些.set_valueX(valueX)的函数合并成一个呢?这样做是怎么实现的?而且这样做简单吗,不需要导入其他库?
4 个回答
这件事其实挺简单的,不过知道你为什么想这么做会更有帮助。最符合Python风格的方法可能是使用一个元类来创建你所有的访问器函数。如果你想认真做这件事,我建议你了解一下Python的property
概念。不过,如果你所有的访问器函数都一样,那为什么还要为它们单独写函数呢?你可以很简单地通过以下方式来设置值:
x = Object()
x.value = value
... 只要你去掉完全不必要的双下划线就行了。如果你真的需要改变每个属性的每次调用,你可以重写__setattr__
(Python数据模型参考)。顺便说一下,在我参与过的每个团队里,使用双下划线的方式都是被强烈不推荐的。这并不能阻止任何人实际访问这个值(你可以通过instance._Object__value1
来做到),但这会让后续的子类化变得非常麻烦。
如果你想对每个属性的获取或设置都做相同的预处理,你可以这样做:
class Object:
_ATTR_NAMES = ('value1', 'value2', ...)
def __init__(self):
for name in self._ATTR_NAMES:
setattr(self, "_" + name, None)
def set_attr(self, name, value):
if name in self._ATTR_NAMES:
setattr(self, name[1:], value)
else:
raise AttributeError("No attribute named %s found"%name)
Python的setattr是一个可以设置属性的函数。在这种情况下,你可以简单地列出属性,根据这个列表来设置和获取它们,使用相应的'getattr'函数。不过,我还是对你这么做的理由有些担心。通常来说,明确的做法比隐含的要好。多写一个获取/设置函数,让引用变得更清晰,通常并不难。
如果你真的必须这么做,你也可以使用类似我刚才展示的属性的技术来生成获取/设置函数(也就是说,动态创建一些包装set_attr
的函数),但这样通常会让代码更难维护和阅读。除非你真的有强烈且明确的需求,否则我建议避免使用这些技术。如果你真的需要这样做,可以试试以下方法:
class Object:
_ATTR_NAMES = ('value1', 'value2', ...)
def __init__(self):
for name in self._ATTR_NAMES:
setattr(self, "_" + name, None)
def _set_attr(self, name, value):
if name in self._ATTR_NAMES:
setattr(self, name[1:], value)
else:
raise AttributeError("No attribute named %s found"%name)
# This binds named set functions to the class
for name in Object._ATTR_NAMES:
def tempSetter(self, value):
self._set_attr(name, value)
setattr('set_'+name, tempSetter)
del name
del tempSetter
这是一种比较hacky的做法。用元类来处理这种事情会更优雅。不过,由于理解起来需要很多背景知识,我就不详细讲了。如果你需要在每个设置函数之前做预处理,通常更推荐做如下的事情:
def _preprocessing(self, value):
# Do something here
return value
def set_value1(self, value):
value = self._preprocessing(value)
self._value1 = value
def set_value2(self, value):
value = self._preprocessing(value)
self._value2 = value
最后,如果你唯一担心的是生成模板获取/设置函数的时间,那就写一个脚本,自动生成你的Python类的骨架作为文本文件。我有这样的脚本,可以根据原始模块概述我的单元测试用例。或者在你的文本编辑器里设置一个脚本宏。代码被阅读的次数远远多于被写的次数,所以不要为了省几个按键而写复杂的代码。
我先说一下,使用获取器和设置器在Python中并不是很常见,而且在这个情况下似乎也没必要。如果你需要自定义获取和设置的行为,可以考虑使用属性(Python @property 与获取器和设置器的对比),否则就可以用最普通的方式来获取和设置(比如 obj.foo = 'bar'
, x = obj.foo
)。
不过如果你真的想这么做的话……可以用一点 __getattr__
的魔法来实现:
class A(object):
def __init__(self, id):
self.__id = id
self.__value1 = None
self.__value2 = None
def _setter(self, name):
def set(value):
setattr(self, name, value)
return set
def __getattr__(self, name):
if name.startswith('set_'):
varname = name.split('_', 1)[-1]
return self._setter('__' + varname)
return super(A, self).__getattr__(name)
这样可以实现……
In [1]: a = A('123')
In [2]: a.set_value1(5)
In [3]: a.__value1
Out[3]: 5
In [4]: a.set_value2(7)
In [5]: a.__value2
Out[5]: 7
如果你愿意妥协,使用一个函数来处理所有设置器的逻辑,你可以这样做:
class Test:
def __init__(self):
self._value1 = 1
self._value2 = 2
self._value3 = 3
def set_value(self, attr, value):
setattr(x, attr, value)
然后你可以通过以下方式访问每个方法的设置器:
test_obj = Test()
test_obj.set_value("_value1", 10)
不过,我不太建议你这样做。相反,你可以通过 @property 装饰器来使用获取器和设置器。
这种方法更符合“Python风格”,你可以在这里找到更多信息:
可以使用setatter来实现:
class obj(object):
def __setter(self,**kwargs):
key,value = kwargs.items()[0]
setattr(self,"__"+key,value)
def __init__(self,id):
self.id = id
self.__value1 = None
self.__value2 = None
#You should assign setter as the set funciton like this:
self.set_value1 = self.__setter
self.set_value2= self.__setter
#and so on...
init方法也可以用同样的技巧写得更简洁。