何时应该在Python中使用类和self方法?

5

我一直在尝试编写一个Python程序,根据距离从四个锚点计算一个点的位置。我决定将其计算为四个圆的交点。

我有一个问题,关于如何在这样的程序中使用类,而不是算法。我对面向对象编程没有太多经验。在这里使用类是否真正必要,或者它是否可以以任何方式改进程序?

这是我的代码:

import math

class Program():
    def __init__(self, anchor_1, anchor_2, anchor_3, anchor_4, data):
        self.anchor_1 = anchor_1
        self.anchor_2 = anchor_2
        self.anchor_3 = anchor_3
        self.anchor_4 = anchor_4



    def intersection(self, P1, P2, dist1, dist2): 
        PX = abs(P1[0]-P2[0])               
        PY = abs(P1[1]-P2[1])
        d = math.sqrt(PX*PX+PY*PY)   

        if d < dist1+ dist2 and d > (abs(dist1-dist2)):

            ex = (P2[0]-P1[0])/d
            ey = (P2[1]-P1[1])/d

            x = (dist1*dist1 - dist2*dist2 + d*d) / (2*d)
            y = math.sqrt(dist1*dist1 - x*x)

            P3 = ((P1[0] + x * ex - y * ey),(P1[1] + x*ey + y*ex))
            P4 = ((P1[0] + x * ex + y * ey),(P1[1] + x*ey - y*ex))  

            return (P3,P4)
        elif d == dist1 + dist2:
            ex = (P2[0]-P1[0])/d
            ey = (P2[1]-P1[1])/d

            x = (dist1*dist1 - dist2*dist2 + d*d) / (2*d)
            y = math.sqrt(dist1*dist1 - x*x)

            P3 = ((P1[0] + x * ex + y * ey),(P1[1] + x*ey + y*ex))

            return(P3, None)
        else:
            return (None, None)





    def calc_point(self, my_list):
        if len(my_list) != 5:
            print("Wrong data")
        else:
            tag_id = my_list[0];
            self.dist_1 = my_list[1];
            self.dist_2 = my_list[2];
            self.dist_3 = my_list[3];
            self.dist_4 = my_list[4];

        (self.X1, self.X2) = self.intersection(self.anchor_1, self.anchor_2, self.dist_1, self.dist_2)
        (self.X3, self.X4) = self.intersection(self.anchor_1, self.anchor_3, self.dist_1, self.dist_3)
        (self.X5, self.X6) = self.intersection(self.anchor_1, self.anchor_4, self.dist_1, self.dist_4)





with open('distances.txt') as f:
    dist_to_anchor = f.readlines()

dist_to_anchor = [x.strip() for x in dist_to_anchor]
dist_to_anchor = [x.split() for x in dist_to_anchor]
for row in dist_to_anchor:
    for k in range(0,5):
        row[k] = float(row[k])
anchor_1= (1,1)
anchor_2 = (-1,1)
anchor_3 = (-1, -1)
anchor_4 = (1, -1)

My_program = Program (anchor_1, anchor_2, anchor_3, anchor_4, dist_to_anchor)
My_program.calc_point(dist_to_anchor[0])

print(My_program.X1)
print(My_program.X2)
print(My_program.X3)
print(My_program.X4)
print(My_program.X5)
print(My_program.X6)

此外,我不太明白应该在哪里使用self关键字以及何时是不需要的。

self不是一个关键字,它就像my_listf或者anchor_1一样是一个标识符。 - melpomene
学习课程:Python - 面向对象编程 - stovfl
将程序包装在类中并不是面向对象编程风格的充分途径。但是这里的答案为您提供了几种不同的方法。 - progmatico
2个回答

9

这里真的需要使用类吗?或者说它能以任何方式改善程序吗?

类并非必需,但通常对于组织代码非常有用。

在您的情况下,您已经将过程式代码包装成一个类。它仍然基本上是一堆函数调用。您最好要么将其编写为过程,要么编写适当的类。


让我们看一下在过程式面向对象编程风格中如何进行几何学操作。
过程式编程主要是编写函数(程序),这些函数接收一些数据,对其进行处理,并返回一些数据。
def area_circle(radius):
    return math.pi * radius * radius

print(area_circle(5))

你有一个圆的半径,需要计算出它的面积。
面向对象编程是关于让数据去做事情。
class Circle():
    def __init__(self, radius=0):
        self.radius = radius

    def area(self):
        return math.pi * self.radius * self.radius

circle = Circle(radius=5)
print(circle.area())

你有一个圆,你要求它的面积。
这似乎是为了一个非常微妙的区别而增加了很多额外的代码。为什么费这个劲呢?
如果你需要计算其他形状会发生什么?这里是面向对象中的一个正方形。
class Square():
    def __init__(self, side=0):
        self.side = side
    
    def area(self):
        return self.side * self.side

square = Square(side=5)
print(square.area())

现在是过程式编程。

def area_square(side):
    return side * side
print(area_square(5));

当你想计算一个形状的面积时,会发生什么?在程序中,每个需要处理形状的地方都必须知道正在处理的是哪种形状、调用哪个过程以及从哪里获取该过程。这种逻辑可能分散在代码的各个地方。为了避免这种情况,您可以编写一个包装函数,并确保根据需要导入它。
from circle import 'area_circle'
from square import 'area_square'

def area(type, shape_data):
    if type == 'circle':
        return area_circle(shape_data)
    elif type == 'square':
        return area_square(shape_data)
    else:
        raise Exception("Unrecognized type")

print(area('circle', 5))
print(area('square', 5))

在面向对象编程中,你可以免费获得这个。
print(shape.area())

无论是一个圆形还是一个正方形,都可以使用shape.area()。作为使用形状的人,你不需要知道任何关于它如何工作的信息。如果你想在形状上做更多的事情,例如计算周长,在你的形状类中添加一个perimeter方法,现在它就可以在任何有形状的地方使用了。
随着越来越多的形状被添加,过程式代码在需要使用形状的所有地方变得越来越复杂。面向对象的代码保持完全相同,只需编写更多的类。
这就是面向对象的目的:将工作原理的细节隐藏在接口后面。对于你的代码来说,它的工作原理并不重要,只要结果是相同的。

5

在我看来,类和面向对象编程始终是一个不错的选择。使用它们可以更好地组织和重用代码,您可以创建从现有类派生的新类来扩展其功能(继承),或者在需要时更改其行为(多态性),以及封装代码的内部以使其更安全(虽然 Python 中没有真正的封装)。

例如,在您具体的情况下,您正在构建一个计算器,该计算器使用一种技术来计算交点。如果其他人使用您的类想要修改该行为,则可以覆盖该函数(这就是多态性的实际应用):

class PointCalculator:
    def intersection(self, P1, P2, dist1, dist2): 
        # Your initial implementation

class FasterPointCalculator(PointCalculator):
    def __init__(self):
        super().__init__()

    def intersection(self, P1, P2, dist1, dist2):
        # New implementation

或者,您可能会在将来扩展该类:

class BetterPointCalculator(PointCalculator):
        def __init__(self):
            super().__init__()

        def distance(self, P1, P2):
            # New function

您可能需要使用某些必需的数据来初始化您的类,而且可能不希望用户能够修改它,您可以通过在变量名称前加下划线来表示封装:
class PointCalculator:
    def __init__(self, p1, p2):
        self._p1 = p1
        self._p2 = p2

    def do_something(self): 
        # Do something with your data
        self._p1 + self._p2

你可能已经注意到,在调用函数时,self会被自动传递,它包含对当前对象(类的实例)的引用,因此你可以访问在其中声明的任何内容,例如上面示例中的变量_p1和_p2。

你还可以创建类方法(静态方法),然后就无法访问self了。对于执行通用计算或不需要特定实例的任何操作的方法,应该这样做,比如你的intersection方法可以是一个很好的选择。

class PointCalculator:

    @staticmethod
    def intersection(P1, P2, dist1, dist2): 
        # Return the result

现在你不需要一个PointCalculator实例,你可以直接调用PointCalculator.intersection(1, 2, 3, 4)

使用类的另一个好处可能是内存优化。Python会在对象超出范围时从内存中删除它们,因此如果您有一个包含大量数据的长脚本,则这些数据直到脚本终止才会从内存中释放。

话虽如此,对于执行非常特定任务的小型实用程序脚本,例如安装应用程序、配置某些服务、运行一些操作系统管理任务等,一个简单的脚本完全足够,这也是Python如此受欢迎的原因之一。


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