【问题标题】:Python: Pass class member item to one of its member functions by referencePython:通过引用将类成员项传递给其成员函数之一
【发布时间】:2020-08-05 20:42:39
【问题描述】:

我在一个类中有一些对类成员进行操作的函数。除了它们操作的类成员之外,这些函数几乎相同。为了避免一遍又一遍地键入函数,只更改被更改的类成员,我创建了一个函数,其中一个参数是要更改的类成员。

这是一个非常简化的版本:

class Foo:
    a = 0
    b = 0

    def __set_member(self, member, value):
        member = value

    def set_a(self, value):
        self.__set_member(self.a, value)

f = Foo()

print(f.a)
f.set_a(2)
print(f.a)

但是这个输出:

0
0

在 C++ 中可以这样做:

class Foo {

    void set_member(int& member, int value) {
        member = value;
    }

public:
    int a = 0;
    int b = 0;

    void set_a(int value) {
        set_member(this->a, value);
    }
};

显然我给出的示例不需要set_member 函数,因为它非常简单。

在我的实际代码中,setter 看起来像这样:

def requestOpenDoor1(self):
    # Non blocking function: sets door1 to `Opening` and then after 2 seconds sets it to `Open`
    self.__door1 = DoorStates.Opening
    async def callback(): self.__door1 = DoorStates.Open
    self.__tasks.append(AsyncTimer(2, callback))

一种方法是复制粘贴这个函数20次左右,只更改正在修改的类成员、转换状态、时间和结束状态,但我宁愿减少代码重复。

我也尝试过使用装饰器来解决这个问题:

class request_function_decorator(object):
    def __init__(self, tasks, stateToChange, transitionState, time, endState):
        self.__tasks = tasks
        self.__stateToChange = stateToChange
        self.__transitionState = transitionState
        self.__time = time
        self.__endState = endState
    def __call__(self, function):
        def wrapped_f(*args):
            self.__stateToChange = self.__transitionState
            async def callback(): self.__stateToChange = self.__endState
            self.__tasks.append(AsyncTimer(self.__time, callback))
        return wrapped_f

@request_function_decorator(__tasks, __door1, States.Opening, 2, States.Open)
def requestOpenDoor1(self):
    pass

但这也行不通。

如何推广这个函数以减少代码重复?

谢谢。

【问题讨论】:

  • 类似setattr?
  • 你确定你真的想要类属性吗?我怀疑 ab 应该是 instance 属性,您可以直接设置它们而无需任何设置器样板。 (如果你确实发现你以后需要setter,你可以定义属性。)
  • 您还可以使用列表的类成员获得类似指针的效果。这就像拥有一个使用其索引取消引用的指针。我发布了一个答案来证明这一点。
  • 我好心但非常强烈地建议您 1/ 阅读 full Python official tutorial,2/ 阅读 doc on Python's data model,最后 3/ 阅读此 very good article about python's names and bindings。 Python 与 C++ 完全不同,所以你不能期望只用 Python 编写 C++ 代码;-)

标签: python python-3.x pass-by-reference


【解决方案1】:

默认情况下你应该有__setattr__

class Foo:
    a = 0
    b = 0

f = Foo()
print(f.a)
# 0

f.a = 9
print(f.a)
# 9

f.__setattr__("a", 99)
print(f.a)
# 99

setattr(f, "a", 900)
print(f.a)
# 900

【讨论】:

    【解决方案2】:

    您可以通过使类变量可变列表对象来获得类似指针的效果。

    显然,有人不喜欢我这里的例子。我在这里演示的内容在典型代码中不应该是必需的;但是,来自 C/C++ 背景可能有一定的意义。它显示了一个类似的情况,即从指针的角度思考与通过索引引用列表并将其传递给方法和函数相似。

    >>> class Foo:
    ...     _a = [0]   # Class vars.
    ...     _b = [0]
    ...     
    ...     def __init__(self):
    ...         self._c = [0]  # Instance var.
    ... 
    ...     def _set_member(self, member, value): 
    ...         # Some lengthy algorithm you don't want to 
    ...         # implement separately for each data member...
    ...         member[0] = value
    ... 
    ...     @property
    ...     def a(self):
    ...         return self._a[0]
    ...         
    ...     @a.setter
    ...     def a(self, value):
    ...         self._set_member(self._a, value)
    ...         
    >>> f = Foo()
    >>> f.a
    0
    >>> f.a = 22
    >>> f.a
    22
    >>> 
    

    C 指针可以类似地使用零索引来取消引用。 int *a; ...a[0] = 1;.

    有一次在使用 Python C API 实现与本机库的接口时,我需要为多个函数编写包装器,这些函数将多个指针作为参数。我想保持面向 Python 的函数参数列表靠近 C 函数。所以我使用这些参数的列表来模拟指针效果。

    【讨论】:

      【解决方案3】:

      第一个 sn-p 的 Python 等价物非常简单:

      class Foo:
          def __init__(self):
              # instances attributes must be defined on the instance, 
              # not on the class.
              self.a = 0
              self.b = 0
      
          def set_a(self, a):
              setattr(self, "a", a)
      
      f = Foo()
      
      print(f.a)
      f.set_a(2)
      print(f.a)
      

      现在注意这里set_a是-从pythonic的角度来看-一个完整的WTF:首先它完全没用(a是一个公共属性,你可以直接设置它),然后它的实现使用setattr没有充分的理由(您可以将其实现为self.a = a

      FWIW,您很少会在 Python 中找到显式的 setter / getter。 Python 对计算属性有很强的支持,所以如果你打算公开一个属性(这是 getter/setter 的目标和效果),你可以从一个普通的属性开始,以及是否以及何时需要控制对它的访问, 你只要把它变成一个计算出来的,即:

      class Foo:
          def __init__(self):
              # instances attributes must be defined on the instance, 
              # not on the class.
              self.a = 0
              self.b = 0
      
          @property
          def a(self):
              return self._a
      
          @a.setter 
          def a(self, a):
              if a == 42:
                  raise HitchikerError("yes, but what is the question ?")  
              self._a = a
      

      另请注意,实现属性(不属于类公共接口的属性)的规范命名约定是一个前导下划线。双前导下划线(及其触发的名称管理系统)仅用于防止深层继承层次结构中的意外名称冲突,实际上几乎从未使用过(因为几乎永远需要)。

      希望对您有所帮助...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-25
        • 2013-05-02
        • 1970-01-01
        • 2019-09-27
        • 1970-01-01
        相关资源
        最近更新 更多