我正在创建一个继承自collections.UserList
的类,它具有与NumPy的ndarray
非常相似的功能(仅用于练习目的)。关于涉及类属性修改的递归函数,我遇到了一点障碍:
以flatten
方法为例:
class Array(UserList):
def __init__(self, initlist):
self.data = initlist
def flatten(self):
# recursive function
...
上面可以看到,flatten
方法中有一个奇异参数,即所需的self
参数。理想情况下,递归函数应该接受递归传递的参数通过函数。因此,例如,它可能采用lst
参数,生成签名:
Array.flatten(self, lst)
这解决了必须将lst
设置为self.data
的问题,因此将而不是递归地工作,因为self.data
不会改变。但是,在函数中使用该参数将会很难看,并且会妨碍可能使用该函数的最终用户的用户体验。
所以,这就是我想出的解决方案:
def flatten(self):
self.data = self.__flatten(self.data)
def __flatten(self, lst):
...
return result
另一个解决方案是将__flatten
嵌套在flatten
中,像这样:
def flatten(self):
def __flatten(lst):
...
return result
self.data = __flatten(self.data)
然而,我不确定嵌套是否会是最可读的,因为flatten
不是我的类中唯一的递归函数,所以它很快就会变得混乱。
递归方法不需要任何额外的参数,从调用者的角度来看,这些参数在逻辑上是不必要的;self
参数足以在"子"节点上递归。元素才能工作,因为当您在子对象上调用方法时,子对象在递归调用中被绑定到self
。下面是一个例子:
from itertools import chain
class MyArray:
def __init__(self, data):
self.data = [
MyArray(x) if isinstance(x, list) else x
for x in data]
def flatten(self):
return chain.from_iterable(
x.flatten() if isinstance(x, MyArray) else (x,)
for x in self.data)
用法:
>>> a = MyArray([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
>>> list(a.flatten())
[1, 2, 3, 4, 5, 6, 7, 8]
由于UserList
是一个可迭代对象,您可以使用辅助函数来平铺嵌套的可迭代对象,它可以类似地处理列表和Array
对象:
from collections import UserList
from collections.abc import Iterable
def flatten_iterable(iterable):
for item in iterable:
if isinstance(item, Iterable):
yield from flatten_iterable(item)
else:
yield item
class Array(UserList):
def __init__(self, initlist):
self.data = initlist
def flatten(self):
self.data = list(flatten_iterable(self.data))
a = Array([[1, 2], [3, 4]])
a.flatten(); print(a) # prints [1, 2, 3, 4]
b = Array([Array([1, 2]), Array([3, 4])])
b.flatten(); print(b) # prints [1, 2, 3, 4]
抽象障碍
将array
写为一个单独的模块。flatten
可以像这里的示例实现一样是通用的。这与a_guest的答案不同,因为只有列表是扁平的,而不是所有的可迭代对象。这是你作为模块作者要做的选择-
# array.py
from collections import UserList
def flatten(t): # generic function
if isinstance(t, list):
for v in t:
yield from flatten(v)
else:
yield t
class array(UserList):
def flatten(self):
return list(flatten(self.data)) # specialization of generic function
为什么模块很重要
别忘了你也是模块用户!您可以从模块创建的抽象障碍的两个方面获得好处-
- 作为作者,您可以轻松地扩展,修改和测试您的模块,而不必担心破坏程序的其他部分
- 作为用户,您可以依赖模块的功能,而不必考虑模块是如何编写的或底层数据结构可能是什么
# main.py
from array import array
t = array([1,[2,3],4,[5,[6,[7]]]]) # <- what is "array"?
print(t.flatten())
[1, 2, 3, 4, 5, 6, 7]
作为用户,我们不必回答"什么是array
?"而不是你必须回答"什么是dict
?"或者"什么是iter
?"我们使用这些特性时不需要了解它们的实现细节。它们的内部可能会随着时间的推移而改变,但如果接口保持不变,我们的程序将继续工作而不需要改变。
可重用性
好的程序在很多方面都是可重用的。请参阅python的内置函数来证明这一点,或者参阅Unix哲学的指导原则-
- 编写只做一件事并把它做好的程序。
- 编写程序协同工作
如果你想在程序的其他地方使用flatten
,我们可以很容易地重用它-
# otherscript.py
from array import flatten
result = flatten(something)
通常,类的所有方法都至少有一个名为self的参数,以便能够引用调用该方法的实际对象。
如果你不需要self
在你的函数中,但你仍然想把它包含在一个类中,你可以使用@staticmethod
,只是包括一个普通的函数,像这样:
class Array(UserList):
def __init__(self, initlist):
self.data = initlist
@staticmethod
def flatten():
# recursive function
...
基本上,@staticmethod
允许您将任何函数作为可以在类或类(对象)的实例上调用的方法。所以你可以这样做:
arr = Array()
arr.flatten()
以及this:
Array.flatten()
以下是python文档中的进一步参考:https://docs.python.org/3/library/functions.html#staticmethod