Python: 用可变名称设置对象值的方法

2 投票
4 回答
1081 浏览
提问于 2025-04-18 04:17

我们来看看一个简单的类,这个类里面有一些值和设置这些值的方法:

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 个回答

0

这件事其实挺简单的,不过知道你为什么想这么做会更有帮助。最符合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类的骨架作为文本文件。我有这样的脚本,可以根据原始模块概述我的单元测试用例。或者在你的文本编辑器里设置一个脚本宏。代码被阅读的次数远远多于被写的次数,所以不要为了省几个按键而写复杂的代码。

0

我先说一下,使用获取器和设置器在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
0

如果你愿意妥协,使用一个函数来处理所有设置器的逻辑,你可以这样做:

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风格”,你可以在这里找到更多信息:

https://docs.python.org/2/library/functions.html#property

0

可以使用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方法也可以用同样的技巧写得更简洁。

撰写回答