Python API设计中的重载(或替代)

bub*_*bba 5 python overloading

我有一个现有的大型程序库,目前有一个.NET绑定,我正在考虑编写一个Python绑定.现有API广泛使用基于签名的重载.所以,我有很多静态函数集合,如:

Circle(p1, p2, p3) -- Creates a circle through three points
Circle(p, r)       -- Creates a circle with given center point and radius
Circle(c1, c2, c3) -- Creates a circle tangent to three curves
Run Code Online (Sandbox Code Playgroud)

在某些情况下,必须以不同的方式使用相同的输入,因此基于签名的重载不起作用,而我必须使用不同的函数名称.例如

BezierCurve(p1,p2,p3,p4) -- Bezier curve using given points as control points
BezierCurveThroughPoints(p1,p2,p3,p4) -- Bezier curve passing through given points
Run Code Online (Sandbox Code Playgroud)

我想第二种技术(使用不同的函数名称)可以在Python API中的任何地方使用.所以,我会的

CircleThroughThreePoints(p1, p2, p3)
CircleCenterRadius(p, r)
CircleTangentThreeCurves(c1, c2, c3)
Run Code Online (Sandbox Code Playgroud)

但这些名字看起来令人不快(我不喜欢缩写),而发明所有这些将是一个相当大的挑战,因为该库有数千个功能.

低优先级:
努力(就我而言) - 我不在乎是否需要编写大量代码.
性能

高优先级:
易于使用/理解呼叫者(许多人将编程新手).
我很容易写出好的文档.
简单 - 避免在调用者代码中使用高级概念.

我确信我不是第一个希望在Python中使用基于签名的重载的人.人们通常使用哪些解决方法?

dan*_*ano 8

一个选项是在构造函数中排除关键字参数,并包含逻辑以确定应该使用的内容:

class Circle(object):
    def __init__(self, points=(), radius=None, curves=()):
        if radius and len(points) == 1:
            center_point = points[0]
            # Create from radius/center point
        elif curves and len(curves) == 3:
            # create from curves
        elif points and len(points) == 3:
            # create from points
        else:
            raise ValueError("Must provide a tuple of three points, a point and a radius, or a tuple of three curves)
Run Code Online (Sandbox Code Playgroud)

您还可以使用classmethods为API的用户提供便利:

class Circle(object):
    def __init__(self, points=(), radius=None, curves=()):
         # same as above

    @classmethod
    def from_points(p1, p2, p3):
        return cls(points=(p1, p2, p3))

    @classmethod
    def from_point_and_radius(cls, point, radius):
        return cls(points=(point,), radius=radius)

    @classmethod
    def from_curves(cls, c1, c2, c3):
        return cls(curves=(c1, c2, c3))
Run Code Online (Sandbox Code Playgroud)

用法:

c = Circle.from_points(p1, p2, p3)
c = Circle.from_point_and_radius(p1, r)
c = Circle.from_curves(c1, c2, c3)
Run Code Online (Sandbox Code Playgroud)


Blc*_*ght 5

有几种选择.

您可以拥有一个接受任意数量的参数(带有*args和/或**varargs语法)的构造函数,并根据参数的数量和类型执行不同的操作.

或者,您可以将辅助构造函数编写为类方法.这些被称为"工厂"方法.如果您有多个构造函数,它们使用相同类的相同数量的对象(如您的BezierCurve示例中所示),这可能是您唯一的选择.

如果你不介意覆盖__new__而不是__init__,你甚至可以同时拥有这两种__new__方法,方法本身处理一种形式的参数,并将其他类型引用到工厂方法进行正则化.以下是可能的示例,包括多个签名的doc字符串__new__:

class Circle(object):
    """Circle(center, radius) -> Circle object
       Circle(point1, point2, point3) -> Circle object
       Circle(curve1, curve2, curve3) -> Circle object

       Return a Circle with the provided center and radius. If three points are given,
       the center and radius will be computed so that the circle will pass through each
       of the points. If three curves are given, the circle's center and radius will
       be chosen so that the circle will be tangent to each of them."""

    def __new__(cls, *args):
        if len(args) == 2:
            self = super(Circle, cls).__new__(cls)
            self.center, self.radius = args
            return self
        elif len(args) == 3:
            if all(isinstance(arg, Point) for arg in args):
                return Circle.through_points(*args)
            elif all(isinstance(arg, Curve) for arg in args):
                return Circle.tangent_to_curves(*args)
        raise TypeError("Invalid arguments to Circle()")

    @classmethod
    def through_points(cls, point1, point2, point3):
        """from_points(point1, point2, point3) -> Circle object

        Return a Circle that touches three points."""

        # compute center and radius from the points...
        # then call back to the main constructor:
        return cls(center, radius)

    @classmethod
    def tangent_to_curves(cls, curve1, curve2, curve3):
        """from_curves(curve1, curve2, curve3) -> Circle object

        Return a Circle that is tangent to three curves."""

        # here too, compute center and radius from curves ...
        # then call back to the main constructor:
        return cls(center, radius)
Run Code Online (Sandbox Code Playgroud)


Enr*_*ata 2

你可以使用字典,就像这样

Circle({'points':[p1,p2,p3]})
Circle({'radius':r})
Circle({'curves':[c1,c2,c3])
Run Code Online (Sandbox Code Playgroud)

初始化器会说

def __init__(args):
  if len(args)>1:
    raise SomeError("only pass one of points, radius, curves")
  if 'points' in args: {blah}
  elsif 'radius' in args: {blahblah}
  elsif 'curves' in args: {evenmoreblah}
  else: raise SomeError("same as above")
Run Code Online (Sandbox Code Playgroud)

  • 相反,您可能应该使用“**kwargs”来代替,或者您想强制使用关键字参数。 (3认同)