类级递归"Pythonic"?



我正在创建一个继承自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

为什么模块很重要

别忘了你也是模块用户!您可以从模块创建的抽象障碍的两个方面获得好处-

  1. 作为作者,您可以轻松地扩展,修改和测试您的模块,而不必担心破坏程序的其他部分
  2. 作为用户,您可以依赖模块的功能,而不必考虑模块是如何编写的或底层数据结构可能是什么
# 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哲学的指导原则-

  1. 编写只做一件事并把它做好的程序。
  2. 编写程序协同工作

如果你想在程序的其他地方使用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

最新更新