dar*_*ate 3 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
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我们有相同数量的属性要初始化,而且它们是相关的,这样您就可以从一个属性获得另一个属性。确实,在这个特定的示例中,我们可以处理以下事实:顶点是元组,而边可能是浮点数(或字符串或其他东西)但当然情况并非总是如此。
一种可能的解决方案是将初始化过程委托给其他函数,例如:
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
Run Code Online (Sandbox Code Playgroud)
但它看起来不太干净,所有像“区域”这样的功能都必须检查属性是否正确实例化(或采用 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
Run Code Online (Sandbox Code Playgroud)
这种其他方法似乎更可取,但是我不确定在 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
Run Code Online (Sandbox Code Playgroud)
要获取 的实例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)
Run Code Online (Sandbox Code Playgroud)
这里唯一的区别是Triangle(p1, p2, p3)隐藏了对 的隐式调用Triangle.__new__,它是一个类方法,就像by_vertices和 一样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 会隐式调用 的__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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5890 次 |
| 最近记录: |