3

在 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

但它看起来不是很干净,并且像“区域”这样的所有功能都必须检查属性是否正确实例化(或采用 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” 。你对这件事有什么看法?有没有更好的方法来协调这种情况?

4

3 回答 3

4

备用构造函数是类方法最常见的用例。您的“真实”__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_verticesand的类方法by_sides。(实际上,您可以简单地定义by_vertices = __new__。)在所有三种情况下,Python 都会隐式调用Triangle.__init__任何cls返回值。

(请注意,by_vertices生成一个特定的三角形,而by_sides可以生成任意数量的“等效”三角形,它们仅在相对于原点的位置和旋转方面有所不同。相反,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 隐式调用if 它__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
于 2017-06-23T16:41:43.480 回答
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)
于 2017-06-23T16:42:59.167 回答
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__)
于 2019-09-08T17:31:10.193 回答