流畅的 Python 第二版(GPT 重译)(九)(2)

简介: 流畅的 Python 第二版(GPT 重译)(九)

流畅的 Python 第二版(GPT 重译)(九)(1)https://developer.aliyun.com/article/1484704

提示

我发现在谈论从生成器获得的值时严谨是有帮助的。说生成器“返回”值是令人困惑的。函数返回值。调用生成器函数返回一个生成器。生成器产生值。生成器不以通常的方式“返回”值:生成器函数体中的return语句会导致生成器对象引发StopIteration。如果在生成器中return x,调用者可以从StopIteration异常中检索到x的值,但通常使用yield from语法会自动完成,我们将在“从协程返回值”中看到。

示例 17-7 使for循环和函数体之间的交互更加明确。

示例 17-7. 一个在运行时打印消息的生成器函数
>>> def gen_AB():
...     print('start')
...     yield 'A'          # ①
...     print('continue')
...     yield 'B'          # ②
...     print('end.')      # ③
...
>>> for c in gen_AB():     # ④
...     print('-->', c)    # ⑤
...
start # ⑥
--> A # ⑦
continue # ⑧
--> B # ⑨
end. # ⑩
>>>       ⑪

for循环中对④的第一次隐式调用next()将打印'start'并在第一个yield处停止,产生值'A'

for循环中第二次隐式调用next()将打印'continue'并在第二个yield处停止,产生值'B'

第三次调用next()将打印'end.'并穿过函数体的末尾,导致生成器对象引发StopIteration

为了迭代,for机制执行等效于g = iter(gen_AB())以获取一个生成器对象,然后在每次迭代时执行next(g)

循环打印-->next(g)返回的值。这个输出只会在生成器函数内部的print调用输出之后出现。

文本start来自生成器体中的print('start')

生成器体中的yield 'A'产生值A,被for循环消耗,赋给变量c,导致输出--> A

迭代继续,第二次调用next(g),将生成器体从yield 'A'推进到yield 'B'。第二个print在生成器体中输出continue

yield 'B'产生值B,被for循环消耗,赋给循环变量c,因此循环打印--> B

迭代继续,第三次调用next(it),推进到函数体的末尾。由于生成器体中的第三个print,输出中出现了end.

当生成器函数运行到末尾时,生成器对象会引发StopIteration异常。for循环机制捕获该异常,循环干净地终止。

现在希望清楚了示例 17-5 中的Sentence.__iter__是如何工作的:__iter__是一个生成器函数,当调用时,会构建一个实现Iterator接口的生成器对象,因此不再需要SentenceIterator类。

第二个Sentence版本比第一个更简洁,但不像它可以那样懒惰。如今,懒惰被认为是一个好特性,至少在编程语言和 API 中是这样。懒惰的实现将产生值推迟到最后可能的时刻。这样可以节省内存,也可能避免浪费 CPU 周期。

我们将构建懒惰的Sentence类。

懒惰的句子

Sentence的最终变体是懒惰的,利用了re模块中的懒惰函数。

句子第四次尝试:懒惰生成器

Iterator接口被设计为懒惰的:next(my_iterator)每次产生一个项目。懒惰的相反是急切:懒惰评估和急切评估是编程语言理论中的技术术语。

到目前为止,我们的Sentence实现并不懒惰,因为__init__急切地构建了文本中所有单词的列表,并将其绑定到self.words属性。这需要处理整个文本,而且列表可能使用的内存和文本本身一样多(可能更多;这取决于文本中有多少非单词字符)。如果用户只迭代前几个单词,大部分工作将是徒劳的。如果你想知道,“在 Python 中有没有一种懒惰的方法?”答案通常是“是的”。

re.finditer函数是re.findall的惰性版本。re.finditer返回一个生成器,按需产生re.MatchObject实例,而不是一个列表。如果有很多匹配,re.finditer可以节省大量内存。使用它,我们的第三个Sentence版本现在是惰性的:只有在需要时才从文本中读取下一个单词。代码在示例 17-8 中。

示例 17-8. sentence_gen2.py: 使用调用re.finditer生成器函数实现的Sentence
import re
import reprlib
RE_WORD = re.compile(r'\w+')
class Sentence:
    def __init__(self, text):
        self.text = text  # ①
    def __repr__(self):
        return f'Sentence({reprlib.repr(self.text)})'
    def __iter__(self):
        for match in RE_WORD.finditer(self.text):  # ②
            yield match.group()  # ③

不需要有一个words列表。

finditerself.text上的RE_WORD匹配中构建一个迭代器,产生MatchObject实例。

match.group()MatchObject实例中提取匹配的文本。

生成器是一个很好的快捷方式,但可以用生成器表达式进一步简化代码。

第五种句子:惰性生成器表达式

我们可以用生成器表达式替换前一个Sentence类中的简单生成器函数(示例 17-8)。就像列表推导式构建列表一样,生成器表达式构建生成器对象。示例 17-9 对比了它们的行为。

示例 17-9. gen_AB生成器函数被列表推导式使用,然后被生成器表达式使用
>>> def gen_AB():  # ①
...     print('start')
...     yield 'A'
...     print('continue')
...     yield 'B'
...     print('end.')
...
>>> res1 = [x*3 for x in gen_AB()]  # ②
start continue end. >>> for i in res1:  # ③
...     print('-->', i)
...
--> AAA --> BBB >>> res2 = (x*3 for x in gen_AB())  # ④
>>> res2
<generator object <genexpr> at 0x10063c240> >>> for i in res2:  # ⑤
...     print('-->', i)
...
start # ⑥
--> AAA continue --> BBB end.

这是与示例 17-7 中相同的gen_AB函数。

列表推导式急切地迭代由gen_AB()返回的生成器对象产生的项目:'A''B'。注意下面行中的输出:startcontinueend.

这个for循环迭代由列表推导式构建的res1列表。

生成器表达式返回res2,一个生成器对象。这里生成器没有被消耗。

只有当for循环迭代res2时,这个生成器才从gen_AB获取项目。for循环的每次迭代隐式调用next(res2),进而调用gen_AB()返回的生成器对象上的next(),将其推进到下一个yield

注意gen_AB()的输出如何与for循环中的print输出交错。

我们可以使用生成器表达式进一步减少Sentence类中的代码量。参见示例 17-10。

示例 17-10. sentence_genexp.py: 使用生成器表达式实现的Sentence
import re
import reprlib
RE_WORD = re.compile(r'\w+')
class Sentence:
    def __init__(self, text):
        self.text = text
    def __repr__(self):
        return f'Sentence({reprlib.repr(self.text)})'
    def __iter__(self):
        return (match.group() for match in RE_WORD.finditer(self.text))

与示例 17-8 唯一的区别是__iter__方法,在这里不是一个生成器函数(没有yield),而是使用生成器表达式构建一个生成器,然后返回它。最终结果是一样的:__iter__的调用者得到一个生成器对象。

生成器表达式是一种语法糖:它们总是可以被生成器函数替代,但有时更加方便。下一节将介绍生成器表达式的用法。

何时使用生成器表达式

在实现示例 12-16 中的Vector类时,我使用了几个生成器表达式。这些方法中的每一个都有一个生成器表达式:__eq____hash____abs__angleanglesformat__add____mul__。在所有这些方法中,列表推导也可以工作,但会使用更多内存来存储中间列表值。

在示例 17-10 中,我们看到生成器表达式是一种创建生成器的语法快捷方式,而无需定义和调用函数。另一方面,生成器函数更加灵活:我们可以使用多个语句编写复杂逻辑,甚至可以将它们用作协程,正如我们将在“经典协程”中看到的那样。

对于更简单的情况,一目了然的生成器表达式更易于阅读,就像Vector示例所示。

我在选择要使用的语法时的经验法则很简单:如果生成器表达式跨越多行,我更倾向于出于可读性考虑编写生成器函数。

语法提示

当将生成器表达式作为函数或构造函数的单个参数传递时,您无需为函数调用编写一组括号,然后再为生成器表达式加上另一组括号。只需一对即可,就像在示例 12-16 中Vector调用__mul__方法时一样,如下所示:

def __mul__(self, scalar):
    if isinstance(scalar, numbers.Real):
        return Vector(n * scalar for n in self)
    else:
        return NotImplemented

但是,如果在生成器表达式之后还有更多的函数参数,您需要将其括在括号中,以避免SyntaxError

我们看到的Sentence示例演示了生成器扮演经典迭代器模式的角色:从集合中检索项。但是,我们也可以使用生成器产生独立于数据源的值。下一节将展示一个示例。

但首先,让我们简要讨论迭代器生成器之间重叠概念。

算术级数生成器

经典的迭代器模式完全关乎遍历:导航某些数据结构。但是,基于一种方法来获取系列中的下一个项的标准接口在项是实时生成的情况下也很有用,而不是从集合中检索。例如,range内置函数生成整数的有界算术级数(AP)。如果您需要生成任何类型的数字的算术级数,而不仅仅是整数,该怎么办?

示例 17-11 展示了我们马上将看到的ArithmeticProgression类的一些控制台测试。示例 17-11 中构造函数的签名是ArithmeticProgression(begin, step[, end])range内置函数的完整签名是range(start, stop[, step])。我选择实现不同的签名,因为在算术级数中step是必需的,但end是可选的。我还将参数名称从start/stop更改为begin/end,以明确表明我选择了不同的签名。在示例 17-11 的每个测试中,我对结果调用list()以检查生成的值。

示例 17-11。ArithmeticProgression类演示
>>> ap = ArithmeticProgression(0, 1, 3)
    >>> list(ap)
    [0, 1, 2]
    >>> ap = ArithmeticProgression(1, .5, 3)
    >>> list(ap)
    [1.0, 1.5, 2.0, 2.5]
    >>> ap = ArithmeticProgression(0, 1/3, 1)
    >>> list(ap)
    [0.0, 0.3333333333333333, 0.6666666666666666]
    >>> from fractions import Fraction
    >>> ap = ArithmeticProgression(0, Fraction(1, 3), 1)
    >>> list(ap)
    [Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)]
    >>> from decimal import Decimal
    >>> ap = ArithmeticProgression(0, Decimal('.1'), .3)
    >>> list(ap)
    [Decimal('0'), Decimal('0.1'), Decimal('0.2')]

请注意,生成的算术级数中的数字类型遵循 Python 算术的数字强制转换规则,即begin + step的类型。在示例 17-11 中,您会看到intfloatFractionDecimal数字的列表。示例 17-12 列出了ArithmeticProgression类的实现。

示例 17-12。ArithmeticProgression
class ArithmeticProgression:
    def __init__(self, begin, step, end=None):       # ①
        self.begin = begin
        self.step = step
        self.end = end  # None -> "infinite" series
    def __iter__(self):
        result_type = type(self.begin + self.step)   # ②
        result = result_type(self.begin)             # ③
        forever = self.end is None                   # ④
        index = 0
        while forever or result < self.end:          # ⑤
            yield result                             # ⑥
            index += 1
            result = self.begin + self.step * index  # ⑦

__init__需要两个参数:beginstep;如果endNone,则序列将是无界的。

获取self.beginself.step的添加类型。例如,如果一个是int,另一个是floatresult_type将是float

这一行创建了一个result,其数值与self.begin相同,但被强制转换为后续加法的类型。⁷

为了可读性,如果self.end属性为Noneforever标志将为True,导致一个无界系列。

这个循环运行forever,或直到结果匹配或超过self.end。当这个循环退出时,函数也会退出。

当前的result被生成。

下一个潜在的结果被计算。它可能永远不会被产生,因为while循环可能终止。

在示例 17-12 的最后一行,我选择忽略每次循环中将self.step添加到前一个result中,而是选择忽略前一个result,并通过将self.begin添加到self.step乘以index来添加每个新的result。这避免了连续添加后浮点错误的累积效应。这些简单的实验使差异变得明显:

>>> 100 * 1.1
110.00000000000001
>>> sum(1.1 for _ in range(100))
109.99999999999982
>>> 1000 * 1.1
1100.0
>>> sum(1.1 for _ in range(1000))
1100.0000000000086

来自示例 17-12 的ArithmeticProgression类按预期工作,并且是使用生成器函数实现__iter__特殊方法的另一个示例。然而,如果一个类的整个目的是通过实现__iter__来构建一个生成器,我们可以用生成器函数替换类。毕竟,生成器函数本质上是一个生成器工厂。

示例 17-13 展示了一个名为aritprog_gen的生成器函数,它与ArithmeticProgression执行相同的工作,但代码更少。如果只调用aritprog_gen而不是ArithmeticProgression,则示例 17-11 中的所有测试都会通过。⁸

示例 17-13. aritprog_gen生成器函数
def aritprog_gen(begin, step, end=None):
    result = type(begin + step)(begin)
    forever = end is None
    index = 0
    while forever or result < end:
        yield result
        index += 1
        result = begin + step * index

示例 17-13 非常优雅,但请记住:标准库中有大量现成的生成器可供使用,下一节将展示使用itertools模块的更短实现。

使用itertools的算术进度

Python 3.10 中的itertools模块有 20 个生成器函数,可以以各种有趣的方式组合。

例如,itertools.count 函数返回一个生成器,产生数字。没有参数时,它产生以0开头的一系列整数。但是你可以提供可选的startstep值来实现类似于我们的aritprog_gen函数的结果:

>>> import itertools
>>> gen = itertools.count(1, .5)
>>> next(gen)
1
>>> next(gen)
1.5
>>> next(gen)
2.0
>>> next(gen)
2.5
警告

itertools.count永远不会停止,因此如果调用list(count()),Python 将尝试构建一个填满所有已制造的内存芯片的list。实际上,在调用失败之前,您的机器会变得非常不高兴。

另一方面,有itertools.takewhile函数:它返回一个消耗另一个生成器并在给定谓词评估为False时停止的生成器。因此,我们可以将两者结合起来写成这样:

>>> gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))
>>> list(gen)
[1, 1.5, 2.0, 2.5]

利用takehwhilecount,示例 17-14 更加简洁。

示例 17-14. aritprog_v3.py:这与之前的aritprog_gen函数相同
import itertools
def aritprog_gen(begin, step, end=None):
    first = type(begin + step)(begin)
    ap_gen = itertools.count(first, step)
    if end is None:
        return ap_gen
    return itertools.takewhile(lambda n: n < end, ap_gen)

请注意,示例 17-14 中的aritprog_gen不是一个生成器函数:它的主体中没有yield。但它返回一个生成器,就像生成器函数一样。

但是,请记住,itertools.count会重复添加step,因此它生成的浮点数序列不像示例 17-13 那样精确。

示例 17-14 的要点是:在实现生成器时,要了解标准库中提供了什么,否则很可能会重复造轮子。这就是为什么下一节涵盖了几个可直接使用的生成器函数。

标准库中的生成器函数

标准库提供了许多生成器,从提供逐行迭代的纯文本文件对象,到令人惊叹的os.walk函数,该函数在遍历目录树时产生文件名,使递归文件系统搜索就像一个for循环一样简单。

os.walk生成器函数令人印象深刻,但在本节中,我想专注于以任意可迭代对象作为参数并返回生成器的通用函数,这些生成器产生选定的、计算的或重新排列的项目。在下面的表格中,我总结了两打这样的函数,来自内置的itertoolsfunctools模块。为方便起见,我根据高级功能对它们进行了分组,而不管它们在哪里定义。

第一组包含过滤生成器函数:它们产生输入可迭代对象生成的项目子集,而不改变项目本身。像takewhile一样,表 17-1 中列出的大多数函数都接受一个predicate,这是一个一参数布尔函数,将应用于输入中的每个项目,以确定是否将项目包含在输出中。

表 17-1. 过滤生成器函数

模块 函数 描述
itertools compress(it, selector_it) 并行消耗两个可迭代对象;每当selector_it中对应的项目为真时,从it中产生项目
itertools dropwhile(predicate, it) 消耗it,跳过predicate计算为真时的项目,然后产生所有剩余项目(不再进行进一步检查)
(内置) filter(predicate, it) iterable的每个项目应用predicate,如果predicate(item)为真,则产生该项目;如果predicateNone,则只产生真值项目
itertools filterfalse(predicate, it) filter相同,但predicate逻辑取反:每当predicate计算为假时产生项目
itertools islice(it, stop) or islice(it, start, stop, step=1) it的切片中产生项目,类似于s[:stop]s[start:stop:step],除了it可以是任何可迭代对象,且操作是惰性的
itertools takewhile(predicate, it) predicate计算为真时产生项目,然后停止,不再进行进一步检查

示例 17-15 中的控制台列表显示了表 17-1 中所有函数的使用。

示例 17-15. 过滤生成器函数示例
>>> def vowel(c):
...     return c.lower() in 'aeiou'
...
>>> list(filter(vowel, 'Aardvark'))
['A', 'a', 'a']
>>> import itertools
>>> list(itertools.filterfalse(vowel, 'Aardvark'))
['r', 'd', 'v', 'r', 'k']
>>> list(itertools.dropwhile(vowel, 'Aardvark'))
['r', 'd', 'v', 'a', 'r', 'k']
>>> list(itertools.takewhile(vowel, 'Aardvark'))
['A', 'a']
>>> list(itertools.compress('Aardvark', (1, 0, 1, 1, 0, 1)))
['A', 'r', 'd', 'a']
>>> list(itertools.islice('Aardvark', 4))
['A', 'a', 'r', 'd']
>>> list(itertools.islice('Aardvark', 4, 7))
['v', 'a', 'r']
>>> list(itertools.islice('Aardvark', 1, 7, 2))
['a', 'd', 'a']

下一组包含映射生成器:它们产生从输入可迭代对象的每个单独项目计算得到的项目,或者在mapstarmap的情况下,产生自输入可迭代对象的项目。表 17-2 中的生成器每个输入可迭代对象产生一个结果。如果输入来自多个可迭代对象,则一旦第一个输入可迭代对象耗尽,输出就会停止。

表 17-2. 映射生成器函数

模块 函数 描述
itertools accumulate(it, [func]) 产生累积和;如果提供了func,则产生将其应用于第一对项目的结果,然后应用于第一个结果和下一个项目等的结果
(内置) enumerate(iterable, start=0) 产生形式为(index, item)的 2 元组,其中indexstart计数,item取自iterable
(内置) map(func, it1, [it2, …, itN]) func应用于it的每个项目,产生结果;如果给出了 N 个可迭代对象,则func必须接受 N 个参数,并且可迭代对象将并行消耗
itertools starmap(func, it) func应用于it的每个项目,产生结果;输入可迭代对象应产生可迭代对象iit,并且func被应用为func(*iit)

Example 17-16 演示了itertools.accumulate的一些用法。

示例 17-16。itertools.accumulate生成器函数示例
>>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
>>> import itertools
>>> list(itertools.accumulate(sample))  # ①
[5, 9, 11, 19, 26, 32, 35, 35, 44, 45] >>> list(itertools.accumulate(sample, min))  # ②
[5, 4, 2, 2, 2, 2, 2, 0, 0, 0] >>> list(itertools.accumulate(sample, max))  # ③
[5, 5, 5, 8, 8, 8, 8, 8, 9, 9] >>> import operator
>>> list(itertools.accumulate(sample, operator.mul))  # ④
[5, 20, 40, 320, 2240, 13440, 40320, 0, 0, 0] >>> list(itertools.accumulate(range(1, 11), operator.mul))
[1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800] # ⑤

运行总和。

运行最小值。

运行最大值。

运行乘积。

1!10!的阶乘。

Table 17-2 的其余函数显示在 Example 17-17 中。

示例 17-17。映射生成器函数示例
>>> list(enumerate('albatroz', 1))  # ①
[(1, 'a'), (2, 'l'), (3, 'b'), (4, 'a'), (5, 't'), (6, 'r'), (7, 'o'), (8, 'z')] >>> import operator
>>> list(map(operator.mul, range(11), range(11)))  # ②
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100] >>> list(map(operator.mul, range(11), [2, 4, 8]))  # ③
[0, 4, 16] >>> list(map(lambda a, b: (a, b), range(11), [2, 4, 8]))  # ④
[(0, 2), (1, 4), (2, 8)] >>> import itertools
>>> list(itertools.starmap(operator.mul, enumerate('albatroz', 1)))  # ⑤
['a', 'll', 'bbb', 'aaaa', 'ttttt', 'rrrrrr', 'ooooooo', 'zzzzzzzz'] >>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
>>> list(itertools.starmap(lambda a, b: b / a,
...     enumerate(itertools.accumulate(sample), 1)))  # ⑥
[5.0, 4.5, 3.6666666666666665, 4.75, 5.2, 5.333333333333333, 5.0, 4.375, 4.888888888888889, 4.5]

1开始对单词中的字母编号。

010的整数的平方。

并行从两个可迭代对象中相乘的数字:当最短的可迭对象结束时,结果停止。

这就是zip内置函数的作用。

根据单词中的位置重复每个字母,从1开始。

运行平均值。

接下来,我们有合并生成器组 - 所有这些都从多个输入可迭代对象中产生项目。chainchain.from_iterable按顺序消耗输入可迭代对象(一个接一个地),而productzipzip_longest并行消耗输入可迭代对象。参见 Table 17-3。

Table 17-3。合并多个输入可迭代对象的生成器函数

模块 函数 描述
itertools chain(it1, …, itN) it1,然后从it2等无缝地产生所有项目
itertools chain.from_iterable(it) it生成的每个可迭代对象中产生所有项目,一个接一个地无缝地;it将是一个可迭代对象,其中项目也是可迭代对象,例如,元组列表
itertools product(it1, …, itN, repeat=1) 笛卡尔积:通过组合来自每个输入可迭代对象的项目生成 N 元组,就像嵌套的for循环可以产生的那样;repeat允许多次消耗输入可迭代对象
(内置) zip(it1, …, itN, strict=False) 从并行获取的每个项目构建 N 元组,默默地在第一个可迭代对象耗尽时停止,除非给出strict=True^(a)
itertools zip_longest(it1, …, itN, fillvalue=None) 从并行获取的每个项目构建 N 元组,仅在最后一个可迭代对象耗尽时停止,用fillvalue填充空白
^(a) strict关键字参数是 Python 3.10 中的新参数。当strict=True时,如果任何可迭代对象的长度不同,则会引发ValueError。默认值为False,以确保向后兼容性。

示例 17-18 展示了itertools.chainzip生成器函数及其相关函数的使用。请记住,zip函数是以拉链拉链(与压缩无关)命名的。zipitertools.zip_longest都是在“神奇的 zip”中引入的。

示例 17-18. 合并生成器函数示例
>>> list(itertools.chain('ABC', range(2)))  # ①
['A', 'B', 'C', 0, 1] >>> list(itertools.chain(enumerate('ABC')))  # ②
[(0, 'A'), (1, 'B'), (2, 'C')] >>> list(itertools.chain.from_iterable(enumerate('ABC')))  # ③
[0, 'A', 1, 'B', 2, 'C'] >>> list(zip('ABC', range(5), [10, 20, 30, 40]))  # ④
[('A', 0, 10), ('B', 1, 20), ('C', 2, 30)] >>> list(itertools.zip_longest('ABC', range(5)))  # ⑤
[('A', 0), ('B', 1), ('C', 2), (None, 3), (None, 4)] >>> list(itertools.zip_longest('ABC', range(5), fillvalue='?'))  # ⑥
[('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]

通常使用两个或更多可迭代对象调用chain

当使用单个可迭代对象调用chain时,它不会产生任何有用的效果。

但是chain.from_iterable从可迭代对象中获取每个项目,并按顺序链接它们,只要每个项目本身是可迭代的。

zip可以并行消耗任意数量的可迭代对象,但是生成器总是在第一个可迭代对象结束时停止。在 Python ≥ 3.10 中,如果给定strict=True参数并且一个可迭代对象在其他可迭代对象之前结束,则会引发ValueError

itertools.zip_longest的工作原理类似于zip,只是它会消耗所有输入的可迭代对象,根据需要用None填充输出元组。

fillvalue关键字参数指定自定义填充值。

itertools.product生成器是计算笛卡尔积的一种懒惰方式,我们在“笛卡尔积”中使用了多个for子句的列表推导式构建。具有多个for子句的生成器表达式也可以用于懒惰地生成笛卡尔积。示例 17-19 演示了itertools.product

流畅的 Python 第二版(GPT 重译)(九)(3)https://developer.aliyun.com/article/1484707

相关文章
|
7天前
|
数据采集 存储 人工智能
【Python+微信】【企业微信开发入坑指北】4. 企业微信接入GPT,只需一个URL,自动获取文章总结
【Python+微信】【企业微信开发入坑指北】4. 企业微信接入GPT,只需一个URL,自动获取文章总结
21 0
|
12天前
|
机器学习/深度学习 人工智能 自然语言处理
总结几个GPT的超实用之处【附带Python案例】
总结几个GPT的超实用之处【附带Python案例】
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(七)(3)
JavaScript 权威指南第七版(GPT 重译)(七)
32 0
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(七)(1)
JavaScript 权威指南第七版(GPT 重译)(七)
60 0
|
12天前
|
存储 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(六)(4)
JavaScript 权威指南第七版(GPT 重译)(六)
90 2
JavaScript 权威指南第七版(GPT 重译)(六)(4)
|
12天前
|
前端开发 JavaScript API
JavaScript 权威指南第七版(GPT 重译)(六)(3)
JavaScript 权威指南第七版(GPT 重译)(六)
55 4
|
12天前
|
JSON 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(五)(2)
JavaScript 权威指南第七版(GPT 重译)(五)
35 5
|
12天前
|
JSON JavaScript 前端开发
JavaScript 权威指南第七版(GPT 重译)(四)(4)
JavaScript 权威指南第七版(GPT 重译)(四)
67 6
|
12天前
|
Web App开发 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(四)(1)
JavaScript 权威指南第七版(GPT 重译)(四)
35 2
|
12天前
|
存储 JavaScript 前端开发
JavaScript 权威指南第七版(GPT 重译)(三)(3)
JavaScript 权威指南第七版(GPT 重译)(三)
41 1