前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >PyTorch源码解读之torch.utils.data.DataLoader「建议收藏」

PyTorch源码解读之torch.utils.data.DataLoader「建议收藏」

作者头像
全栈程序员站长
发布于 2022-07-01 04:58:21
发布于 2022-07-01 04:58:21
75330
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

PyTorch中数据读取的一个重要接口是torch.utils.data.DataLoader,该接口定义在dataloader.py脚本中,只要是用PyTorch来训练模型基本都会用到该接口,该接口主要用来将自定义的数据读取接口的输出或者PyTorch已有的数据读取接口的输入按照batch size封装成Tensor,后续只需要再包装成Variable即可作为模型的输入,因此该接口有点承上启下的作用,比较重要。这篇博客介绍该接口的源码,主要包含DataLoader和DataLoaderIter两个类dataloader.py脚本的的github地址https://github.com/pytorch/pytorch/blob/master/torch/utils/data/dataloader.py

DataLoader类源码如下。先看看__init__中的几个重要的输入:1、dataset,这个就是PyTorch已有的数据读取接口(比如torchvision.datasets.ImageFolder)或者自定义的数据接口的输出,该输出要么是torch.utils.data.Dataset类的对象,要么是继承自torch.utils.data.Dataset类的自定义类的对象。2、batch_size,根据具体情况设置即可。3、shuffle,一般在训练数据中会采用。4、collate_fn,是用来处理不同情况下的输入dataset的封装,一般采用默认即可,除非你自定义的数据读取输出非常少见。5、batch_sampler,从注释可以看出,其和batch_size、shuffle等参数是互斥的,一般采用默认。6、sampler,从代码可以看出,其和shuffle是互斥的,一般默认即可。7、num_workers,从注释可以看出这个参数必须大于等于0,0的话表示数据导入在主进程中进行,其他大于0的数表示通过多个进程来导入数据,可以加快数据导入速度。8、pin_memory,注释写得很清楚了: pin_memory (bool, optional): If True, the data loader will copy tensors into CUDA pinned memory before returning them. 也就是一个数据拷贝的问题。9、timeout,是用来设置数据读取的超时时间的,但超过这个时间还没读取到数据的话就会报错。 在__init__中,RandomSampler类表示随机采样且不重复,所以起到的就是shuffle的作用。BatchSampler类则是把batch size个RandomSampler类对象封装成一个,这样就实现了随机选取一个batch的目的。这两个采样类都是定义在sampler.py脚本中,地址:https://github.com/pytorch/pytorch/blob/master/torch/utils/data/sampler.py。以上这些都是初始化的时候进行的。当代码运行到要从torch.utils.data.DataLoader类生成的对象中取数据的时候,比如: train_data=torch.utils.data.DataLoader(...) for i, (input, target) in enumerate(train_data): ... 就会调用DataLoader类的__iter__方法,__iter__方法就一行代码:return DataLoaderIter(self),输入正是DataLoader类的属性。因此当调用__iter__方法的时候就牵扯到另外一个类:DataLoaderIter,接下来介绍。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class DataLoader(object):
""" Data loader. Combines a dataset and a sampler, and provides single- or multi-process iterators over the dataset. Arguments: dataset (Dataset): dataset from which to load the data. batch_size (int, optional): how many samples per batch to load (default: 1). shuffle (bool, optional): set to ``True`` to have the data reshuffled at every epoch (default: False). sampler (Sampler, optional): defines the strategy to draw samples from the dataset. If specified, ``shuffle`` must be False. batch_sampler (Sampler, optional): like sampler, but returns a batch of indices at a time. Mutually exclusive with batch_size, shuffle, sampler, and drop_last. num_workers (int, optional): how many subprocesses to use for data loading. 0 means that the data will be loaded in the main process. (default: 0) collate_fn (callable, optional): merges a list of samples to form a mini-batch. pin_memory (bool, optional): If ``True``, the data loader will copy tensors into CUDA pinned memory before returning them. drop_last (bool, optional): set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then the last batch will be smaller. (default: False) timeout (numeric, optional): if positive, the timeout value for collecting a batch from workers. Should always be non-negative. (default: 0) worker_init_fn (callable, optional): If not None, this will be called on each worker subprocess with the worker id as input, after seeding and before data loading. (default: None) """

    def __init__(self, dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=default_collate, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None):
        self.dataset = dataset
        self.batch_size = batch_size
        self.num_workers = num_workers
        self.collate_fn = collate_fn
        self.pin_memory = pin_memory
        self.drop_last = drop_last
        self.timeout = timeout
        self.worker_init_fn = worker_init_fn

        if timeout < 0:
            raise ValueError('timeout option should be non-negative')

        if batch_sampler is not None:
            if batch_size > 1 or shuffle or sampler is not None or drop_last:
                raise ValueError('batch_sampler is mutually exclusive with '
                                 'batch_size, shuffle, sampler, and drop_last')

        if sampler is not None and shuffle:
            raise ValueError('sampler is mutually exclusive with shuffle')

        if self.num_workers < 0:
            raise ValueError('num_workers cannot be negative; '
                             'use num_workers=0 to disable multiprocessing.')

        if batch_sampler is None:
            if sampler is None:
                if shuffle:
                    sampler = RandomSampler(dataset)
                else:
                    sampler = SequentialSampler(dataset)
            batch_sampler = BatchSampler(sampler, batch_size, drop_last)

        self.sampler = sampler
        self.batch_sampler = batch_sampler

    def __iter__(self):
        return DataLoaderIter(self)

    def __len__(self):
        return len(self.batch_sampler)

DataLoaderIter类源码如下。self.index_queue = multiprocessing.SimpleQueue()中的multiprocessing是Python中的多进程管理包,而threading则是Python中的多线程管理包,二者很大一部分的接口用法类似。还是照例先看看__init__,前面部分都是一些赋值操作,比较特殊的是self.sample_iter = iter(self.batch_sampler),得到的self.sample_iter可以通过next(self.sample_iter)来获取batch size个数据的index。self.rcvd_idx表示读取到的一个batch数据的index,初始化为0,该值在迭代读取数据的时候会用到。if self.num_workers语句是针对多进程或单进程的情况进行初始化,如果不是设置为多进程读取数据,那么就不需要这些初始化操作,后面会介绍单进程数据读取。在if语句中通过multiprocessing.SimpleQueue()类创建了一个简单的队列对象。multiprocessing.Process类就是构造进程的类,这里根据设定的进程数来启动,然后赋值给self.workers。接下来的一个for循环就通过调用start方法依次启动self.workers中的进程。接下来关于self.pin_memory的判断语句,该判断语句内部主要是实现了多线程操作。self.pin_memory的含义在前面已经介绍过了,当为True的时候,就会把数据拷到CUDA中。self.data_queue = queue.Queue()是通过Python的queue模块初始化得到一个先进先出的队列(queue模块也可以初始化得到先进后出的队列,需要用queue.LifoQueue()初始化),queue模块主要应用在多线程读取数据中。在threading.Thread的args参数中,第一个参数in_data就是一个进程的数据,一个进程中不同线程的数据也是通过队列来维护的,这里采用的是Python的queue模块来初始化得到一个队列:queue.Queue()。初始化结束后,就会调用__next__方法,接下来介绍。 总的来说,如果设置为多进程读取数据,那么就会采用队列的方式来读,如果不是采用多进程来读取数据,那就采用普通方式来读

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class DataLoaderIter(object):
    "Iterates once over the DataLoader's dataset, as specified by the sampler"

    def __init__(self, loader):
        self.dataset = loader.dataset
        self.collate_fn = loader.collate_fn
        self.batch_sampler = loader.batch_sampler
        self.num_workers = loader.num_workers
        self.pin_memory = loader.pin_memory and torch.cuda.is_available()
        self.timeout = loader.timeout
        self.done_event = threading.Event()

        self.sample_iter = iter(self.batch_sampler)

        if self.num_workers > 0:
            self.worker_init_fn = loader.worker_init_fn
            self.index_queue = multiprocessing.SimpleQueue()
            self.worker_result_queue = multiprocessing.SimpleQueue()
            self.batches_outstanding = 0
            self.worker_pids_set = False
            self.shutdown = False
            self.send_idx = 0
            self.rcvd_idx = 0
            self.reorder_dict = {}

            base_seed = torch.LongTensor(1).random_()[0]
            self.workers = [
                multiprocessing.Process(
                    target=_worker_loop,
                    args=(self.dataset, self.index_queue, self.worker_result_queue, self.collate_fn,
                          base_seed + i, self.worker_init_fn, i))
                for i in range(self.num_workers)]

            if self.pin_memory or self.timeout > 0:
                self.data_queue = queue.Queue()
                self.worker_manager_thread = threading.Thread(
                    target=_worker_manager_loop,
                    args=(self.worker_result_queue, self.data_queue, self.done_event, self.pin_memory,
                          torch.cuda.current_device()))
                self.worker_manager_thread.daemon = True
                self.worker_manager_thread.start()
            else:
                self.data_queue = self.worker_result_queue

            for w in self.workers:
                w.daemon = True  # ensure that the worker exits on process exit
                w.start()

            _update_worker_pids(id(self), tuple(w.pid for w in self.workers))
            _set_SIGCHLD_handler()
            self.worker_pids_set = True

            # prime the prefetch loop
            for _ in range(2 * self.num_workers):
                self._put_indices()

DataLoaderIter类的__next__方法如下,包含3个if语句和1个while语句。 第一个if语句是用来处理self.num_workers等于0的情况,也就是不采用多进程进行数据读取,可以看出在这个if语句中先通过indices = next(self.sample_iter)获取长度为batch size的列表:indices,这个列表的每个值表示一个batch中每个数据的index,每执行一次next操作都会读取一批长度为batch size的indices列表。然后通过self.collate_fn函数将batch size个tuple(每个tuple长度为2,其中第一个值是数据,Tensor类型,第二个值是标签,int类型)封装成一个list,这个list长度为2,两个值都是Tensor,一个是batch size个数据组成的FloatTensor,另一个是batch size个标签组成的LongTensor。所以简单讲self.collate_fn函数就是将batch size个分散的Tensor封装成一个Tensor。batch = pin_memory_batch(batch)中pin_memory_batch函数的作用就是将输入batch的每个Tensor都拷贝到CUDA中,该函数后面会详细介绍。 第二个if语句是判断当前想要读取的batch的index(self.rcvd_idx)是否之前已经读出来过(已读出来的index和batch数据保存在self.reorder_dict字典中,可以结合最后的while语句一起看,因为self.reorder_dict字典的更新是在最后的while语句中),如果之前已经读取过了,就根据这个index从reorder_dict字典中弹出对应的数据。最后返回batch数据的时候是 return self._process_next_batch(batch),该方法后面会详细介绍。主要做是获取下一个batch的数据index信息。 第三个if语句,self.batches_outstanding的值在前面初始中调用self._put_indices()方法时修改了,所以假设你的进程数self.num_workers设置为3,那么这里self.batches_outstanding就是3*2=6,可具体看self._put_indices()方法。 最后的while循环就是真正用来从队列中读取数据的操作,最主要的就是idx, batch = self._get_batch(),通过调用_get_batch()方法来读取,后面有介绍,简单讲就是调用了队列的get方法得到下一个batch的数据,得到的batch一般是长度为2的列表,列表的两个值都是Tensor,分别表示数据(是一个batch的)和标签。_get_batch()方法除了返回batch数据外,还得到另一个输出:idx,这个输出表示batch的index,这个if idx != self.rcvd_idx条件语句表示如果你读取到的batch的index不等于当前想要的index:selg,rcvd_idx,那么就将读取到的数据保存在字典self.reorder_dict中:self.reorder_dict[idx] = batch,然后继续读取数据,直到读取到的数据的index等于self.rcvd_idx。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    def __next__(self):
        if self.num_workers == 0:  # same-process loading
            indices = next(self.sample_iter)  # may raise StopIteration
            batch = self.collate_fn([self.dataset[i] for i in indices])
            if self.pin_memory:
                batch = pin_memory_batch(batch)
            return batch

        # check if the next sample has already been generated
        if self.rcvd_idx in self.reorder_dict:
            batch = self.reorder_dict.pop(self.rcvd_idx)
            return self._process_next_batch(batch)

        if self.batches_outstanding == 0:
            self._shutdown_workers()
            raise StopIteration

        while True:
            assert (not self.shutdown and self.batches_outstanding > 0)
            idx, batch = self._get_batch()
            self.batches_outstanding -= 1
            if idx != self.rcvd_idx:
                # store out-of-order samples
                self.reorder_dict[idx] = batch
                continue
            return self._process_next_batch(batch)

pin_memory_batch函数不是定义在DataLoader类或DataLoaderIter类中。该函数主要是对batch中的Tensor执行batch.pin_memory()操作,这里的很多条件语句只是用来判断batch的类型,假如batch是一个列表,列表中的每个值是Tensor,那么就会执行 elif isinstance(batch, collections.Sequence):这个条件,从而遍历该列表中的每个Tensor,然后执行第一个条件语句的内容: return batch.pin_memory()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def pin_memory_batch(batch):
    if torch.is_tensor(batch):
        return batch.pin_memory()
    elif isinstance(batch, string_classes):
        return batch
    elif isinstance(batch, collections.Mapping):
        return {k: pin_memory_batch(sample) for k, sample in batch.items()}
    elif isinstance(batch, collections.Sequence):
        return [pin_memory_batch(sample) for sample in batch]
    else:
        return batch

DataloaderIter类的_get_batch方法。主要根据是否设置了超时时间来操作,如果超过指定的超时时间后没有从队列中读到数据就报错,如果不设置超时时间且一致没有从队列中读到数据,那么就会一直卡着且不报错,这部分是PyTorch后来修的一个bug。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    def _get_batch(self):
        if self.timeout > 0:
            try:
                return self.data_queue.get(True, self.timeout)
            except queue.Empty:
                raise RuntimeError('DataLoader timed out after {} seconds'.format(self.timeout))
        else:
            return self.data_queue.get()

DataLoaderIter类的_process_next_batch方法。首先对self.rcvd_idx进行加一,也就是更新下下一个要读取的batch数据的index。然后调用_put_indices()方法获取下一个batch的每个数据的index。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   def _process_next_batch(self, batch):
        self.rcvd_idx += 1
        self._put_indices()
        if isinstance(batch, ExceptionWrapper):
            raise batch.exc_type(batch.exc_msg)
        return batch

DataLoaderIter类的_put_indices方法。该方法主要实现从self.sample_iter中读取下一个batch数据中每个数据的index:indices = next(self.sample_iter, None),注意这里的index和前面idx是不一样的,这里的index是一个batch中每个数据的index,idx是一个batch的index;然后将读取到的index通过调用queue对象的put方法压到队列self.index_queue中:self.index_queue.put((self.send_idx, indices))

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def _put_indices(self):
        assert self.batches_outstanding < 2 * self.num_workers
        indices = next(self.sample_iter, None)
        if indices is None:
            return
        self.index_queue.put((self.send_idx, indices))
        self.batches_outstanding += 1
        self.send_idx += 1

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/131373.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
3 条评论
热度
最新
SSH的本地机器也必须是linux吗?
SSH的本地机器也必须是linux吗?
回复回复点赞举报
我是在windows上用SSH链接的云主机linux, 在云主机上按照上面的安装完,运行code,没有报错,也没有反应
我是在windows上用SSH链接的云主机linux, 在云主机上按照上面的安装完,运行code,没有报错,也没有反应
回复回复点赞举报
运行code后没有反应呢。。我是在云主机上装的
运行code后没有反应呢。。我是在云主机上装的
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
探索企业基本信息查询API:数据访问的便捷方式
当涉及到获取企业的基本信息时,传统的方法往往需要大量的时间和人力资源,以收集、整理和验证数据。然而,现在有一种便捷的方式可以解决这个问题,那就是通过企业基本信息查询API。本文将探讨这种API是如何成为数据访问的便捷方式,以及它们为企业和个人带来的好处。
用户10428865
2023/10/25
4550
企业一般纳税人查询API:简化税务信息获取的利器
随着数字化时代的到来,企业纳税和财务管理领域也经历了革命性的变化。税务管理不再是繁琐的手动工作,而是通过技术工具实现高效和精确。其中,企业一般纳税人查询API成为了企业税务信息获取的强大利器。这一工具不仅简化了税务信息的访问,还提供了多种优势,有助于企业在税务合规性和财务管理方面取得更大的成功。
用户10428865
2023/10/24
9410
如何使用企业经营异常信息API及其应用实例
随着企业竞争日益激烈,风险控制变得越来越重要。在这个过程中,企业经营异常信息查询API成为了一种非常有用的数据工具,用来检测企业的经营状况和风险信息。本文将介绍如何使用企业经营异常信息查询API并提供一些实例。
用户10428865
2023/11/09
2140
企业变更记录查询API:解密企业演变的关键数据
随着市场竞争的不断升级和商业环境的动态变化,企业必须不断适应新的情况和变革。在这个过程中,企业的变更记录成为了关键的数据,它可以帮助企业了解自己的发展历程、监测竞争对手的动态、评估市场趋势和满足法律法规的合规要求。为了解密企业演变的关键数据,企业变更记录查询API应运而生。
用户10428865
2023/09/11
2260
备案信息一键查询:网站备案信息查询API的操作与优势
在当今数字化的时代,企业的在线存在至关重要。而为了确保在网络空间的法规合规性,了解和管理网站备案信息变得尤为重要。为了使这一过程更为高效、便捷,网站备案信息查询API应运而生,为企业提供了一种简便的方式来获取和管理备案信息。
用户10428865
2023/11/22
6820
企业经营异常查询API:保障供应链的可靠性与稳定性
在全球化和数字化的商业环境中,供应链的可靠性和稳定性对企业的成功至关重要。企业经营异常信息查询API正崭露头角,成为供应链管理中不可或缺的工具。本文将深入探讨如何利用企业经营异常信息查询API来保障供应链的可靠性和稳定性,以及它们对供应链管理的积极影响。
用户10428865
2023/09/14
1890
企业经营异常查询API:保障供应链的可靠性与稳定性
关于工商详细信息 API,你想了解的都在这里了
工商详细信息 API 是一种基于云计算和数据挖掘技术的 API,旨在帮助用户快速获取公司的详细信息。通过使用这个 API,用户可以输入公司的名称或注册号,获取公司的详细信息,例如公司名称、法定代表人、注册资本、注册地址、经营范围、股东信息等等。这种 API 可以为用户节省大量查询信息的时间和精力,提高数据处理的效率和准确性。
不是海碗
2023/04/07
1K0
关于工商详细信息 API,你想了解的都在这里了
数字化风险管理:利用企业失信人API提前预警合作潜在风险
在当今数字化时代,企业面临着日益复杂的商业环境和风险。为了保障商业合作的安全可靠,数字化风险管理变得至关重要。其中,利用先进的技术工具如企业失信人API,提前预警合作潜在风险成为企业经营中的一项关键策略。本文将深入探讨数字化风险管理的必要性,并阐述企业失信人API在这一背景下的作用与优势。
用户10428865
2023/12/07
1960
特殊企业信息轻松查询:特殊企业基本信息查询API的实用性探讨
在当今数字化时代,企业管理和决策往往取决于有效获取和分析关键信息。对于特殊企业,如香港公司、社会组织、律所、事业单位、基金会和新机构,获取准确、及时的基本信息至关重要。在这个背景下,特殊企业基本信息查询API正逐渐成为管理者和决策者的得力工具,极大地提升了信息获取的效率和便捷性。
用户10428865
2023/11/23
1640
企业立案信息API的优势与应用场景
随着科技的不断进步,创业者和企业家们在创办新企业时愈发依赖数字化工具。其中,企业立案信息API成为了一项重要的资源,它提供了有关企业立案的关键信息,为企业家们提供了许多优势和丰富的应用场景。本文将探讨企业立案信息API的优势,并深入了解它在不同领域的应用场景。
用户10428865
2023/10/17
1480
探索企业主要人员API在金融领域的应用
随着金融科技的不断发展,企业主要人员API在金融领域的应用日益重要。本文将探讨这个话题,并介绍一些关键的应用案例。
用户10428865
2023/11/17
1520
中篇:热门免费 API 集合(附接入代码示例)
1、APISpace 的 通知短信:当您需要快速通知用户时,通知短信是最快捷有效的方式。短信通知支持三大运营商以及虚拟运营商,我们提供电信级运维保障、独享专用通道。
用户10428865
2023/07/26
2710
中篇:热门免费 API 集合(附接入代码示例)
企业工商四要素核验API的实现原理和功能介绍
随着社会经济的不断发展,对企业信息的准确性和可信度要求也越来越高。为了有效防范企业信息不实和欺诈行为,企业工商四要素核验API应运而生。该API可以通过传入企业名称、社会统一信用代码、法人名称、法人身份证等信息,快速进行核验,确保这四要素的一致性,从而提高对企业信息的信任度。
用户10428865
2023/11/16
5620
企业商标信息查询API的优势和应用实例分析
企业商标是企业在市场中的重要标识和竞争力的体现,而商标信息查询API则成为了企业品牌管理的重要工具。那么,这篇文章将详细阐述企业商标信息查询API的优势和应用实例分析。
用户10428865
2023/11/16
4980
用便捷API接口快速获取企业联系方式
在如今竞争激烈的商业环境中,了解企业的联系方式对于拓展市场、建立商业合作关系、寻求合作伙伴等方面至关重要。本文将为大家介绍一个便捷的API接口,通过公司名称、注册号或社会统一信用代码,快速获取企业联系方式的详细信息。
wapicn
2024/05/21
3490
用便捷API接口快速获取企业联系方式
下篇:热门免费 API 集合(附接入代码示例)
2、APISpace 的 运营商三要素 API:输入姓名、身份证号码、手机号码,验证此三种信息是否一致,返回验证结果、手机归属地、运营商名称。
用户10428865
2023/07/27
2390
热门免费 API 集合(附接入代码示例)
1、天气预报查询:支持全国以及全球多个城市的天气查询,包含国内3400+个城市以及国际4万个城市的实况数据,同时也支持国内任意经纬度查询,接口会返回该经纬度最近的站点信息;更新频率分钟级别。
用户10428865
2023/07/25
3510
热门免费 API 集合(附接入代码示例)
企业著作权数据的价值:探索企业作品著作权API的应用
随着知识经济的崛起,企业的知识产权和著作权保护变得愈发重要。企业拥有大量的著作权作品,包括文档、软件、设计、创意和更多。这些作品代表了企业的创新和核心价值。为了更好地保护和管理这些资产,企业可以探索企业作品著作权API的应用。本文将探讨企业著作权数据的价值以及如何利用API提高知识产权管理。
用户10428865
2023/10/19
2960
如何使用企业联系方式查询API拓展客户群
在当今竞争激烈的商业环境中,拓展企业客户群已经成为许多企业的首要任务之一。在这种情况下,使用企业联系方式查询API可以帮助企业在社交媒体上寻找潜在客户。本文将探讨如何使用企业联系方式查询API拓展企业客户群。
用户10428865
2023/11/07
2160
企业年报API:打开企业经营大数据的新视角
随着数字化转型的深入推进,企业年报API作为企业信息化建设中的重要组成部分,受到了越来越多企业和机构的关注和重视。而且,随着中国经济的快速发展,企业年报也成为投资者、监管机构以及相关利益方了解企业经营状况的重要途径。本文将从新视角出发,探讨企业年报API对企业经营大数据的打开作用。
用户10428865
2023/11/13
3490
推荐阅读
相关推荐
探索企业基本信息查询API:数据访问的便捷方式
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验