Python对象的好风格

时间:2012-12-22 00:06:35

标签: python oop data-structures styles

我在Python之前的大多数编程都是用C ++或Matlab编写的。我没有CS学位(几乎完成了物理学博士学位),但已经完成了一些课程和大量的实际编程。现在,我正在参加Coursera的算法课程(顺便说一下,斯坦福大学的一位教授)。我决定用Python实现homeworks。但是,有时我发现自己想要语言不那么容易支持的东西。我习惯于在C ++中为事物创建类和对象,只是为了将数据组合在一起(即没有方法时)。然而,在Python中,你可以动态添加字段,我基本上最终想要的是Matlab结构。我认为这可能是一个标志,我没有使用好的风格,并以“Pythonic”的方式做事。

下面是我对union-find数据结构的实现(对于Kruskal的算法)。虽然实现相对较短并且运行良好(没有太多的错误检查),但有几个奇点。例如,我的代码假定最初传入union-find的数据是一个对象列表。但是,如果传入明确的数据片段列表(即整数列表),则代码失败。是否有一些更清晰,更Pythonic的方式来实现它?我试图谷歌这一点,但大多数例子都非常简单,并且更多地涉及过程代码(即在python中执行for循环的“正确”方法)。

class UnionFind:
    def __init__(self,data):
        self.data = data

        for d in self.data:
            d.size = 1
            d.leader = d
            d.next = None
            d.last = d

    def find(self,element):
        return element.leader

    def union(self,leader1,leader2):
        if leader1.size >= leader2.size:
            newleader = leader1
            oldleader = leader2
        else:
            newleader = leader2
            oldleader = leader1

        newleader.size = leader1.size + leader2.size

        d = oldleader
        while d != None:
            d.leader = newleader
            d = d.next

        newleader.last.next = oldleader
        newleader.last = oldleader.last

        del(oldleader.size)
        del(oldleader.last)    

5 个答案:

答案 0 :(得分:2)

一般来说,在Python上做这种事情意味着你试图让你的代码不关心给它的东西,至少不是真正需要的东西。

让我们以你的联合查找算法为例。 union-find算法实际上对传递给它的值唯一做的就是比较它们是否相等。因此,要创建一个通常有用的UnionFind类,您的代码不应该依赖于它接收的值而不是相等测试。特别是,您不应该依赖于能够为值分配任意属性。

我建议解决这个问题的方法是让UnionFind使用包装器对象来保存给定值以及使算法工作所需的任何属性。您可以按照另一个答案的建议使用namedtuple,或者创建一个小的包装类。将元素添加到UnionFind时,首先将其包装在其中一个对象中,然后使用包装器对象存储属性leadersize等。唯一的一次访问被包装的东西是检查它是否等于另一个值。

实际上,至少在这种情况下,应该可以安全地假设您的值是可清除的,这样您就可以将它们用作Python字典中的键来查找与给定值对应的包装器对象。当然,并非Python中的所有对象都必须是可以删除的,但是那些不是相对稀少的对象,并且制作能够处理这些对象的数据结构将会有更多的工作。

答案 1 :(得分:0)

如果你不必要的话,更多的pythonic方法是避免繁琐的物体。

class UnionFind(object):
    def __init__(self, members=10, data=None):
        """union-find data structure for Kruskal's algorithm
        members are ignored if data is provided
        """
        if not data:
            self.data = [self.default_data() for i in range(members)]
            for d in self.data:
                d.size   = 1
                d.leader = d
                d.next   = None
                d.last   = d
        else:
            self.data = data

    def default_data(self):
        """create a starting point for data"""
        return Data(**{'last': None, 'leader':None, 'next': None, 'size': 1})

    def find(self, element):
        return element.leader

    def union(self, leader1, leader2):
        if leader2.leader is leader1:
            return
        if leader1.size >= leader2.size:
            newleader = leader1
            oldleader = leader2
        else:
            newleader = leader2
            oldleader = leader1

        newleader.size = leader1.size + leader2.size

        d = oldleader
        while d is not None:
            d.leader = newleader
            d = d.next

        newleader.last.next = oldleader
        newleader.last = oldleader.last

        oldleader.size = 0
        oldleader.last = None

class Data(object):
    def __init__(self, **data_dict):
        """convert a data member dict into an object"""
        self.__dict__.update(**data_dict)

答案 2 :(得分:0)

要检查参数是否属于预期类型,请使用内置的isinstance()函数:

if not isinstance(leader1, UnionFind):
    raise ValueError('leader1 must be a UnionFind instance')

此外,将docstrings添加到函数,类和成员函数是一个好习惯。这样的函数或方法的文档字符串应该描述它的作用,要传递给它的参数以及返回的内容以及可以引发的异常。

答案 3 :(得分:0)

一种选择是使用词典来存储您需要的有关数据项的信息,而不是直接存储项目的属性。例如,您可以引用d.size(其中size[d]size个实例),而不是引用dict。这要求您的数据项可以清除,但不需要在其上分配属性。

以下是您当前代码的直接翻译,以使用此样式:

class UnionFind:
    def __init__(self,data):
        self.data = data
        self.size = {d:1 for d in data}
        self.leader = {d:d for d in data}
        self.next = {d:None for d in data}
        self.last = {d:d for d in data}

    def find(self,element):
        return self.leader[element]

    def union(self,leader1,leader2):
        if self.size[leader1] >= self.size[leader2]:
            newleader = leader1
            oldleader = leader2
        else:
            newleader = leader2
            oldleader = leader1

        self.size[newleader] = self.size[leader1] + self.size[leader2]

        d = oldleader
        while d != None:
            self.leader[d] = newleader
            d = self.next[d]

        self.next[self.last[newleader]] = oldleader
        self.last[newleader] = self.last[oldleader]

最小测试用例:

>>> uf = UnionFind(list(range(100)))
>>> uf.find(10)
10
>>> uf.find(20)
20
>>> uf.union(10,20)
>>> uf.find(10)
10
>>> uf.find(20)
10

除此之外,您还可以考虑稍微改变实现,以减少初始化。这是一个不进行任何初始化的版本(它甚至不需要知道它将要处理的数据集)。它使用路径压缩和逐级联合,而不是始终为集合的所有成员维护最新的leader值。它应该比你当前的代码渐近地快,特别是如果你做了很多工会:

class UnionFind:
    def __init__(self):
        self.rank = {}
        self.parent = {}

    def find(self, element):
        if element not in self.parent: # leader elements are not in `parent` dict
            return element
        leader = self.find(self.parent[element]) # search recursively
        self.parent[element] = leader # compress path by saving leader as parent
        return leader

    def union(self, leader1, leader2):
        rank1 = self.rank.get(leader1,1)
        rank2 = self.rank.get(leader2,1)

        if rank1 > rank2: # union by rank
            self.parent[leader2] = leader1
        elif rank2 > rank1:
            self.parent[leader1] = leader2
        else: # ranks are equal
            self.parent[leader2] = leader1 # favor leader1 arbitrarily
            self.rank[leader1] = rank1+1 # increment rank

答案 4 :(得分:-2)

我猜这里的缩进问题只是将代码输入SO的简单错误。你能否创建一个简单的内置数据类型的子类?例如,您可以通过将数据类型放在括号中来创建列表数据类型的子类:

class UnionFind(list):
'''extends list object'''