是 subclassing a python class 来更改初始化程序的最佳模式吗?
Is subclassing a python class to change the initialiser the best pattern to use?
我正在设计一个功能相当强大的库 class - 我们可以将其视为一个列表。
我希望用户能够创建自己的列表,并预先填充特定内容。例如,诸如“向列表中添加 n 个相同的项目”或“使用 a->b 中的数字进行初始化”之类的事情。请注意,这些东西并没有真正改变列表的工作方式,它们只是以某种复杂的方式进行设置。
用户将需要从不同的代码位使用这些,以便将来能够重用它们。
我可以看到用户执行此操作的两种可能方式:
创建一个为他们构建对象的函数,例如:
def make_item_list(n,x):
ls = [x]
ls *= n
return ls
或者,子class列表以添加初始化程序:
class RepeatList(List):
def __init__(self,n,x):
super().__init__()
self.listitems = [x]*n
显然这是一个玩具示例,但其中哪个更明智 'pythonic'?如果 Python 中还有其他模式可以做到这一点,它们是什么?如果模式的选择取决于我没有提到的其他因素,它是什么以及它将如何影响决策制定?如果您需要,我很乐意提供更多信息。谢谢!
我倾向于使用 class,因为您可以扩展它的功能,例如添加对用户有帮助的方法。
class List:
pass
class GenerateList(List):
def __init__(self, n, x, repeat=True):
super().__init__()
if repeat:
self.listitems = [x] * n
else:
self.listitems = list(range(n, x+1))
def get_even(self, flip=False):
ret = [v for v in self.listitems if v%2 == 0]
return ret if not flip else ret[::-1]
def get_odd(self, flip=False):
ret = [v for v in self.listitems if v%2 != 0]
return ret if not flip else ret[::-1]
# Request a repeated list.
m = GenerateList(3, 2, repeat=True)
repeatlist = m.listitems
# Request a list in a range.
a = GenerateList(1, 10, repeat=False)
mylist = a.listitems
mylistodd = a.get_odd()
mylisteven = a.get_even(flip=True)
print(repeatlist)
print(mylist)
print(mylistodd)
print(mylisteven)
输出:
[2, 2, 2]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 3, 5, 7, 9]
[10, 8, 6, 4, 2]
大多数风格指南都不赞成过多的子classing。 pylint 的默认配置会在没有至少几种方法的情况下抱怨 classes 是有原因的。 Python 有足够多的轻量级方法来实现相同的目的。正如您自己所说,工厂功能做同样的工作。
也有点别扭。您是否真的希望您的用户创建 class 类 ListFromFile
和 ListFromString
而不是简单的函数 make_list_from_file
和 make_list_from_string
?
除了简单的风格,还有功能上的原因需要考虑:
- 子class 将允许您检查类型信息。这有关系吗?可能不是,但当然有些情况下你 subclass 只是为了创建一个新类型的简单目的,例如异常 classes
- 子class 具有更高的运行时开销。特别是方法查找会比较慢。
- Subclassing 可能会导致混乱的行为,或者在编程时可能需要额外注意。例如,假设您要实现
__getitem__(self, slice)
并喜欢 Python 的列表,return 给定切片的副本(例如 list[0:10]
)。副本是您的基础 class 还是您的子 class 的实例?如果是subclass的实例,如何确保init方法接受正确的参数?如果它将是基 class 的一个实例,任何其他被覆盖的行为都将消失。
我建议如下:
- 创建一个简单而健壮的 init 方法并告诉人们不要覆盖它,或者只用相同的参数覆盖它
- 使用 class 方法作为某些类型参数的附加构造函数,例如来自文件,来自 python 列表,无论什么
- 对于简单的情况提倡简单的独立函数
- 仅在添加新行为时建议 subclassing
- 提倡在为这些复杂情况添加新的构造函数时添加新的class方法
最后一个想法:了解你的用户。如果你和我一样,他们更熟悉使用自由函数和模块的结构化编程,而不是使用继承的面向对象编程。当然,如果你的用户对OOP比较熟悉,可能是因为你曾经是Java店铺,可能恰恰相反
附录:作为 subclassing 出错的案例研究,请查看 numpy 及其 ndarray subclasses。特别是矩阵和内存映射。找个时间尝试一下,看看在出现问题之前你能走多远,因为语义不一样。如果将矩阵传递给函数,您可能需要一个矩阵作为 return 值。但是如果你传递一个 memmap,你就不能这样做,因为你不能简单地创建一个新的 memmap。现在,您还必须仔细考虑每个函数是否要使用 np.asarray
或 np.asanyarray
来清理您的输入,并了解您可能调用或不调用的不同语义。
如果连 numpy 的人都搞砸了,您和您的用户正确的可能性有多大?
我正在设计一个功能相当强大的库 class - 我们可以将其视为一个列表。
我希望用户能够创建自己的列表,并预先填充特定内容。例如,诸如“向列表中添加 n 个相同的项目”或“使用 a->b 中的数字进行初始化”之类的事情。请注意,这些东西并没有真正改变列表的工作方式,它们只是以某种复杂的方式进行设置。
用户将需要从不同的代码位使用这些,以便将来能够重用它们。
我可以看到用户执行此操作的两种可能方式:
创建一个为他们构建对象的函数,例如:
def make_item_list(n,x):
ls = [x]
ls *= n
return ls
或者,子class列表以添加初始化程序:
class RepeatList(List):
def __init__(self,n,x):
super().__init__()
self.listitems = [x]*n
显然这是一个玩具示例,但其中哪个更明智 'pythonic'?如果 Python 中还有其他模式可以做到这一点,它们是什么?如果模式的选择取决于我没有提到的其他因素,它是什么以及它将如何影响决策制定?如果您需要,我很乐意提供更多信息。谢谢!
我倾向于使用 class,因为您可以扩展它的功能,例如添加对用户有帮助的方法。
class List:
pass
class GenerateList(List):
def __init__(self, n, x, repeat=True):
super().__init__()
if repeat:
self.listitems = [x] * n
else:
self.listitems = list(range(n, x+1))
def get_even(self, flip=False):
ret = [v for v in self.listitems if v%2 == 0]
return ret if not flip else ret[::-1]
def get_odd(self, flip=False):
ret = [v for v in self.listitems if v%2 != 0]
return ret if not flip else ret[::-1]
# Request a repeated list.
m = GenerateList(3, 2, repeat=True)
repeatlist = m.listitems
# Request a list in a range.
a = GenerateList(1, 10, repeat=False)
mylist = a.listitems
mylistodd = a.get_odd()
mylisteven = a.get_even(flip=True)
print(repeatlist)
print(mylist)
print(mylistodd)
print(mylisteven)
输出:
[2, 2, 2]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 3, 5, 7, 9]
[10, 8, 6, 4, 2]
大多数风格指南都不赞成过多的子classing。 pylint 的默认配置会在没有至少几种方法的情况下抱怨 classes 是有原因的。 Python 有足够多的轻量级方法来实现相同的目的。正如您自己所说,工厂功能做同样的工作。
也有点别扭。您是否真的希望您的用户创建 class 类 ListFromFile
和 ListFromString
而不是简单的函数 make_list_from_file
和 make_list_from_string
?
除了简单的风格,还有功能上的原因需要考虑:
- 子class 将允许您检查类型信息。这有关系吗?可能不是,但当然有些情况下你 subclass 只是为了创建一个新类型的简单目的,例如异常 classes
- 子class 具有更高的运行时开销。特别是方法查找会比较慢。
- Subclassing 可能会导致混乱的行为,或者在编程时可能需要额外注意。例如,假设您要实现
__getitem__(self, slice)
并喜欢 Python 的列表,return 给定切片的副本(例如list[0:10]
)。副本是您的基础 class 还是您的子 class 的实例?如果是subclass的实例,如何确保init方法接受正确的参数?如果它将是基 class 的一个实例,任何其他被覆盖的行为都将消失。
我建议如下:
- 创建一个简单而健壮的 init 方法并告诉人们不要覆盖它,或者只用相同的参数覆盖它
- 使用 class 方法作为某些类型参数的附加构造函数,例如来自文件,来自 python 列表,无论什么
- 对于简单的情况提倡简单的独立函数
- 仅在添加新行为时建议 subclassing
- 提倡在为这些复杂情况添加新的构造函数时添加新的class方法
最后一个想法:了解你的用户。如果你和我一样,他们更熟悉使用自由函数和模块的结构化编程,而不是使用继承的面向对象编程。当然,如果你的用户对OOP比较熟悉,可能是因为你曾经是Java店铺,可能恰恰相反
附录:作为 subclassing 出错的案例研究,请查看 numpy 及其 ndarray subclasses。特别是矩阵和内存映射。找个时间尝试一下,看看在出现问题之前你能走多远,因为语义不一样。如果将矩阵传递给函数,您可能需要一个矩阵作为 return 值。但是如果你传递一个 memmap,你就不能这样做,因为你不能简单地创建一个新的 memmap。现在,您还必须仔细考虑每个函数是否要使用 np.asarray
或 np.asanyarray
来清理您的输入,并了解您可能调用或不调用的不同语义。
如果连 numpy 的人都搞砸了,您和您的用户正确的可能性有多大?