gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-03 14:04:54 25 4
gpt4 key购买 nike

在 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

在这种情况下,我们有相同数量的属性要初始化,而且它们是相关的,因此您可以从一个获得另一个。是的,在这个特定示例中,可以处理这样一个事实,即顶点是元组,而边可能是 float (或字符串或其他东西),但当然情况并非总是如此。

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

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”。你对这件事有什么看法?有没有更好的方法来协调这种情况?

最佳答案

备用构造函数是类方法最常见的用例。您的“真实”__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__。)在所有这三种情况下,无论 cls 返回什么,Python 都会隐式调用 Triangle.__init__

(请注意,by_vertices 生成一个特定的三角形,而 by_sides 可以生成任意数量的“等效”三角形,它们仅在位置和相对于原点的旋转方面有所不同。反之, by_sides 可以被认为生成“真正的”三角形,by_vertices 指定特定位置的三角形。这些都与手头的问题没有特别相关。)


切线背景。

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

__call__ 用于使实例可调用。 my_obj(args) 成为 my_obj.__call__(args) 的语法糖,它本身与所有实例方法一样,是 的语法糖(初步近似)类型(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)。如果 Triangle.__new__ 实际上是 Triangle 的一个实例,则 Python 然后隐式调用 __init__。因此,您可以将 type.__call__ 视为定义为

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

关于python - 如何在python中实现多个构造函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44726196/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com