如何告诉python类方法引用自己的字段?

时间:2018-06-15 21:29:46

标签: python

这是一个简单的问题,我很难用语言表达,因为我对Python的语法不太熟悉。我有一个名为“Quadrilateral”的类需要4个点,我正在尝试创建一个名为“side_length”的方法,我想用它来计算四边形上两个顶点之间的直线长度:

import math


class Point:
    x = 0.0
    y = 0.0

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
        print("Point Constructor")

    def to_string(self):
        return "{X: " + str(self.x) + ", Y: " + str(self.y) + "}"


class Quadrilateral:
    p1 = 0
    p2 = 0
    p3 = 0
    p4 = 0

    def __init__(self, p1=Point(), p2=Point(), p3=Point(), p4=Point()):
        self.p1 = p1
        self.p2 = p2
        self.p3 = p3
        self.p4 = p4
        print("Quadrilateral Constructor")

    def to_string(self):
        return "{P1: " + self.p1.ToString() + "}, " + "{P2: " + self.p2.ToString() + "}, " + \
           "{P3: " + self.p3.ToString() + "}," + "{P4: " + self.p4.ToString() + "}"

    def side_length(self, p1, p2):
        vertex1 = p1
        vertex2 = p2
        return math.sqrt((vertex2.x - vertex1.x)**2 + (vertex2.y - vertex1.y)**2)

    def perimeter(self):
        side1 = self.side_length(self.p1, self.p2)
        side2 = self.side_length(self.p2, self.p3)
        side3 = self.side_length(self.p3, self.p4)
        side4 = self.side_length(self.p4, self.p1)
        return side1 + side2 + side3 + side4

现在我通过明确告诉它使用四边形的点来调用side_length方法,但有没有办法隐式使用“p1”和“p2”而不需要告诉它使用四边形的点(当我只想使用p1和p2并暗示python使用四边形的点时,我正在使用q.p1和q.p2)?我已经意识到它基本上是一个静态方法,我希望它使用类字段而不是任何一点。

q = Quadrilateral(p1, p2, p3, p4)
print(q.to_string())
print("Side length between " + q.p1.to_string() + " and " + q.p2.to_string() + ": " + str(q.side_length(q.p1, q.p2)))
print("Perimeter is: " + str(q.perimeter()))

我还有其他冗余问题 - 比如有没有更好的方法来最初定义四边形类中的点p1,p2,p3,p4,是否有更简单的方法来计算四边形的周长?

3 个答案:

答案 0 :(得分:0)

对于https://codereview.stackexchange.com来说,这看起来几乎是一个完美的问题,但无论如何我都会进行编辑。

在开始时注意:

  • 在你能够
  • 之前避免上课
  • 尝试编写更少的垃圾/支持/样板代码和更有趣的代码
  • 在制作新内容之前重新编写python所提供的内容
  • 至少写一个小测试或assert
  • 在学习时跳过上述规则

以下是我对您的代码的编辑,希望您找到一些有用的想法/线索:

# not needed
#import math

class Point:
#FIXME: delete this and do some reading on class attributes
#    x = 0.0
#    y = 0.0

    def __init__(self, x, y):
        self.x = x
        self.y = y

#FIXME: use __str__ method for this
#    def to_string(self):
#        return "{X: " + str(self.x) + ", Y: " + str(self.y) + "}"

    def distance(self, p):
        return ((self.x - p.x) ** 2 + (self.y - p.y) ** 2) ** .5                    


class Quadrilateral:
# FIXME: same  as in class Point
#    p1 = 0
#    p2 = 0
#    p3 = 0
#    p4 = 0

# FIXED: you do not want anything undefined for constructor
#    def __init__(self, p1=Point(), p2=Point(), p3=Point(), p4=Point()):
    def __init__(self, p1, p2, p3, p4):
        # saving some typing 
        self.points = [p1, p2, p3, p4]
#        self.p2 = p2
#        self.p3 = p3
#        self.p4 = p4
#        print("Quadrilateral Constructor")

# FIXME: please make some life easier
#    def to_string(self):
#        return "{P1: " + self.p1.ToString() + "}, " + "{P2: " + self.p2.ToString() + "}, " + \
#           "{P3: " + self.p3.ToString() + "}," + "{P4: " + self.p4.ToString() + "}"

    def side(self, vertex_n):
        # TODO: elaborate some protection against wrong *vertex_n*
        a, b = self.points[vertex_n], self.points[vertex_n-1]
        return a.distance(b)          

    @property
    def perimeter(self):
        return sum([self.side(i) for i in range(4)])
        # any duplicated code is a sign of danger: somehtign is going wrong!
        #side1 = self.side_length(self.p1, self.p2)
        #side2 = self.side_length(self.p2, self.p3)
        #side3 = self.side_length(self.p3, self.p4)
        #side4 = self.side_length(self.p4, self.p1)
        #return side1 + side2 + side3 + side4

pts = [Point(x, y) for x, y in [(0,0), (1,0), (1,1), (0,1)]]        
assert Quadrilateral(*pts).perimeter == 4

作为练习,您可以创建父类Polygon并从中继承Quadrilateral。

更新:在我写的原始答案中:

# COMMENT: class Point better be a namedtuple, please google it and use it

对于一位受人尊敬的读者来说,这听起来过于绝对,他指出:

  

Point是否可变取决于您想要在应用中使用点数做什么。如果您没有任何理由不这样做,通常默认使用不可变。

换句话说,除非您确定数据结构不应该改变,否则不要急于namedtuple。对我来说听起来过于保护,但你不能否认你选择数据结构的逻辑取决于你打算如何使用它。

要将某些内容转换为namedtuple,请在Raymond Hettinger - Beyond PEP 8 -- Best practices for beautiful intelligible code查看一个很好的重构示例。

其他建议的方向是使用dataclass,可在标准库中找到Python 3.7

答案 1 :(得分:0)

这是你的意思吗?

def side_length(self, side):
    if side == 1:
        vertex1 = self.p1
        vertex2 = self.p2
    elif side == 2:
        vertex1 = self.p2
        vertex2 = self.p3
    elif side == 3:
        vertex1 = self.p3
        vertex2 = self.p4
    elif side == 4:            
        vertex1 = self.p4
        vertex2 = self.p1
    else:
        print("Error! No side {}".format(side))
        return None
    return math.sqrt((vertex2.x - vertex1.x)**2 + (vertex2.y - vertex1.y)**2)

答案 2 :(得分:0)

我建议您更改跟踪积分的方式。不要使用名称中包含数字的四个单独属性,而是使用列表。然后,您可以将索引传递给side_lengths方法:

def __init__(self, p1, p2, p3, p4):
    self.points = [p1, p2, p3, p4]

def side_length(self, n):
    vertex1 = self.points[n]
    vertex2 = self.points[n-1] # if the index becomes -1, that's ok, it will wrap around
    return math.sqrt((vertex2.x - vertex1.x)**2 + (vertex2.y - vertex1.y)**2)

def perimeter(self):
    return sum(side_length(i) for i in range(4))

我还建议您将to_string函数重命名为__str__,因为在某些情况下Python会自动为您调用该方法(例如当您打印对象时)。 / p>