什么是迭代器、可迭代和迭代?

2024-11-21 08:33:00
admin
原创
6
摘要:问题描述:Python 中的“iterable”、“iterator”和“iteration”是什么?它们是如何定义的?另请参阅:如何构建基本迭代器?解决方案 1:迭代是一个通用术语,表示逐个获取某事物的每个项。任何时候,只要您使用显式或隐式循环来遍历一组项,这就是迭代。在Python中,iterable和i...

问题描述:

Python 中的“iterable”、“iterator”和“iteration”是什么?它们是如何定义的?


另请参阅:如何构建基本迭代器?


解决方案 1:

迭代是一个通用术语,表示逐个获取某事物的每个项。任何时候,只要您使用显式或隐式循环来遍历一组项,这就是迭代。

在Python中,iterableiterator具有特定的含义。

可迭代对象是具有__iter__返回迭代器的方法的对象,或者定义了__getitem__可以从零开始获取顺序索引的方法(当IndexError索引不再有效时会引发)。因此,可迭代对象是可以从中获取迭代器的对象。

迭代器是一个具有next(Python 2)或(Python 3)方法的对象__next__

无论何时在 Python 中使用for循环、或map、列表推导式等,都会自动调用该方法从迭代器next中获取每个项目,从而完成迭代的过程。

开始学习的一个好地方是教程中的迭代器部分和标准类型页面中的迭代器类型部分。了解基础知识后,请尝试函数式编程指南中的迭代器部分。

解决方案 2:

以下是我在教授 Python 课程时使用的解释:

ITERABLE 是:

  • 任何可以循环的东西(例如,你可以循环遍历字符串或文件)或

  • 任何可以出现在 for 循环右侧的内容: for x in iterable: ...

  • 任何你可以调用iter()该命令的东西都会返回一个 ITERATOR: iter(obj)或者

  • __iter__定义返回新的 ITERATOR 的对象,或者它可能具有__getitem__适合索引查找的方法。

ITERATOR 是一个对象:

  • 具有记住迭代过程中位置的状态,

  • 使用__next__以下方法:

+ 返回迭代中的下一个值
+ 更新状态以指向下一个值
+ 通过提高来表示完成`StopIteration`
  • 并且它是自可迭代的(意味着它有一个__iter__返回的方法self)。

笔记:

  • Python 3 中的方法在 Python 2 中__next__拼写如下,next

  • 内置函数next()在传递给它的对象上调用该方法。

例如:

>>> s = 'cat'      # s is an ITERABLE
                   # s is a str object that is immutable
                   # s has no state
                   # s has a __getitem__() method 

>>> t = iter(s)    # t is an ITERATOR
                   # t has state (it starts by pointing at the "c"
                   # t has a next() method and an __iter__() method

>>> next(t)        # the next() function returns the next value and advances the state
'c'
>>> next(t)        # the next() function returns the next value and advances
'a'
>>> next(t)        # the next() function returns the next value and advances
't'
>>> next(t)        # next() raises StopIteration to signal that iteration is complete
Traceback (most recent call last):
...
StopIteration

>>> iter(t) is t   # the iterator is self-iterable

解决方案 3:

上述答案都很棒,但正如我所看到的大多数答案一样,对于像我这样的人来说,并没有足够强调区别

此外,人们往往会在前面加上“X 是一个具有__foo__()方法的对象”这样的定义,这会让其变得“过于 Python 化”。这样的定义是正确的——它们基于鸭子类型哲学,但是当试图理解这个概念的简单性时,对方法的关注往往会偏离主题。

因此我添加了我的版本。


在自然语言中,

  • 迭代是从一行元素中一次取出一个元素的过程。

在 Python 中,

  • iterable是一个可迭代的对象,简单地说,它意味着它可以用于迭代,例如循环for。如何使用?通过使用iterator。我将在下面解释。

  • ... 而迭代器是一个定义如何实际进行迭代的对象——具体来说,下一个元素是什么。这就是为什么它必须有
    next()方法。

迭代器本身也是可迭代的,不同之处在于它们的__iter__()方法返回相同的对象(self),无论其项是否已被先前的调用所使用next()


那么当 Python 解释器看到语句时会怎么想呢for x in obj:

看,一个for循环。看起来像是迭代器的工作……我们去弄一个吧。……有这个obj人,我们去问他吧。

“先生obj,您有迭代器吗?”(......调用iter(obj),然后调用
obj.__iter__(),然后高兴地递出一个闪亮的新迭代器_i。)

好的,这很简单...让我们开始迭代吧。(x = _i.next()...... x = _i.next()

由于先生obj成功通过了这项测试(通过使用某种方法返回有效的迭代器),我们用形容词奖励他:你现在可以称他为“可迭代的先生obj”。

但是,在简单情况下,通常不会从单独使用迭代器和可迭代器中受益。因此,您只定义一个对象,它也是自己的迭代器。(Python 并不真正关心_i所分发的内容obj是否光鲜亮丽,而只是它obj本身。)

这就是为什么在我见过的大多数例子中(以及曾经让我一次又一次感到困惑的例子),你会看到:

class IterableExample(object):

    def __iter__(self):
        return self

    def next(self):
        pass

而不是

class Iterator(object):
    def next(self):
        pass

class Iterable(object):
    def __iter__(self):
        return Iterator()

不过,在某些情况下,将迭代器与可迭代对象分开会让您受益,例如当您想要一行项目但需要更多“游标”时。例如,当您想要使用“当前”和“即将推出”元素时,您可以为两者使用单独的迭代器。或者从一个巨大的列表中拉取的多个线程:每个线程都可以有自己的迭代器来遍历所有项目。请参阅上面@Raymond和@glglgl 的回答。

想象一下你可以做什么:

class SmartIterableExample(object):

    def create_iterator(self):
        # An amazingly powerful yet simple way to create arbitrary
        # iterator, utilizing object state (or not, if you are fan
        # of functional), magic and nuclear waste--no kittens hurt.
        pass    # don't forget to add the next() method

    def __iter__(self):
        return self.create_iterator()

笔记:

  • 我再重复一遍:迭代器不是可迭代的。迭代器不能用作for循环中的“源”。for循环主要需要的是__iter__()
    (返回带有的东西next())。

  • 当然,for不是唯一的迭代循环,因此以上也适用于其他一些构造(while...)。

  • 迭代器next()可以抛出 StopIteration 来停止迭代。但这不是必须的,它可以永远迭代或使用其他方法。

  • 在上述“思维过程”中,_i实际上并不存在。这是我编造的名字。

  • Python 3.x 中有一个小小的变化:next()现在必须调用方法(不是内置方法)__next__()。是的,它一直都应该是这样的。

  • 你也可以这样想:iterable 有数据,迭代器拉取下一个项目

免责声明:我不是任何 Python 解释器的开发人员,所以我真的不知道解释器“想”什么。上面的思考仅表明我如何从其他解释、实验和 Python 新手的实际经验中理解该主题。

解决方案 4:

可迭代对象是具有方法的对象__iter__()。它可以被迭代多次,例如list()s 和tuple()s。

迭代器是进行迭代的对象。它由一个__iter__()方法返回,通过其自己的__iter__()方法返回自身,并且有一个next()方法(__next__()在 3.x 中)。

next()迭代是不断调用这个resp.__next__()直到它引发的过程StopIteration

例子:

>>> a = [1, 2, 3] # iterable
>>> b1 = iter(a) # iterator 1
>>> b2 = iter(a) # iterator 2, independent of b1
>>> next(b1)
1
>>> next(b1)
2
>>> next(b2) # start over, as it is the first call to b2
1
>>> next(b1)
3
>>> next(b1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> b1 = iter(a) # new one, start over
>>> next(b1)
1

解决方案 5:

我不知道这是否对任何人有帮助,但我总是喜欢在脑海中将概念形象化,以便更好地理解它们。因此,当我有一个小儿子时,我用砖块和白纸将可迭代/迭代器概念形象化。

假设我们在黑暗的房间里,地板上有我给我儿子的积木。现在,积木的大小和颜色都无所谓了。假设我们有 5 块这样的积木。这 5 块积木可以描述为一个对象- 我们就叫它积木套件吧。我们可以用这个积木套件做很多事情 - 可以拿一块,然后拿第二块,再拿第三块,可以交换积木的位置,把第一块积木放在第二块上面。我们可以用它们做很多事情。因此,这个积木套件是一个可迭代对象序列,因为我们可以遍历每一块积木并用它做一些事情。我们只能像我的小儿子那样做 - 我们一次只能玩一块积木。所以我再次将这个积木套件想象成一个可迭代的对象

现在请记住,我们在黑暗的房间里。或者几乎是黑暗的。问题是我们看不清楚那些砖块,它们是什么颜色,什么形状等等。所以即使我们想用它们做点什么——也就是对它们进行迭代——我们也不知道该做什么,也不知道怎么做,因为太暗了。

我们能做的是,在第一块砖(作为砖块套件的元素)附近放一张白色荧光纸,以便我们能看见第一块砖元素的位置。每次我们从套件中取出一块砖时,我们都会将白纸替换到下一块砖上,以便在暗室中看清。这张白纸只不过是一个迭代器。它也是一个对象。但它是一个可以操作和使用可迭代对象(砖块套件)元素的对象。

顺便说一下,这也解释了我早期在 IDLE 中尝试执行以下操作并收到 TypeError 时所犯的错误:

 >>> X = [1,2,3,4,5]
 >>> next(X)
 Traceback (most recent call last):
    File "<pyshell#19>", line 1, in <module>
      next(X)
 TypeError: 'list' object is not an iterator

这里的列表 X 是我们的积木套件,而不是一张白纸。我需要先找到一个迭代器:

>>> X = [1,2,3,4,5]
>>> bricks_kit = [1,2,3,4,5]
>>> white_piece_of_paper = iter(bricks_kit)
>>> next(white_piece_of_paper)
1
>>> next(white_piece_of_paper)
2
>>>

不知道这是否有用,但对我有帮助。如果有人能确认/纠正这个概念的可视化,我将不胜感激。这将有助于我学到更多。

解决方案 6:

可迭代对象有一个__iter__方法,每次都会实例化一个新的迭代器。

迭代器实现一个__next__返回单个项的方法,以及一个__iter__返回的方法self

因此,迭代器也是可迭代的,但可迭代的不一定是迭代器。

Luciano Ramalho,流利的 Python。

解决方案 7:

我不认为你能得到比文档更简单的内容,但是我会尝试:

  • Iterable是可以迭代的东西。在实践中,它通常意味着一个序列,例如,有开始和结束的东西,以及某种方法来遍历其中的所有项目。

  • 你可以将Iterator视为辅助伪方法(或伪属性),它提供(或保存)可迭代对象中的下一个(或第一个)项。 (实际上,它只是一个定义方法的对象next()

  • 迭代可能最好通过 Merriam-Webster对这个词的定义来解释:

b :重复执行计算机指令序列指定的次数或直到满足某个条件为止 - 比较递归

解决方案 8:

可迭代:可迭代的东西就是可迭代的;比如列表、字符串等序列。它还具有__getitem__方法或__iter__方法。现在如果我们iter()对该对象使用函数,我们将获得一个迭代器。

迭代器:当我们从函数中获取迭代器对象时iter(),我们调用__next__()方法(在 Python3 中)或简单地调用方法next()(在 Python2 中)来逐个获取元素。此类或此类的实例称为迭代器。

来自文档:-

迭代器的使用遍布整个 Python 并使其统一起来。在幕后,for 语句会调用 iter() 容器对象。该函数返回一个迭代器对象,该对象定义了 __next__() 一次访问容器中一个元素的方法。当没有更多元素时, __next__() 会引发 StopIteration 异常,告知 for 循环终止。您可以 __next__() 使用 next() 内置函数调用该方法;此示例展示了它的工作原理:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    next(it)
StopIteration

例如:-

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]


>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s

解决方案 9:

迭代器是实现iternext方法的对象。如果定义了这些方法,我们就可以使用 for 循环或推导式。

class Squares:
    def __init__(self, length):
        self.length = length
        self.i = 0
        
    def __iter__(self):
        print('calling __iter__') # this will be called first and only once
        return self
    
    def __next__(self): 
        print('calling __next__') # this will be called for each iteration
        if self.i >= self.length:
            raise StopIteration
        else:
            result = self.i ** 2
            self.i += 1
            return result

迭代器会耗尽。这意味着在迭代完项目后,您无法重复迭代,而必须创建一个新对象。假设您有一个类,它包含城市属性,并且您想要迭代。

class Cities:
    def __init__(self):
        self._cities = ['Brooklyn', 'Manhattan', 'Prag', 'Madrid', 'London']
        self._index = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self._index >= len(self._cities):
            raise StopIteration
        else:
            item = self._cities[self._index]
            self._index += 1
            return item

Cities 类的实例是一个迭代器。但是,如果您想重复迭代城市,则必须创建一个新对象,这是一项昂贵的操作。您可以将该类分为两个类:一个返回城市,另一个返回一个迭代器,该迭代器将城市作为初始化参数。

class Cities:
    def __init__(self):
        self._cities = ['New York', 'Newark', 'Istanbul', 'London']        
    def __len__(self):
        return len(self._cities)



class CityIterator:
    def __init__(self, city_obj):
        # cities is an instance of Cities
        self._city_obj = city_obj
        self._index = 0
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self._index >= len(self._city_obj):
            raise StopIteration
        else:
            item = self._city_obj._cities[self._index]
            self._index += 1
            return item

现在,如果我们需要创建一个新的迭代器,我们不必再次创建数据,即城市。我们创建城市对象并将其传递给迭代器。但我们仍在做额外的工作。我们可以通过仅创建一个类来实现这一点。

Iterable是实现了iterable 协议的 Python 对象。它只需要__iter__()返回一个新的迭代器对象实例。

class Cities:
    def __init__(self):
        self._cities = ['New York', 'Newark', 'Istanbul', 'Paris']
        
    def __len__(self):
        return len(self._cities)
    
    def __iter__(self):
        return self.CityIterator(self)
    
    class CityIterator:
        def __init__(self, city_obj):
            self._city_obj = city_obj
            self._index = 0

        def __iter__(self):
            return self

        def __next__(self):
            if self._index >= len(self._city_obj):
                raise StopIteration
            else:
                item = self._city_obj._cities[self._index]
                self._index += 1
                return item

迭代器有__iter____next__,可迭代器有__iter__,所以我们可以说迭代器也是可迭代器,但它们是会耗尽的可迭代器。另一方面,可迭代器永远不会耗尽,因为它们总是返回一个新的迭代器,然后用于迭代

您会注意到,可迭代对象代码的主要部分是在迭代器中,而可迭代对象本身只不过是一个允许我们创建和访问迭代器的额外层。

迭代可迭代对象

Python 有一个内置函数iter(),它调用__iter__()。当我们迭代一个可迭代对象时,Python 调用iter()返回一个迭代器,然后它开始使用__next__()迭代器迭代数据。

请注意,在上面的例子中,Cities 创建了一个可迭代对象,但它不是序列类型,这意味着我们无法通过索引获取城市。要解决这个问题,我们应该直接添加__get_item__到 Cities 类中。

class Cities:
    def __init__(self):
        self._cities = ['New York', 'Newark', 'Budapest', 'Newcastle']
        
    def __len__(self):
        return len(self._cities)
    
    def __getitem__(self, s): # now a sequence type
        return self._cities[s]
    
    def __iter__(self):
        return self.CityIterator(self)
    
    class CityIterator:
        def __init__(self, city_obj):
            self._city_obj = city_obj
            self._index = 0

        def __iter__(self):
            return self

        def __next__(self):
            if self._index >= len(self._city_obj):
                raise StopIteration
            else:
                item = self._city_obj._cities[self._index]
                self._index += 1
                return item

解决方案 10:

这是使用 的另一个视图collections.abc。此视图在第二次或以后可能会有用。

从中collections.abc我们可以看到以下层次结构:

builtins.object
    Iterable
        Iterator
            Generator

即 Generator 派生自 Iterator , Iterator 派生自 Iterable , Iterable 派生自基对象。

因此,

  • 每个迭代器都是可迭代的,但并非每个可迭代的都是迭代器。例如,[1, 2, 3]range(10)是可迭代的,但不是迭代器。 x = iter([1, 2, 3])是迭代器可迭代的。

  • Iterator和Generator之间也存在类似的关系。

  • 调用iter()迭代器或生成器将返回其自身。因此,如果it是迭代器,则为iter(it) is itTrue。

  • 在底层,列表推导式[2 * x for x in nums]或 for 循环就像在可迭代对象 () 上调用for x in nums:一样,然后使用该迭代器进行迭代。因此,以下所有内容在功能上都是等效的(例如):
    iter()`numsnumsnums=[1, 2, 3]`

    • for x in nums:

    • for x in iter(nums):

    • for x in iter(iter(nums))

    • for x in iter(iter(iter(iter(iter(nums))))):

解决方案 11:

iterable = [1, 2] 

iterator = iter(iterable)

print(iterator.__next__())   

print(iterator.__next__())   

所以,

  1. iterable是一个可以循环的对象。例如列表,字符串,元组等。

  2. iter在我们的对象上使用该函数iterable将返回一个迭代器对象。

  3. 现在这个迭代器对象有一个名为__next__(在 Python 3 中,或者仅next在 Python 2 中)的方法,您可以通过它访问可迭代的每个元素。

因此,上述代码的输出将是:

1

2

解决方案 12:

在处理可迭代对象和迭代器之前,决定可迭代对象和迭代器的主要因素是序列

序列:序列是数据的集合

Iterable:Iterable是支持方法的序列类型对象__iter__

Iter 方法:Iter 方法以序列作为输入并创建一个称为迭代器的对象

迭代器:迭代器是调用 next 方法并遍历序列的对象。调用 next 方法时,它返回当前遍历的对象。

例子:

x=[1,2,3,4]

x 是一个由数据集合组成的序列

y=iter(x)

调用iter(x)它时仅当 x 对象具有 iter 方法时才返回迭代器,否则会引发异常。如果它返回迭代器,则 y 分配如下:

y=[1,2,3,4]

由于 y 是迭代器,因此它支持next()方法

调用下一个方法时,它会逐个返回列表中的各个元素。

返回序列的最后一个元素后,如果我们再次调用下一个方法,则会引发 StopIteration 错误

例子:

>>> y.next()
1
>>> y.next()
2
>>> y.next()
3
>>> y.next()
4
>>> y.next()
StopIteration

解决方案 13:

  • 可迭代对象是具有iter () 方法的对象,该方法返回迭代器。它是可以循环的东西。示例:列表是可迭代的,因为我们可以循环列表,但它不是迭代器

  • 迭代是一个可以从中获取迭代器的对象。它是一个具有状态的对象,因此它可以记住迭代过程中的位置

要查看对象是否具有此方法iter (),我们可以使用下面的函数。

ls = ['hello','bye']
print(dir(ls))

输出

['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

如你所见,它有iter (),这意味着它是一个可迭代对象,但不包含next () 方法,这是迭代器对象的一个​​特性

每当你在 Python 中使用 for 循环、map 或列表推导时,都会自动调用next方法来获取迭代中的每个项目

解决方案 14:

其他人已经全面解释了什么是可迭代迭代器,所以我将尝试用生成器做同样的事情。

我认为,理解生成器的主要问题是“生成器”一词的令人困惑的使用,因为这个词有两种不同的含义:

  1. 作为创建(生成)迭代器的工具

    • 以返回迭代器的函数形式(即yield其主体中包含语句),

    • 以生成器表达式的形式

  2. 作为使用该工具的结果,即生成的迭代器。

(从这个意义上讲,生成器迭代器的一种特殊形式——“生成器”一词指出了这个迭代器是如何创建的。


生成器作为第一类工具:

In[2]: def my_generator():
  ...:     yield 100
  ...:     yield 200

In[3]: my_generator
Out[3]: <function __main__.my_generator()>
In[4]: type(my_generator)
Out[4]: function

使用此工具的结果生成器(即迭代器):

In[5]: my_iterator = my_generator()
In[6]: my_iterator
Out[6]: <generator object my_generator at 0x00000000053EAE48>
In[7]: type(my_iterator)
Out[7]: generator

生成器作为第二种类型的工具— — 与该工具生成的迭代器没有区别:

In[8]: my_gen_expression = (2 * i for i in (10, 20))
In[9]: my_gen_expression
Out[9]: <generator object <genexpr> at 0x000000000542C048>
In[10]: type(my_gen_expression)
Out[10]: generator

解决方案 15:

对我来说,Python 的词汇表对这些问题最有帮助,例如对于 iterable 它说:

能够一次返回其成员的对象。可迭代对象的示例包括所有序列类型(例如列表、字符串和元组)和一些非序列类型,例如字典、文件对象以及使用 iter ( ) 方法或实现序列语义的getitem () 方法定义的任何类的对象。

可迭代对象可用于 for 循环以及需要序列的许多其他位置(zip()、map() 等)。当将可迭代对象作为参数传递给内置函数 iter() 时,它会返回该对象的迭代器。此迭代器适用于对一组值进行一次传递。使用可迭代对象时,通常无需调用 iter() 或亲自处理迭代器对象。for 语句会自动为您执行此操作,创建一个临时未命名变量来在循环期间保存迭代器。另请参阅迭代器、序列和生成器。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用