如何在python中实现多个构造函数?

时间:2017-06-23 16:33:46

标签: python constructor constructor-overloading

在python中,不可能多次定义 init 函数,知道语言是如何工作的,这是相当公平的。创建对象时,会调用 init ,因此,有两个会产生不确定性。然而,在某些设计中,这种性质是可取的例如:

class Triangle(object):
  def __init__(self,vertices):
    self.v1 = vertices[0]
    self.v2 = vertices[1]
    self.v3 = vertices[2]

 def area(self):
   # calculate the are using vertices
   ...
   return result

class Triangle(object):
  def __init__(self,sides):
    self.v1 = sides[0]
    self.v2 = sides[1]
    self.v3 = sides[2]

 def area(self):
   # calculate the are using sides
   ...
   return result

在这种情况下,我们有相同数量的属性来初始化,并且它们是相关的,因此从一个,你可以获得另一个。真实,在这个特定的例子中,可以使用顶点是元组而边的事实可能是漂浮物(或字符串或其他东西),但当然并非总是如此。

一种可能的解决方案是将初始化过程委托给其他函数,例如:

class Triangle(object):
  def __init__(self):
    self.v1 = None
    self.v2 = None
    self.v3 = None

  def byVertices(self,vertices):
    self.v1 = vertices[0]
    self.v2 = vertices[1]
    self.v3 = vertices[2]

 def sidesToVertices(self,sides):
    # converts sides to vertices
    ...
    return vertices

 def bySides(self,sides):
    vertices = sidesToVertices(sides)
    self.v1 = vertices[0]
    self.v2 = vertices[1]
    self.v3 = vertices[2]

 def area(self):
   # calculate the are using vertices
   ...
   return result

但它看起来不是很干净,并且像“area”这样的所有功能都必须检查属性是否正确实例化(或采用try / catch),这是很多代码,加上它破坏了可读性该项目。总的来说,它看起来像是一个廉价的技巧。

另一种选择是告诉实例,您要初始化的属性类型是什么:

class Triangle(object):
  def __init__(self, data, type = "vertices"):
    if type == "sides":
      data = sideToVertices(self,sides)
    else if type == "vertices":
      pass
    else:
      raise(Exception)

   self.v1 = data[0]
   self.v2 = data[1]
   self.v3 = data[3]

 def sidesToVertices(self,sides):
    # converts sides to vertices
    ...
    return vertices



 def area(self):
   # calculate the are using vertices

这种其他方法似乎更受欢迎,但我不确定在 init 中引入逻辑有多少“pythonic”。你对此事有何看法?有没有更好的方法来协调这种情况?

3 个答案:

答案 0 :(得分:3)

备用构造函数是类方法的最常见用例。你真实的" __init__通常是各种类方法的最低共同点。

class Triangle(object):
    def __init__(self, v1, v2, v3):
        self.v1 = v1
        self.v2 = v2
        self.v3 = v3

    # This is just here to demonstrate, since it is just
    # wrapping the built-in __new__ for no good reason.
    @classmethod
    def by_vertices(cls, vertices):
        # Make sure there are exactly three vertices, though :)
        return cls(*vertices)

    @staticmethod
    def sidesToVertices(sides):
        # converts sides to vertices
        return v1, v2, v3 

    @classmethod
    def by_sides(cls, sides):
        return cls(*sides_to_vertices(sides)) 

    def area(self):
        # calculate the are using vertices
        ...
        return result

要获取Triangle的实例,您可以编写以下任何内容:

t = Triangle(p1, p2, p3)
t = Triangle.by_vertices(p1, p2, p3)  # Same as the "direct" method
t = Triangle.by_sides(s1, s2, s3)

这里唯一的区别是Triangle(p1, p2, p3)隐藏了对Triangle.__new__的隐式调用,这是一种类方法,就像by_verticesby_sides一样。 (实际上,您可以简单地定义by_vertices = __new__。)在所有这三种情况下,Python都会在Triangle.__init__返回时隐式调用cls

(请注意by_vertices会生成一个特定的三角形,而by_sides可以生成任意数量的#34;等效的#34;三角形,这些三角形的位置和旋转相对于原点不同。相反,可以认为by_sides生成"真正的"三角形,by_vertices指定特定位置的三角形。这些都与手头的问题无关。)

切向背景。

t = Triangle(v1, v2, v3)是"正常"方法,但这是什么意思? Triangle是一个类,而不是一个函数。要回答这个问题,您需要了解元类和__call__方法。

__call__用于使实例可调用。 my_obj(args)成为my_obj.__call__(args)的语法糖,它本身就像所有实例方法一样,是type(my_obj).__call__(my_obj, args)的语法糖(对第一个近似值)。

您已经听说Python中的所有内容都是对象。对于课程也是如此;每个类对象都是其元类的实例,即 类型的类型。默认值为type,因此Triangle(v1, v2, v3)可能会Triangle.__call__(v1, v2, v3)type.__call__(Triangle, v1, v2, v3)

除此之外,type.__call__做了什么?不多。它只是为其第一个参数调用适当的类方法__new__。也就是type.__call__(Triangle, v1, v2, v3) == Triangle.__new__(v1, v2, v3)。然后Python会在__init__的返回值上隐式调用Triangle.__new__,如果它实际上是Triangle的实例。因此,您可以将type.__call__视为定义为

def __call__(cls, *args, **kwargs):
    obj = cls.__new__(*args, **kwargs)
    if isinstance(obj, cls):
       cls.__init__(obj, *args, **kwargs)
    return obj

答案 1 :(得分:1)

您可以使用可选参数并使用更方便的参数调用构造函数。

class Triangle(object):
    def __init__(self, vertices=None, sides=None):
        if vertices is not None:
            # Initialize with vertices
        elif sides is not None:
            # Initialize with sides
        else:
            # Error: neither parameter was passed!
my_sides = ...
my_vertices = ...

triangle_sides = Triangle(sides=my_sides)
triangle_vertices = Triangle(vertices=my_vertices)

答案 2 :(得分:0)

class Employee:
    def __init__(self, fname, lname, salary):
        self.fname = fname
        self.lname = lname
        self.salary = salary

    def __init__(self, *employee):
        self.fname = employee[0]
        self.lname = employee[1]


abdullah = Employee("Abdullah", "Aqib", 10000)
mustafa = Employee("Mustafa", "Ahmad")

print(abdullah.__dict__)
print(mustafa.__dict__)