返回顶部
首页 > 资讯 > 后端开发 > Python >Python生成器和协程怎么用
  • 521
分享到

Python生成器和协程怎么用

2024-04-02 19:04:59 521人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

本篇内容主要讲解“python生成器和协程怎么用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python生成器和协程怎么用”吧!认识生成器你将如何生成任意长度

本篇内容主要讲解“python生成器和协程怎么用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习Python生成器和协程怎么用”吧!

认识生成器

你将如何生成任意长度的斐波那契数列?显然,你需要跟踪一些数据,并且需要以某种方式对其进行操作以创建下一个元素。

你的第一直觉可能是创建一个可迭代的类,这不失是一个好方法。让我们开始,使用我们在前面几节中已经介绍过的内容:

class Fibonacci:

    def __init__(self, limit):
        self.n1 = 0
        self.n2 = 1
        self.n = 1
        self.i = 1
        self.limit = limit

    def __iter__(self):
        return self

    def __next__(self):
        if self.i > self.limit:
            raise StopIteration

        if self.i > 1:
            self.n = self.n1 + self.n2
            self.n1, self.n2 = self.n2, self.n

        self.i += 1
        return self.n


fib = Fibonacci(10)
for i in fib:
    print(i)

让我们把它变得更紧凑。

如果你到目前为止一直在关注该系列,那么这里可能不会有任何惊喜。然而,对于像序列这样简单的事情,这种方法可能会让人觉得有点过头了。

这种情况正是生成器的用途。

def fibonacci(limit):
    if limit >= 1:
        yield (n2 := 1)

    n1 = 0

    for _ in range(1, limit):
        yield (n := n1 + n2)
        n1, n2 = n2, n


for i in fibonacci(10):
    print(i)

生成器看起来肯定更紧凑——只有 9 行长,而类为 22 行——但它同样可读。

关键是yield关键字,它返回一个值而不退出函数。yield在功能上与我们类中的__next__()函数相同。生成器将运行到(并包括)它的yield语句,然后在它做任何事情之前等待另一个__next__()调用。一旦它得到那个调用,它将继续运行,直到它碰到另一个yield

注意:看起来很奇怪的:=是 Python 3.8 中的新“海象运算符”,它分配并返回一个值。如果你使用的是 Python 3.7 或更早版本,则可以将这些语句分成两行(单独去赋值和写yield语句)。

你还会注意到缺少raise StopIteration声明。生成器不需要它们;事实上,自PEP 479以来,他们甚至不允许他们这样做。当生成器函数自然终止或使用return语句终止时,StopIteration会在幕后自动触发。

尝试生成器

修订日期:2019 年 11 月 29 日

曾经规定了yield不能出现在代码中try子句中的try-finally中。PEP 255定义了生成器语法,解释了原因:

难点在于不能保证生成器会被恢复,因此不能保证 finally 块会被执行;这就违背finally的目的了。

这在 PEP 342 PEP 342中进行了更改,并在 Python 2.5 中完成。

那么,为什么要讨论这样一个古老的变化呢?简单:直到今天,我的印象是yield无法出现在try-finally中. 一些关于该主题的文章错误地引用了旧规则。

把生成器作为对象

你可能还记得 Python 将函数视为对象,生成器也不例外!在我们之前的示例的基础上,我们可以保存生成器的特定实例。

例如,如果我只想打印斐波那契数列的第 10-20 个值怎么办?

首先,我将生成器保存在一个变量中,以便我可以重用它。限制对我来说并不重要,所以我会使用大的限制。使用我的循环范围来更容易显示内容,因为这会使限制逻辑接近打印语句。

fib = fibonacci(100)

接下来,我将使用循环跳过前 10 个元素。

for _ in range(10):
    next(fib)

next()函数实际上是循环始终用于推进迭代的函数。在生成器的情况下,这将返回由yield返回的任何值。在这种情况下,由于我们还不关心这些值,我们只是将它们扔掉(对它们什么都不做)。

顺便说一句,我也可以这样调用fib.__next__()——但我更喜欢采取的更简洁方法next(fib)。它通常取决于个人偏好。两者同样有效。

我现在准备好从生成器访问一些值,但不是全部。因此,我仍将使用range(),并直接使用next()从生成器中检索值。

for n in range(10, 21):
    print(f"{n}th value: {next(fib)}")

这可以很好地打印出所需的值:

10th value: 89
11th value: 144
12th value: 233
13th value: 377
14th value: 610
15th value: 987
16th value: 1597
17th value: 2584
18th value: 4181
19th value: 6765
20th value: 10946

还记得我们之前将限制设置为 100,现在已经完成了我们的生成器,但我们不应该直接离开并让它等待另一个next()调用!我们程序的其余部分处于空闲状态就会浪费资源(尽管很少)。

相反,我们可以手动告诉我们的生成器我们已经完成了它。

fib.close()

这将手动关闭生成器,就像它已经到达一个return语句一样。它现在可以由垃圾收集器清理。

认识协程

生成器允许我们快速定义一个在调用之间存储其状态的可迭代对象。但是,如果我们想要相反的结果:传递信息让函数耐心等待它得到它呢?Python为此提供了协程。

对于已经有点熟悉协程的人,你应该明白我所指的是简单的协程(尽管我只是为了读者的理智而自始至终都在说“协程”。)如果你已经看过任何使用并发的 Python 代码,你可能已经遇到过它的小弟,原生协程(也称为“异步协程”)。

现在,了解简单协程原生协程都被官方认为是“协程”,它们有很多共同的原则;原生协程建立在简单协程引入的概念之上。我们会在后续的文章中讨论async

同样,现在假设当我说“协程”时,我指的是一个简单的协程。

想象一下,你想找到一堆字符串之间的所有共同字母,比如一本书籍中那些有趣的人物名字。你不知道有多少字符串,它们会在运行时输入,不一定是一次全部输入。

显然,这种方法必须:

  • 可重复使用。

  • 有状态(到目前为止共有的字母。)

  • 本质上是迭代的,因为我们不知道我们会得到多少个字符串。

普通的函数并不适合这种情况,因为我们必须一次将所有数据作为列表或元组传递,而且它们本身不存储状态。同时,生成器不能处理输入,除非是第一次调用。

我们可以尝试新建一个类,尽管有很多模板。不管怎样,让我们从这儿开始,只是为了更好地掌握我们正在处理的内容。

在我的第一个版本中,我将对传递给类的列表进行修改,因此我可以随时查看结果。如果我坚持使用类实现,我可能不会那样做,但它是实现我们目的最小的可行类了。此外,它在功能上与我们稍后将要编写的协程相同,这用来比较实现方法很有用。

class CommonLetterCounter:

    def __init__(self, results):
        self.letters = {}
        self.counted = []
        self.results = results
        self.i = 0

    def add_word(self, Word):
        word = word.lower()
        for c in word:
            if c.isalpha():
                if c not in self.letters:
                    self.letters[c] = 0
                self.letters[c] += 1

        self.counted = sorted(self.letters.items(), key=lambda kv: kv[1])
        self.counted = self.counted[::-1]

        self.results.clear()
        for item in self.counted:
            self.results.append(item)


names = ['Skimpole', 'Sloppy', 'Wopsle', 'Toodle', 'Squeers',
         'Honeythunder', 'Tulkinghorn', 'Bumble', 'Wegg',
         'Swiveller', 'Sweedlepipe', 'Jellyby', 'Smike', 'Heep',
         'Sowerberry', 'Pumblechook', 'Podsnap', 'Tox', 'Wackles',
         'Scrooge', 'Snodgrass', 'Winkle', 'Pickwick']

results = []
counter = CommonLetterCounter(results)

for name in names:
    counter.add_word(name)

for letter, count in results:
    print(f'{letter} apppears {count} times.')

根据我的输出,这本数据特别喜欢带有 e、o、s、l 和 p 的名字。谁知道?

我们可以使用协程完成相同的结果。

def count_common_letters(results):
    letters = {}

    while True:
        word = yield
        word = word.lower()
        for c in word:
            if c.isalpha():
                if c not in letters:
                    letters[c] = 0
                letters[c] += 1

        counted = sorted(letters.items(), key=lambda kv: kv[1])
        counted = counted[::-1]

        results.clear()
        for item in counted:
            results.append(item)


names = ['Skimpole', 'Sloppy', 'Wopsle', 'Toodle', 'Squeers',
         'Honeythunder', 'Tulkinghorn', 'Bumble', 'Wegg',
         'Swiveller', 'Sweedlepipe', 'Jellyby', 'Smike', 'Heep',
         'Sowerberry', 'Pumblechook', 'Podsnap', 'Tox', 'Wackles',
         'Scrooge', 'Snodgrass', 'Winkle', 'Pickwick']

results = []
counter = count_common_letters(results)
counter.send(None)  # prime the coroutine

for name in names:
    counter.send(name)  # send data to the coroutine

counter.close()  # manually end the coroutine

for letter, count in results:
    print(f'{letter} apppears {count} times.')

让我们仔细看看这里发生了什么。乍一看,协程与函数并没有什么不同,但与生成器一样,yield关键字的使用就大不相同了。

在协程中,yield它代表“等到你的输入,然后在这里使用它”。

你会注意到两种方法之间的大多数处理逻辑是相同的。我们只是取消了类模板。我们存储协程的实例就像存储对象一样,只是为了确保每次向它发送更多数据时都使用相同的实例。

类和协程之间的主要区别在于用法。我们使用协程的send()函数向协程发送数据:

for name in names:
    counter.send(name)

在我们这样做之前,我们必须首先调用(上面使用counter.send(None)的)或counter.__next__()。协程不能立即接收值;它必须首先运行它的所有代码,直到它的第一个yield.

与生成器一样,协程在到达其正常执行流程的末尾或到达return语句时完成。由于在我们的示例中这些情况都没有发生的机会,所以我选择手动关闭协程:

counter.close()

简而言之,使用协程:

  • 将其实例保存为变量,例如counter

  • counter.send(None),counter.__next__()next(counter)输入协程,

  • counter.send()发送数据,

  • 如有必要,用counter.close()关闭它。

尝试协程

还记得关于生成器的规则,不能将 yield放在语句的try子句中try-finally吗?但是这里不适用!因为yield在协程中的行为非常不同(处理传入数据,而不是传出数据),以这种方式使用它是完全可以接受的。

throw()

生成器和协程也有一个throw()函数,用于在它们暂停的地方引发异常。你会从《错误和异常》一文中了解到,异常可以用作代码执行流程的正常部分。

例如,假设你想将数据发送到远程服务器。你现在已经有一个连接对象,并且已使用协程通过该连接发送数据。

在你的代码中,当检测到你已经失去了网络连接,但是由于你与服务器的通信方式,协程发送的所有数据都会毫无保留的被丢弃。

考虑一下下面这个我已经删除的示例代码。(假设实际的连接逻辑本身不适合处理回退或报告连接错误。)

class Connection:
    """ Stub object simulating connection to a server """

    def __init__(self, addr):
        self.addr = addr

    def transmit(self, data):
        print(f"X: {data[0]}, Y: {data[1]} sent to {self.addr}")


def send_to_server(conn):
    """ Coroutine demonstrating sending data """
    while True:
        raw_data = yield
        raw_data = raw_data.split(' ')
        coords = (float(raw_data[0]), float(raw_data[1]))
        conn.transmit(coords)


conn = Connection("example.com")

sender = send_to_server(conn)
sender.send(None)

for i in range(1, 6):
    sender.send(f"{100/i} {200/i}")

# Simulate connection error...
conn.addr = None
# ...but assume the sender knows nothing about it.

for i in range(1, 6):
    sender.send(f"{100/i} {200/i}")

运行该示例,我们看到前五个send()调用转到example.com,但后五个调用转到None。这显然是不行的——我们想抛出问题,然后开始将数据写到文件中,这样它就不会永远丢失。

这就是throw()的作用。一旦我们知道我们已经失去了连接,我们就可以提醒协程这个事实,让它做出适当的响应。

我们首先在协程中添加一个try-except

def send_to_server(conn):
    while True:
        try:
            raw_data = yield
            raw_data = raw_data.split(' ')
            coords = (float(raw_data[0]), float(raw_data[1]))
            conn.transmit(coords)
        except ConnectionError:
            print("Oops! Connection lost. Creating fallback.")
            # Create a fallback connection!
            conn = Connection("local file")

我们的使用示例只需要进行一处更改:一旦我们知道我们失去了连接,我们就使用sender.throw(ConnectionError)抛出异常:

conn = Connection("example.com")

sender = send_to_server(conn)
sender.send(None)

for i in range(1, 6):
    sender.send(f"{100/i} {200/i}")

# Simulate connection error...
conn.addr = None
# ...but assume the sender knows nothing about it.

sender.throw(ConnectionError) # ALERT THE SENDER!

for i in range(1, 6):
    sender.send(f"{100/i} {200/i}")

这样的话!现在我们会在协程收到警报后立即收到有关连接问题的消息,并将相关错误内容写入到本地文件,也就是所谓的日志文件。

yield from

使用生成器或协程时,你不仅限于yield,你还可以使用yield from.

例如,假设我想重写我的斐波那契数列以使其没有限制,并且我只想编码前五个值。

def fibonacci():
    starter = [1, 1, 2, 3, 5]
    yield from starter

    n1 = starter[-2]
    n2 = starter[-1]

    while True:
        yield (n := n1 + n2)
        n1, n2 = n2, n

在这种情况下,yield from暂时移交给另一个可迭代对象,无论它是容器、对象还是另一个生成器。一旦该可迭代对象结束,生成器就会启动并像往常一样继续运行。

仅仅使用这个生成器,你不会知道它在部分时间内使用了另一个迭代器。它只是像往常一样工作。

fib = fibonacci()

for n in range(1,11):
    print(f"{n}th value: {next(fib)}")

fib.close()

协程也可以以类似的方式进行切换。例如,在我们的 连接示例中,如果我们创建第二个协程来处理将数据写入文件会怎样?如果我们遇到连接错误,我们可以切换到在幕后使用它。

class Connection:
    """ Stub object simulating connection to a server """

    def __init__(self, addr):
        self.addr = addr

    def transmit(self, data):
        print(f"X: {data[0]}, Y: {data[1]} sent to {self.addr}")


def save_to_file():
    while True:
        raw_data = yield
        raw_data = raw_data.split(' ')
        coords = (float(raw_data[0]), float(raw_data[1]))
        print(f"X: {coords[0]}, Y: {coords[1]} sent to local file")


def send_to_server(conn):
    while True:
        if conn is None:
            yield from save_to_file()
        else:
            try:
                raw_data = yield
                raw_data = raw_data.split(' ')
                coords = (float(raw_data[0]), float(raw_data[1]))
                conn.transmit(coords)
            except ConnectionError:
                print("Oops! Connection lost. Using fallback.")
                conn = None


conn = Connection("example.com")

sender = send_to_server(conn)
sender.send(None)

for i in range(1, 6):
    sender.send(f"{100/i} {200/i}")

# Simulate connection error...
conn.addr = None
# ...but assume the sender knows nothing about it.

sender.throw(ConnectionError) # ALERT THE SENDER!

for i in range(1, 6):
    sender.send(f"{100/i} {200/i}")

生成器和协程结合使用

你可能想知道:“我可以像从生成器中那样直接从协程中组合两个返回数据吗?”

我在写这篇文章时也对此感到好奇,显然你可以。这一切都与识别函数何时被视为生成器而不是协程有关。

关键很简单:实际上__next__()send(None)在协程中同样有效。

def count_common_letters():
    letters = {}

    word = yield
    while word is not None:
        word = word.lower()
        for c in word:
            if c.isalpha():
                if c not in letters:
                    letters[c] = 0
                letters[c] += 1
        word = yield

    counted = sorted(letters.items(), key=lambda kv: kv[1])
    counted = counted[::-1]

    for item in counted:
        yield item


names = ['Skimpole', 'Sloppy', 'Wopsle', 'Toodle', 'Squeers',
         'Honeythunder', 'Tulkinghorn', 'Bumble', 'Wegg',
         'Swiveller', 'Sweedlepipe', 'Jellyby', 'Smike', 'Heep',
         'Sowerberry', 'Pumblechook', 'Podsnap', 'Tox', 'Wackles',
         'Scrooge', 'Snodgrass', 'Winkle', 'Pickwick']

counter = count_common_letters()
counter.send(None)

for name in names:
    counter.send(name)

for letter, count in counter:
    print(f'{letter} apppears {count} times.')

我只需要观察协程何时开始接收None(当然是在初始启动之后)。由于我在word中存储了yield的结果,因此我可以用word变成None时作为跳出循环的判断条件。

当我们将协程转化为生成器时,它需要在yield开始输出数据之前处理单个send(None) 。在调用我们的协程时,我们在切换使用之前从未明确地send(None);Python 在后台执行此操作。

另外,请记住协程/生成器仍然是一个函数。它只是在每次遇到yield时暂停。在我的示例中,我不能突然回去使用counter作为协程,因为没有执行流程可以让我回到word = yield。其实完全可以实现它,以便你可以来回切换,但如果它以牺牲可读性或变得过于复杂为代价,则可能不明智。

到此,相信大家对“Python生成器和协程怎么用”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

--结束END--

本文标题: Python生成器和协程怎么用

本文链接: https://lsjlt.com/news/99014.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Python生成器和协程怎么用
    本篇内容主要讲解“Python生成器和协程怎么用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python生成器和协程怎么用”吧!认识生成器你将如何生成任意长度...
    99+
    2024-04-02
  • Python生成器和基于生成器的协程
    小编给大家分享一下Python生成器和基于生成器的协程,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!一、什么是生成器Generator生成器就是可以生成值的函数2.当一个函数里有了 yield关键字就成了生成器3.生成器可...
    99+
    2023-06-15
  • 详解Python生成器和基于生成器的协程
    目录一、什么是生成器二、基于生成器的协程三、协程的注意点四、协程装饰器五、python3原生协程一、什么是生成器 Generator 1.生成器就是可以生成值的函数 2.当一个函数里...
    99+
    2024-04-02
  • 怎么在python中利用生成器实现协程
    这篇文章给大家介绍怎么在python中利用生成器实现协程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。python是什么意思Python是一种跨平台的、具有解释性、编译性、互动性和面向对象的脚本语言,其最初的设计是用于...
    99+
    2023-06-14
  • python生成器怎么定义和使用
    本文小编为大家详细介绍“python生成器怎么定义和使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“python生成器怎么定义和使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1. 生成器概念生成器(英文:...
    99+
    2023-07-02
  • Python3.10 Generator生成器Coroutine原生协程详解
    目录引言协程底层实现业务场景结语引言 普遍意义上讲,生成器是一种特殊的迭代器,它可以在执行过程中暂停并在恢复执行时保留它的状态。而协程,则可以让一个函数在执行过程中暂停并在恢复执行时...
    99+
    2022-12-28
    Python生成器原生协程 Python Generator Coroutine
  • python生成器和yield关键字怎么用
    这篇文章主要介绍了python生成器和yield关键字怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。下列代码用于先体验普通列表推导式和生成器的差别:# def...
    99+
    2023-06-26
  • python中的迭代器和生成器怎么用
    这篇“python中的迭代器和生成器怎么用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“python中的迭代器和生成器怎么用...
    99+
    2023-06-29
  • Python生成器怎么使用
    本篇内容介绍了“Python生成器怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!生成器仅仅拥有生成某种东西的能力,如果不用__nex...
    99+
    2023-06-17
  • 生成器进化到协程 Part 1
    前言 这篇文章大部分来自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。这个PPT很长而且非常烧脑,建议在阅读前应了解 Python 的生成器与携程相关...
    99+
    2023-01-31
    生成器 到协程 Part
  • Python生成器yield怎么使用
    这篇“Python生成器yield怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python生成器yield怎么使用...
    99+
    2023-07-02
  • python迭代器和生成器怎么实现
    本篇内容介绍了“python迭代器和生成器怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!迭代器和生成器是python中非常强大的功能...
    99+
    2023-06-27
  • python列表生成器怎么使用
    本篇内容介绍了“python列表生成器怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!列表生成式基础语法[exp for iter_v...
    99+
    2023-07-02
  • Python生成器与迭代器怎么用
    这篇文章给大家分享的是有关Python生成器与迭代器怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1、生成器现在可以通过生成器来直接创建一个列表,但是由于内存的限制,列表的容量肯定是有限的,如果我们需要一个...
    99+
    2023-06-25
  • python迭代器和生成器
    1.经典迭代器 import re RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = te...
    99+
    2023-01-30
    生成器 迭代 python
  • 一文搞懂​​​​​​​python可迭代对象,迭代器,生成器,协程
    目录设计模式:迭代python:可迭代对象和迭代器为什么要有生成器?python的生成器实现协程设计模式:迭代 迭代是一种设计模式,解决有序便利序列的问题。通用的可迭代对象需要支持d...
    99+
    2024-04-02
  • ​​​​​​​python可迭代对象,迭代器,生成器,协程实例分析
    这篇文章主要介绍了python可迭代对象,迭代器,生成器,协程实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇python可迭代对象,迭代器,生成器,协程实例分析文章都会有所收获,下面我们一起来看看吧。设...
    99+
    2023-06-30
  • python怎么使用send启动生成器
    小编给大家分享一下python怎么使用send启动生成器,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1、说明send方法启动生成器的时候可以传参数。如果第一次启...
    99+
    2023-06-15
  • Python异步之生成器怎么使用
    这篇文章主要介绍“Python异步之生成器怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python异步之生成器怎么使用”文章能帮助大家解决问题。正文生成器是 Python 的基本组成部分。...
    99+
    2023-07-05
  • python教程之生成器和匿名函数
    目录生成器01 什么是生成器?02 通俗的讲解03 生成器到底有什么用?04 生成器的常见用途?匿名函数01 什么是匿名函数?02 通俗的讲解总结生成器 01 什么是生成器? 记住两...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作