Python: 将绑定方法更改为另一个方法

7

我正在学习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之后再改变呢?

你为什么会说你不能呢? - Mad Physicist
可能是名称混淆,顺便说一下。 - Mad Physicist
4个回答

14

嗯,这就是在Python中试图强制隐私所得到的结果。;)

在类的外部,你必须将属性__x__y称为_Point__x_Point__y,因为存在名称修饰

如果你将这两个属性更改为非修饰名称(例如_x_y),或者在draw2中使用名称_Point__x_Point__y,你的代码将不会抛出错误。

我认为在使用修饰名称之前,你应该三思而后行。编写适当的docstrings,但不要用这种令人烦恼的方式限制你的类的用户。在社区中,使用单下划线名称已经被广泛理解为“不要碰这个”。

正如你已经注意到的那样,p1.draw在你的monkey patch之后行为不同,因为draw2不是实例p1的绑定方法,所以你需要显式地将p1作为参数传递。我建议在重新分配名称draw之前,通过利用函数的描述符协议将实例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 作为第一个参数传递。


3
一种替代 draw2.get(p1) 的方法是使用显式类型声明:import types p1.draw = types.MethodType(draw2, p1)。这明确指示实例p1现在具有绑定方法,该方法自动将“self”作为第一个参数传递。 - Attack68

2
双下划线会导致Python对属性名称进行'破坏性操作'。它实际上将存储为_Point__x而不是__x。如果您像这样更改函数,它将起作用:
def draw2(self):
    print("x",self._Point__x, "y", self._Point__y)

双下划线应该表示一个类的私有变量,不应该在类外部访问。名称修饰使这更难以意外地实现

1
这是因为您定义了__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)

请注意,使用__是为了定义私有属性。

0

你的代码有2个问题。

使用双下划线字段会导致它们的名称被“缠绕”(mangled)。换句话说,当您在类Point中指定字段__x时,其名称实际上是_Point__x。目的是仅从同一类中访问此字段(然后它将起作用)。您有两个选项解决这个问题 - 使用__Point_x从外部访问此字段或将其重命名为单下划线字段(_x)。
你的重新绑定(re-bounding)是错误的。应该在类上替换而不是特定对象上替换。你现在的做法是必须显式地传递self调用:p1.draw(p1)。但是,如果您这样做:
Point.draw = draw2
p1.draw()  # works as expected

2
请注意,这里的第二点会改变所有类实例的行为,包括已经实例化和未来的实例化。仅为特定实例绑定方法的解决方案可以从以下示例中看到,例如@timgeb和我的评论,使用MethodType作为替代方法。 - Attack68

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接