【问题标题】:Python: Change bound method to another methodPython:将绑定方法更改为另一种方法
【发布时间】:2018-10-14 06:12:17
【问题描述】:

我正在尝试学习python中的绑定方法并实现了以下代码:

class Point:
    def __init__(self, x,y):
        self.__x=x
        self.__y=y

    def draw(self):
        print(self.__x, self.__y)

def draw2(self):
    print("x",self.__x, "y", self.__y)

p1=Point(1,2)
p2=Point(3,4)
p1.draw()
p2.draw()
p1.draw=draw2
p1.draw(p1)

当我运行这段代码时,会产生以下输出:

1 2
3 4
Traceback (most recent call last):
  File "main.py", line 17, in <module>
    p1.draw(p1)
  File "main.py", line 10, in draw2
    print("x",self.__x, "y", self.__y)
AttributeError: 'Point' object has no attribute '__x'

为什么我不能在更改 p1.draw() 使其指向 draw2 后更改它?

【问题讨论】:

  • 是什么让你说你不能?
  • 顺便说一下可能名字乱码

标签: python class object


【解决方案1】:

嗯,这就是你尝试在 Python 中强制执行隐私的结果。 ;)

在类的主体之外,由于name mangling,您必须将属性__x__y 引用为_Point__x_Point__y

如果您将这两个属性更改为未损坏的名称(例如_x_y)或在draw2 中使用名称_Point__x_Point__y,您的代码将不会引发错误。

在我看来,在使用重命名之前,您应该三思而后行。编写正确的文档字符串,但不要以这种烦人的方式限制您的班级用户。使用单下划线名称在社区中被很好地理解为“不要碰这个”。

您似乎已经注意到,p1.draw 在猴子补丁之后的行为有所不同,因为draw2 不是实例p1 的绑定方法,因此您需要显式传递p1 作为参数。我建议您在重新分配名称draw 之前,利用函数的descriptor protocol 将实例p1 绑定到draw2

把所有东西放在一起,代码

class Point:
    def __init__(self, x,y):
        self._x=x
        self._y=y

    def draw(self):
        print(self._x, self._y)

def draw2(self):
    print("x",self._x, "y", self._y)

p1 = Point(1,2)
p2 = Point(3,4)
p1.draw()
p2.draw()
p1.draw = draw2.__get__(p1)
p1.draw()

产生输出

1 2
3 4
x 1 y 2

其中draw2.__get__(p1) 生成一个行为类似于draw2 的可调用对象,但会自动将p1 作为第一个参数传递。

【讨论】:

  • draw2.__get__(p1) 的替代方法是使用显式类型声明:import typesp1.draw = types.MethodType(draw2, p1)。这明确指示实例 p1 现在有一个绑定方法,该方法自动将 self 作为第一个参数传递
【解决方案2】:

双下划线导致 Python 将 'mangle' 属性的名称。它实际上将存储为_Point__x 而不是__x。如果你像这样改变你的功能,它会起作用:

def draw2(self):
    print("x",self._Point__x, "y", self._Point__y)

双下划线应该表示一个类私有的变量,不应在它之外访问。 Name mangling makes it more difficult to do so accidentally.

【讨论】:

    【解决方案3】:

    这是因为您已经定义了__x,正如您所看到的那样:

    p1=Point(1,2)
    p1.__x
    

    它又说'Point' object has no attribute '__x'。所以这个问题不是因为你的绘图功能。这是为了你的属性。所以如果你这样定义你的类:

    class Point:
        def __init__(self, x,y):
            self.x=x
            self.y=y
    
        def draw(self):
            print(self.x, self.y)
    
    def draw2(self):
        print("x",self.x, "y", self.x)
    

    这样就可以了。

    此外,如果您使用第一个实现,使用dir 函数,您可以看到__x__y 可以像_Point__x_Point__y 一样访问。所以你也可以这样做:

    class Point:
        def __init__(self, x,y):
            self.__x=x
            self.__y=y
    
        def draw(self):
            print(self.__x, self.__y)
    
    def draw2(self):
        print("x",self._Point__x, "y", self._Point__y)
    

    注意,使用__是定义一个私有属性。

    【讨论】:

      【解决方案4】:

      您的代码有 2 个问题。

      1. 使用双下划线字段,导致其名称被“损坏”。换句话说,当您在类Point 中指定字段__x 时,它的名称实际上是_Point__x。关键是只能从同一个类中访问此字段(然后它将起作用)。您有 2 个选项可以解决此问题 - 使用 __Point_x 从外部访问此字段,或者直接将其重命名为单下划线字段 (_x)。

      2. 您的重新绑定错误。您应该在类而不是特定对象上替换它。你现在这样做的方式,你必须调用显式传递self:p1.draw(p1)。但是,如果您愿意:

        Point.draw = draw2
        p1.draw()  # works as expected
        

      【讨论】:

      • 请注意,这里的第 2 点改变了所有类实例的行为,已经实例化和未来实例化。仅针对特定实例绑定方法的解决方案可以在下面看到,例如@timgeb 和我关于使用 MethodType 作为替代方案的评论。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-10
      • 1970-01-01
      • 2017-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-31
      相关资源
      最近更新 更多