独立的功能或方法

时间:2011-10-25 23:13:15

标签: api python readability

我需要以一种返回同一类的第三个对象的方式处理类的两个对象,并且我试图确定将它作为一个接收两个对象的独立函数来做是否更好返回第三个或作为一个方法,它将获取另一个对象并返回第三个。


举个简单的例子。会这样:

from collections import namedtuple

class Point(namedtuple('Point', 'x y')):
    __slots__ = ()
    #Attached to class
    def midpoint(self, otherpoint):
        mx = (self.x + otherpoint.x) / 2.0
        my = (self.y + otherpoint.y) / 2.0
        return Point(mx, my)

a = Point(1.0, 2.0)
b = Point(2.0, 3.0)

print a.midpoint(b)
#Point(x=1.5, y=2.5)

或者这个:

from collections import namedtuple

class Point(namedtuple('Point', 'x y')):
    __slots__ = ()


#not attached to class
#takes two point objects
def midpoint(p1, p2):
    mx = (p1.x + p2.x) / 2.0
    my = (p1.y + p2.y) / 2.0
    return Point(mx, my)


a = Point(1.0, 2.0)
b = Point(2.0, 3.0)

print midpoint(a, b)
#Point(x=1.5, y=2.5)

为什么一个人会优先于另一个?


当我提出问题时,这似乎远没有我预期的那么明确。

总之,似乎a.midpoint(b)之类的东西似乎不是首选的,因为它似乎在一个真正的对称函数中给出了一个特殊的位置,它返回一个全新的点实例。但它似乎主要是在类似独立模块函数或附加到类的函数之间的品味和风格问题,但并不意味着由insance调用,例如Point.midpoint(a,b)。

我认为,就个人而言,我在风格上倾向于独立的模块功能,但这可能取决于具体情况。如果函数肯定与类紧密绑定,并且存在命名空间污染或潜在混淆的风险,那么创建类函数可能更有意义。

此外,有些人提到使功能更通用,可能通过实现类的其他功能来支持这一功能。在这个处理点和中点的特殊情况下,这可能是整体最佳方法。它支持多态和代码重用,并且具有高可读性。但在很多情况下,这不起作用(这个项目激励我去问这个例子),但是点和中点似乎是一个简洁易懂的例子来说明这个问题。

谢谢大家,这很有启发性。

6 个答案:

答案 0 :(得分:3)

第一种方法是合理的,在概念上与 set.union set.intersection 的概念不同。任何func(Point, Point) --> Point明显与Point类相关,因此毫无疑问干扰了该类的统一性或凝聚力。

如果涉及不同的课程,那将是一个更艰难的选择:draw_perpendicular(line, point) --> line。要解决类的选择,您可以选择具有最相关逻辑的类。例如, str.join 需要字符串分隔符和字符串列表。它可能是一个独立的函数(就像过去的字符串模块一样),或者它可能是列表上的方法(但它只适用于字符串列表),或者是字符串上的方法。选择后者是因为连接更多地是关于字符串而不是关于列表。这种选择虽然导致了可疑的尴尬表达delimiter.join(things_to_join)

我不同意推荐使用类方法的其他受访者。这些通常用于备用构造函数签名,但不用于类实例的转换。例如, datetime.fromordinal 是一种类方法,用于从类的实例以外的其他内容构造日期(在本例中,来自 int )。这与 datetime.replace 形成对比,后者是基于现有实例创建新日期时间实例的常规方法。这应该引导你远离使用classmethod进行中点计算。

另一个想法:如果你使用Point()类保持midpoint(),它可以创建具有相同Point API但具有不同内部表示的其他类(即极坐标对于某些类型可能更方便工作比笛卡尔坐标)。如果midpoint()是一个单独的函数,你就会开始失去封装和连贯接口的好处。

答案 1 :(得分:2)

我会选择第二种选择,因为在我看来,它比第一种选择更清晰。您正在两点之间执行中点操作;不是尊重到点的中点操作。同样,此界面的自然扩展可能是定义dotcrossmagnitudeaveragemedian等。其中一些功能将会运作成对的Points和其他人可以在列表上运行。使它成为一个函数使它们都具有一致的接口。

将其定义为函数还允许它与呈现.x .y接口的任何对象一起使用,同时使其成为一种方法要求两者中的至少一个是{ {1}}。

最后,为了解决函数的位置,我认为将它与Point类放在同一个包中是有意义的。这将它放在同一名称空间中,这清楚地表明它与Point的关系,在我看来,它比静态或类方法更具pythonic。

<强>更新 进一步阅读Point与包/模块的Pythonicness:

在Thomas Wouter对问题What is the difference between staticmethod and classmethod in Python的回答和Mike Steder对init and arguments in Python的回答中,作者指出相关函数的包或模块可能是更好的解决方案。 Thomas Wouter说:

  

[staticmethod]在Python中基本没用 - 你可以使用模块函数而不是静态方法。

Mike Steder评论道:

  

如果你发现自己创建的对象只包含静态方法,那么pythonic要做的就是创建一个相关函数的新模块。

但是,Codeape正确地指出,@staticmethod的调用约定将与该类型共同定位功能。由于Point.midpoint(a,b)方法是staticmethod,BDFL似乎也值@staticmethod

我个人的偏好是出于上述原因使用功能,但似乎__new__和独立功能之间的选择主要在旁观者眼中。

答案 2 :(得分:2)

在这种情况下,您可以使用运算符重载:

from collections import namedtuple

class Point(namedtuple('Point', 'x y')):
    __slots__ = ()
    #Attached to class
    def __add__(self, otherpoint):
        mx = (self.x + otherpoint.x)
        my = (self.y + otherpoint.y)
        return Point(mx, my)

    def __div__(self, scalar):
        return Point(self.x/scalar, self.y/scalar)


a = Point(1.0, 2.0)
b = Point(2.0, 3.0)

def mid(a,b): # general function
    return (a+b)/2

print mid(a,b)

我认为决定主要取决于功能的一般性和抽象性。如果您可以以适用于实现一小组干净接口的所有对象的方式编写函数,那么您可以将其转换为单独的函数。你的函数所依赖的接口越多,它们越具体,就把它放在类上就越有意义(因为这个类的实例很可能是函数无论如何都会使用的唯一对象)。

答案 3 :(得分:1)

我会选择版本1,因为这样点的所有功能都存储在点类中,即分组相关功能。此外,点对象最了解其数据的含义和内部工作原理,因此它是实现您的功能的正确位置。一个外部函数,例如在C ++中,必须是一个朋友,它闻起来像一个黑客。

答案 4 :(得分:1)

另一种选择是使用@classmethod。在这种情况下,这可能是我更喜欢的。

class Point(...):
    @classmethod
    def midpoint(cls, p1, p2):
        mx = (p1.x + p2.x) / 2.0
        my = (p1.y + p2.y) / 2.0
        return cls(mx, my)

# ...
print Point.midpoint(a, b)

答案 5 :(得分:0)

另一种方法是通过namedtuple的下标接口访问xy。然后,您可以将midpoint函数完全概括为 n 维度。

class Point(namedtuple('Point', 'x y')):
    __slots__ = ()

def midpoint(left, right):
    return tuple([sum(a)/2. for a in zip(left, right)])

此设计适用于Point类, n -tuples,长度为 n 的列表等。例如:

>>> midpoint(Point(0,0), Point(1,1))
(0.5, 0.5)
>>> midpoint(Point(5,1), (3, 2))
(4.0, 1.5)
>>> midpoint((1,2,3), (4,5,6))
(2.5, 3.5, 4.5)