Python的容器、可迭代对象、迭代器、生成器

预备知识:Python规定的 迭代协议 与 迭代器协议
迭代协议:若对象定义了一个__iter__ 方法且__iter__ 方法的返回值是一个迭代器,即实现了迭代协议。
迭代器协议:若对象定义了一个__iter__ 方法和一个__next__ 方法,即实现了迭代器协议。其中,

  • 对象为实现了迭代协议的对象,即__iter__ 方法的返回值是一个迭代器。
  • __next__ 方法在迭代完所有数据之后,会抛出SyopIteration的错误信息。

1.定义

名称
定义
举例
容器
container
● 把多种元素组织在一起的数据结构,容器中的元素就可以逐个迭代获取。
● 并不是一种数据类型,只是一种概念。
列表(list)、元组(tuple)、字符串(str)、字典(dict)、集合(set )
可迭代对象
iterable
● 可迭代对象是一个类的实例对象。
● 此类定义了__iter__()方法, __iter__()方法返回一个迭代器对象
列表(list)、元组(tuple)、字符串(str)、字典(dict)
迭代器
iterator
● 定义一个类,此类的实例对象即为迭代器对象
● 类中定义了__iter__()和__next__()两个方法。
  __iter__()方法返回对象本身,即 self。
   __next__()方法每次返回下一个数据,如果没有数据了就抛出StopIteration异常。
①创建的迭代器对象
②调用内置函数 iter() 返回一个迭代器【如:iter(可迭代对象)】
③其他内置方法,如通过使用zip() 、enumerate()、map()、filter() 和 reversed()获得迭代器等等
生成器
generator
● 包含了yield关键字的函数即为生成器函数
● 调用此生成器函数时将自动创建生成器对象。
【yield根据生成器类generator创建对象,生成器类的内部声明了__iter__()和__next__()方法】
PS:生成器也可认为是一种特殊的迭代器,可以把它看成迭代器的子类,是实现自己独有方法的迭代器。
①调用生成器函数时自动创建的生成器对象

在这里插入图片描述

可迭代对象、迭代器、生成器三者关系

2.示例与探究

自定义可迭代对象、迭代器、生成器示例代码如下:

"""定义可迭代对象的类"""
class Foo(object):
    def __iter__(self):
        return IT()   #也可以返回生成器对象

"""定义迭代器对象的类"""
class IT(object):
    def __init__(self):
        self.counter = 0

    def __iter__(self):
        return self

    def __next__(self):
        self.counter += 1
        if self.counter == 3:
            raise StopAsyncIteration()
        return self.counter

"""定义生成器函数"""
def func():
    yield 1
    yield 2

上述代码在以下介绍中会继续使用。

2.1可迭代对象

2.1.1 可迭代对象 使用for进行循环的原理

观察以下代码,

"""创建可迭代对象 列表"""
nums = [11, 22, 33]

for num in nums:
    print(num)

运行结果:
11
12
13

"""创建可迭代对象 Foo()"""
obj = Foo()

for item in obj:
	print(item)

运行结果:
1
2

两个例子都可以观察到通过for循环,可迭代对象的元素逐个被打印出来。

思考:为什么可迭代对象仅有__iter__()方法而没有定义__next__()方法,却实现了逐个输出元素的功能?

  由于可迭代对象有__iter__()方法,__iter__()方法返回一个迭代器对象。使用for循环时,内部先执行被迭代对象(即可迭代对象)的__iter__()方法得到返回的一个迭代器对象,然后将此迭代器对象传入内置函数next()方法中,不断执行next(迭代器对象),逐个输出元素。ps:使用内置的 next(迭代器对象) 方法,这个方法会调用迭代器对象中对应的__next__()方法。(其他内置函数原理一致)
  同理,当使用for循环遍历迭代器对象元素时,内部先执行被迭代对象(即迭代器对象)的__iter__()返回了对象自身再调用next函数,此过程等价于直接执行了next函数

2.1.2 可迭代对象 通过iter()和next()输出元素

  除使用for循环以外,可迭代对象也可以通过内置函数iter()和next()输出元素。

  • 内置函数 iter() 的本质是 __iter__() ,也是返回一个迭代器。
  • 内置函数 next() 的本质是 __next__(),也是有了迭代器之后获取元素。

示例如下:

"""创建可迭代对象 Foo()"""
obj = Foo()

"""创建迭代器"""
it = iter(obj)

"""输出元素"""
print(next(it))
print(next(it))
print(next(it))

运行结果:

1
2
Traceback (most recent call last):
  File "F:/xx.py", line 30, in <module>
    print(next(it))
  File "F:/xx.py", line 17, in __next__
    raise StopAsyncIteration()
StopAsyncIteration

2.2 迭代器对象

2.2.1 迭代器对象 分别通过__next__()和next()遍历

  由于迭代器类型的__iter__()方法返回自身,__next__()方法接收自身为参数;另外,内置函数next()也是只需传入迭代器对象作为参数,本质就是__next__()方法,因此 迭代器对象不需要通过自身函数__iter__()或者内置函数iter()做一次转换,可通过自身函数__next__()方法和内置函数next()直接输出元素。示例如下:

①通过自身函数__next__()方法

"""创建迭代器对象 IT()"""
obj1 = IT()
print(obj1.__next__())
print(obj1.__next__())
print(obj1.__next__())

运行结果如下:

1
2
Traceback (most recent call last):
  File "F:/xx.py", line 30, in <module>
    print(next(it))
  File "F:/xx.py", line 17, in __next__
    raise StopAsyncIteration()
StopAsyncIteration

②内置函数next()方法

"""创建迭代器对象 IT()"""
obj1 = IT()
print(next(obj1))
print(next(obj1))
print(next(obj1))

运行结果如下:

1
2
Traceback (most recent call last):
  File "F:/xx.py", line 30, in <module>
    print(next(it))
  File "F:/xx.py", line 17, in __next__
    raise StopAsyncIteration()
StopAsyncIteration

2.2.2 为什么迭代器要实现__iter__()?

为什么迭代器的__iter__()方法 仅返回迭代器自身而不做任何处理 却依然要实现?

根据官方文档描述:
Iterators are required to have an __iter__() method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted.

  综上,之所以迭代器(Iterator)也要求实现__iter__(),是因为很多地方接收的参数是一个可迭代对象(Iterable),如果所有的Iterator都是Iterable,那么这些用Iterable地方都可以无障碍地使用Iterator了。

2.2.3 迭代器是消费型的,怎么理解?

  迭代器是有状态的,只能遍历一次,是“消费型”的,不可以“二次消费”;不属于迭代器的可迭代对象是没有状态的,每一次对可迭代对象调用iter()都会得到一个新的迭代器。示例如下:

a = iter([1,2,3])
print(a.__next__())          # 输出第一个元素
print(a.__next__())          # 输出第二个元素
print(a.__next__())          # 输出第三个元素
a = tuple(a)                 # 转换为元组
print("转换后的元组:",a) 

运行结果如下:

1
2
3
转换后的元组: ()

  通过上面__next__()方法遍历生成器对象以后,如果还需要使用该生成器对象的话,必须创建新的生成器对象!因为遍历以后,原来的生成器对象已经不存在了,输出的新元组为空。

2.3 生成器对象

2.3.1 生成器对象的创建

①自定义生成器对象创建

"""创建生成器对象 func()"""
obj2=func()
print(next(obj2))
print(next(obj2))
print(next(obj2))

运行结果如下:

Traceback (most recent call last):
  File "F:/xx.py", line 9, in <module>
    print(next(obj2))
StopIteration
1
2

②生成器推导式创建对象
生成器推导式的语法格式为:generator = (Expression for var in range),示例如下:

a = (i for i in range(3))    
print(a.__next__())          
print(a.__next__())          
print(a.__next__()) 

运行结果:
0
1
2

补充:列表推导式、生成器推导式、元组推导式

★ 列表推导式:可以快速生成一个新的列表,或者根据某个列表生成满足指定需求的列表。语法格式为:list = [Expression for var in range]

★ 生成器推导式:可以快速生成一个新的生成器对象,或者根据某个可迭代对象生成满足指定需求的生成器对象。语法格式为:generator = (Expression for var in range),示例如下:

a1 = (i for i in range(10))               #生成一个10以内的生成器
a2 = (i for i in range(10) if i % 2 == 0) #生成一个10以内的生成器
print(a1)
print(a2)

运行结果如下:

<generator object <genexpr> at 0x000001DC03E3C900>
<generator object <genexpr> at 0x000001DC03DBBD10>

★ 元组推导式没有此概念! 如果想要使用推导式生成元组,使用 tuple() 函数将生成器推导式对象转换成元组,示例如下:

a1 = (i for i in range(10))               #生成一个10以内的生成器
a2 = (i for i in range(10) if i % 2 == 0) #生成一个10以内的生成器
print("转换后的元组为:",tuple(a1))            #转换为元组
print("转换后的列表为:",list(a2))             #转换为列表

运行结果为:

转换后的元组为: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
转换后的列表为: [0, 2, 4, 6, 8] 

2.3.2 yield的运行原理 和 send()的使用

  生成器使用 yield语句返回一个值,yield语句挂起该生成器函数的状态,保留足够的信息。对生成器函数的第二次(或第n次)调用,会跳转到函数上一次挂起的位置因此生成器不仅“记住”了它的数据状态,还记住了程序执行的位置。

yield和return的区别在于:return是在程序中返回某个值,返回之后程序就不再往下运行了;而yield虽然在程序中也返回了某个值,但是挂起了此时的执行状态和执行位置,在后续代码出现next()函数调用时,继续从yield挂起的地方往下执行。

例子①

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))

运行结果如下:

starting...
4
********************
res: None
4

运行过程分析:
----------------------------主程序执行开始----------------------------

  1. g = foo():因为foo函数中有yield关键字,foo函数并不会真的执行,而是先得到一个生成器g

  2. print(next(g))next(g) 时foo函数正式开始执行

    print("starting...")输出 starting…
    ☆ 进入while进行第一轮循环,执行 res = yield 4yield返回一个4(注意这里是返回而不是对res赋值,yield类似return,因此只执行了右边的yield 4)。
    ----------------------------foo函数执行挂起----------------------------

  3. print("*"*20)输出 ********************

  4. print(next(g))next(g) 时从foo函数上面暂停位置继续执行

    -----------------------foo函数挂起位置继续执行-----------------------

    res = yield 4,因为上述yield 4已经将 4 返回出去了,并没有赋值给 res,只执行了左边的的res = ,因此 res 为 None
    print("res:",res)输出 res: None
    ☆ 进入while进行第二轮循环,执行yield 4yield返回一个4

    ----------------------------foo函数执行再次挂起----------------------------

----------------------------主程序执行完毕----------------------------

例子②

生成器有三个独有的方法:send(往生成器里传入数据)、throw(往生成器里面抛异常)、close(关闭生成器)。

  生成器的send方法先给生成器传值,再执行和next相同的操作( 从上次暂停的地方开始,执行到下一个yield ) 。把上例的最后一行换成 print(g.send(7)),观察下例:

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(g.send(7))

运行结果如下:

starting...
4
********************
res: 7
4

运行结果分析:

  例子①和例子②的前面代码的运行过程大致相同,不同的是例子①的运行分析步骤4部分。例子①的运行分析步骤4中提到,yield 4已经将4返出去,并没有赋值给res;而例子②的send函数是发送一个参数值给res后调用next()方法。例子②的运行过程为:
----------------------------主程序执行开始----------------------------

  1. g = foo():因为foo函数中有yield关键字,foo函数并不会真的执行,而是先得到一个生成器g

  2. print(next(g))next(g) 时foo函数正式开始执行

    print("starting...")输出 starting…
    ☆ 进入while进行第一轮循环,执行 res = yield 4yield返回一个4(注意这里是返回而不是对res赋值,yield类似return,因此只执行了右边的yield 4)。
    ----------------------------foo函数执行挂起----------------------------

  3. print("*"*20)输出 ********************

  4. print(g.send(7))send(7) 时从foo函数上面暂停位置继续执行

    -----------------------foo函数挂起位置继续执行-----------------------

    res = yield 4,因为上述yield 4已经将 4 返回出去了,并没有赋值给 res,但 send会把7这个值赋值给res,即理解为res = 7。由于send方法中包含next()方法,所以程序会继续向下运行。
    print("res:",res)输出 res: 7
    ☆ 进入while进行第二轮循环,执行yield 4yield返回一个4

    ----------------------------foo函数执行再次挂起----------------------------

----------------------------主程序执行完毕----------------------------

2.3.3 yield 和 yield from 的区别

观察下列yield 和 yield from代码的运行结果:

s_list = ['a','b','c']

def generator_1(li):
	yield li

def generator_2(li):
	yield from li

print("yield 产生的结果")
counter = 0
for i in generator_1(s_list):
	counter += 1
	print(i)
print(counter)

print("yield from 产生的结果")
counter = 0
for i in generator_2(s_list):
	counter += 1
	print(i)	
print(counter)

运行结果:

yield 产生的结果
['a', 'b', 'c']
1
yield from 产生的结果
a
b
c
3

  分析:可以观察到,yield直接返回了列表s_list,且counter值为1,意味着for循环只循环了一次;而yield from逐个遍历了列表s_list的元素,且counter值为3。
  因此yield和yield from后加可迭代对象时,yield from是将可迭代对象中的元素一个一个yield出来,而yield是直接yield可迭代对象

2.4 迭代器和生成器的区别

  • 迭代器是访问容器的一种方式,也就是说 容器已经出现。我们是从已有元素拓印出一份副本,只为我们此次迭代使用(从有到有的复制)。
  • 生成器是自己生成元素的(从无到有的生成)。

3.判断可迭代对象、迭代器类型的方法

  通过导入collections下的Iterable可迭代对象和Iterator迭代器类型,结合isinstance方法即可进行判断。示例如下:

from collections.abc import Iterable,Iterator
xxx='abcdec'   #字符串是可迭代对象
print(isinstance(xxx,Iterable))
print(isinstance(xxx,Iterator))

it_xxx = iter(xxx) #转换为迭代器
print(isinstance(it_xxx,Iterable))
print(isinstance(it_xxx,Iterator))

运行结果为:

True
False

True
True

4. 应用场景

4.1 迭代器:节省内存的开销

  文件对象读取有三种常用的方法:readlines、readline、迭代器读取。分别用这三种方法读取如下 script1.py文件

import sys
print(sys.path)
x = 2
print(2 ** 33)

readlines:会将整个文件加载到内存中。常搭配for循环使用,示例如下:

for line in open('script1.py').readlines():
	print(line.upper(),end='')

运行结果如下:

IMPORT SYS
PRINT(SYS.PATH)
X = 2
PRINT(2 ** 33)

但是此方法在文件较大时,往往会引发 MemoryError 内存溢出!

readline:每次调用readline方法时,就会从文件中读取一行文本,到达文件末尾时,就会返回空字符串。

● 交互模式逐行读取文件:

>>>f = open('script1.py') 
     
>>>f.readline()               
import sys\n'

>>>f.readline()
print(sys.path)\n'

>>>f.readline()
x = 2\n'

>>>f.readline()
print(2 ** 33)\n'

>>>f.readline()                 
''

● 搭配循环逐行读取:

f = open('script1.py')	
while True:	
	line = f.readline()
	if not line: 
		break
	print(line.upper(),end='')

运行结果如下:

IMPORT SYS
PRINT(SYS.PATH)
X = 2
PRINT(2 ** 33)

③迭代器读取
文件对象是一个迭代器,支持迭代协议,通过__next__()方法可逐行读取。
● 交互模式下,每次调用__next__()方法

>>>f = open('script1.py')      

>>>f.__next__()                
import sys\n'

>>>f.__next__()
print(sys.path)\n'

>>>f.__next__()
x = 2\n'

>>>f.__next__()
print(2 ** 33)\n'

>>>f.__next__()
Traceback (most recent call last):

...more exception text omitted...

StopIteration

● 搭配for循环逐行读取

for line in open('script1.py'):
	print(line.upper(),end='')    

运行结果如下:

IMPORT SYS
PRINT(SYS.PATH)
X = 2
PRINT(2 ** 33)

  搭配循环的 readline方式与迭代器方式 比较:readline方式可能运行得更慢一些,因为迭代器在Python中是以C语言的速度运行的,而while循环方式则是通过Python虚拟机运行Python字节码的。一般把Python代码换成C程序代码,速度都更快。

4.2 生成器:生产者与消费者模型

  模拟实现一个苹果公司生产手机以及消费者购买手机的 并发过程,这个模型的三要素是 生产者、消费者、缓冲区 ,缓冲区选用队列,其工作方式是先进先出。

import  time
import  random

cacheList = []
# # 设置缓冲区的最大长度, 当缓冲区到达最大长度, 那么生产者就不能再生产了
cacheListLen = 2

def is_full():
    """
    判断缓冲区队列是否已经满了
    """
    return  len(cacheList) == cacheListLen


def Producer(name):
    """
    生产者, 主要用于生产数据
    """
    while True:
        if not is_full():
            print("生产者[%s]正在生产苹果手机....." %(name))
            # 模拟生产者生产数据需要的时间, 随机休眠0~1秒,
            time.sleep(random.random())
            print("[%s] 已经生产苹果手机完成" %(name))
            # 将生产的游戏机放入缓冲区
            cacheList.append('苹果手机')
        else:
            print("缓存已满!请停止生产")
            yield

def Consumer(name):
    """
    消费者, 用于处理/消费数据
    """
    print("【%s】正在准备购买苹果手机" %(name))
    while True:
        item = yield
        print("【%s】购买%s成功" %(name,item))

if __name__ == "__main__":
	# producer是一个生成器
	producer = Producer("苹果公司")
	next(producer)
	
	# 列表生成式, 生成消费者
	consumers = [Consumer("消费者%s" %(i+1)) for i in range(3)]
	
	# 依次遍历所有的消费者, 给提供手机
	for consumer in consumers:
	    if not cacheList:
	        print("目前商店没有手机库存.....")
	    else:
	        #如果缓冲区不满,先生产,再提供消费
	        if not is_full():
	            next(producer)
	            item = cacheList.pop(0)
	            next(consumer)
	            consumer.send(item)
	        else:
	            item = cacheList.pop(0)
	            next(consumer)
	            consumer.send(item)

运行结果如下:

生产者[苹果公司]正在生产苹果手机.....
[苹果公司] 已经生产苹果手机完成
生产者[苹果公司]正在生产苹果手机.....
[苹果公司] 已经生产苹果手机完成
缓存已满!请停止生产
【消费者1】正在准备购买苹果手机
【消费者1】购买苹果手机成功
生产者[苹果公司]正在生产苹果手机.....
[苹果公司] 已经生产苹果手机完成
缓存已满!请停止生产
【消费者2】正在准备购买苹果手机
【消费者2】购买苹果手机成功
生产者[苹果公司]正在生产苹果手机.....
[苹果公司] 已经生产苹果手机完成
缓存已满!请停止生产
【消费者3】正在准备购买苹果手机
【消费者3】购买苹果手机成功

该模型的好处:

  1. 实现了生产者与消费者的解耦和
  2. 平衡了生产力与消费力,就是生产者一直不停的生产,消费者可以不停的消费,因为二者不再是直接沟通的,而是跟队列沟通的。

5.补充

5.1 iter()、 __iter__()与__getitem__()的关系

  • Iter()是产生迭代器的内置函数,凡是 定义有__iter__()函数的对象 或者 支持序列访问协议,也就是定义有__getitem__()函数的对象 皆可以通过 iter()内置函数 产生迭代器(iterable)对象。可迭代对象常通过iter()工厂函数产生迭代器
  • __iter__()是迭代协议函数,凡是实现__iter__方法的对象,皆是可迭代对象。迭代器对象可直接通过自身定义的__next__()函数逐个输出元素。
  • __getitem__()方法可以通过使用下标获取元素(切片操作),单独实现__getitem__()的类,实例是一个可迭代对象但不是Iterable类型。
iter()、__iter__()、__getitem__()三者关系

单独实现__getitem__()的类
  单独实现__getitem__()的类,实例化一个可迭代对象。因此可以用for进行循环和内置函数iter()产生迭代器,如下例:

class Library(object):
    def __init__(self):
        self.books = [1, 2, 3]
        self.index = -1
 
    def __getitem__(self, i):
        return self.books[i]

l = Library()
print(l[1])

print("-"*10)

for i in l:
    print(i)
    
print("-"*10)

it = iter(l)
print(next(it))

运行结果为:

2
----------
1
2
3
----------
1

当__iter__()、__getitem__()同时存在


class Library(object):
    def __init__(self):
        self.books = [1, 2, 3]
        self.index = -1
 
    def __getitem__(self, i):
        return self.books[i]
 
    def __iter__(self):
        return self
    # #
    def __next__(self):
        self.index += 1
        if self.index > len(self.books)-1:
            raise StopIteration()
        return self.books[self.index]
 
 
l = Library()
print(next(l))
print("-"*10)
for i in l:
    print(i)

运行结果为:

1
----------
2
3

  可以看到,刚开始调用next()函数先输出了 1,使得调用__iter__函数返回的迭代器元素从第二位开始;后面通过for循环时依次输出的是后面元素,得知__iter__()优先于__getitem__()返回迭代器

5.2 range与xrange

  在Python 2中,range() 生成的是列表,xrange()是生成器;而在Python 3中 xrange()已不存在,而range() 生成的是可迭代对象。在此就Python 3对 range()展开讨论。

5.2.1 range对象是什么?

range()不是一个函数,而是一个可迭代对象,验证如下:

print(range(1,3)) 

from collections.abc import Iterable,Iterator
print(isinstance(range(5),Iterable))  

运行结果为:

range(1,3)    # 如果range()是函数,返回结果应该是0,1,2
True

函数语法range(start, stop[, step])

参数说明

  • start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range(0, 5)
  • stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
  • step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)

注意点

(1)它表示的是 左闭右开 区间。
(2)它接收的 参数必须是整数,可以是负数,但不能是浮点数等其它类型。
(3)它是 不可变 的序列类型,可以进行判断元素、查找元素、切片等操作,但不能修改元素。
(4)它是 可迭代对象,但不是迭代器。
  在 for 循环 遍历时,可迭代对象与迭代器的性能是一样的,即它们都是惰性求值的,在空间复杂度与时间复杂度上并无差异。不同的是可迭代对象不支持自遍历(即next()方法),而迭代器本身不支持切片(即__getitem__() 方法)。

代码示例

for n in range(5):
    print(n)    # 输出:0 1 2 3 4

5.2.2 range()是什么类型?

  根据官方文档,有三种基本的序列类型:列表、元组和范围(range)对象。
  range()是不可变的序列类型,可以进行判断元素、查找元素、切片等操作,但不能修改元素,也不支持加法拼接和乘法重复

a = range(8)
print(a[0]==10)  # 判断元素
print(a[0])    # 查找元素
print(a[2:5])  # 切片
a[2]=4         # 赋值
print(a[2])

运行结果为:

False
0
range(2, 5)
Traceback (most recent call last):
  File "D:\main.py", line 5, in <module>
    a[2]=4         # 赋值
TypeError: 'range' object does not support item assignment

思考:同样是不可变序列,字符串和元组支持进行加法拼接与乘法重复,为什么 range 不支持呢?
  根据官方文档: range 对象仅仅表示一个遵循着严格模式的序列,而重复与拼接通常会破坏这种模式。range 相当于一个等差数列,拼接两个等差数列或重复拼接一个等差数列有违 range 本身的语法模式。


参考

15分钟彻底搞懂迭代器、可迭代对象、生成器【python迭代器】
python iter()与 __iter__()的区别
python3中的range返回的是迭代器吗
【Python】__iter__和__getitem__区别
python中yield的用法详解——最简单,最清晰的解释
python中生成器与迭代器到底有什么区别?一文带你彻底搞清楚
Python教程:Python中的元组推导式详解
python3range函数采用迭代器方式的好处_range函数用法完全解读
python中yield和yield from的区别(附代码理解)
python系列教程157——文件迭代器

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值