[Python] 打开字符串使像 open(file, 'r') 一样可迭代

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/qq_29757283/article/details/84982227

[Python] 打开字符串使像 open(file, ‘r’) 一样可迭代

需求

你想要像 open(file, ‘r’) 一样 OPEN(string), 然后可以按行读取或者在 for 语句中迭代它。

Solution

solution 这个放的代码是个人的最终版本,有改进或建议请在评论区中指出,我会在可以更新的时候更新本处代码!

Core Code

从 string 生成可迭代对象最简单方式

string_as_list_which_can_be_iterable = string.splitlines(True)
#
# iterable usage:
#
for line in string_as_list_which_can_be_iterable:
    print(line, end="")

仿 open(file, 'r') 完整方法实现方式

N/A

如何使用

N/A

测试

N/A

原理

实现上述功能的方式有很多,这里我们将参照 Fluent Python 中的解释迭代器的章节,一步一步实现早期 python 版本的写法 __item__,和现代 python 版本的写法 __iter__ 以及使用 yield 实现的版本等。

你将学到什么

  1. python 可迭代条件(类型)
  2. python 创建自定义的可迭代类型
  3. python yield 的应用(使用),再解释工作原理
  4. 涉及到一处简单的 RE 表达式,内置 RE 库的使用
  5. 1, 和 2, 我们自己实现了 python 的 魔术 特殊方法,这解释了 python 为什么有强大的 “一致性”。

用于测试我们的实现的 string 说明

在开始具体的实现之前,有必要说明一下我们用于测试的 string 内容是什么。

将 string open 得到一个可迭代的实例对象最初是我在学习 TDD1 的时候,边测试边开发一个 django web 应用时,遇到一章开始创建表单的地方,为了“测试” web 应用是否正确地渲染了前端页面(渲染后地页面用于响应浏览器请求),需要用“手动”渲染地页面源代码和通过 HTTP GET 请求经过正常后台渲染响应的页面源代码比较,看看是否一致。

就像我上面说的,这是一个 django + 表单 页面,所以里面在默认情况下是含有 token 的,而测试的时候是经过了两种不同的路径渲染页面,导致了 token 是不同的。因此是需要删除 token 后再比较页面源代码的。

当然,删除的办法有很多,我当时想到的是因为 token 字段就在页面源代码中的独立一行,像这样:

django token
那么我在做比较的测试代码中,可以用 for 按行迭代,判断如果某一行中有 “csrfmiddlewaretoken” 就跳过比较。

所以,你可以想象在我还没有实现现在正要实现的“打开 string 得到一个可迭代对象”的时候,我使用了一种行数不多但是仍然并不优雅(使用场景很受限,代码不够可复用)的方式实现了我想要的判断2

因此,学习实现一份 “打开 string 得到一个可迭代对象” 正是一项既实用又能够学习应用 python 迭代器,生成器这方面知识的好例子。

最后,如果你没了解过没学过 django,所以没有完全明白我上面在讲什么也没有任何关系,我上面只是讲述了我实现这份代码的背景和这份代码可以使用的一个场景。

因此下面我用于测试的 string 就是一份“网页源代码”,一般网页源代码就是会包括 HTML,JavaScript 代码,并且有相当可观的长度。我使用 requests 包3 获取了我自己开发的一个 web 应用 - “django cloud” 的早期版本的上传文件页面的网页源代码,这个页面长这样:
localhost:8028/basic/ 页面

因此获取这个页面的网页源代码能够限制我们这篇文章的篇幅 - 不会因为有太多的源码贴出来导致文章太长。
右击浏览器,“查看网页源代码”,可以观察这个页面的代码如下:
localhost:8028/basic/ 网页源代码

使用 requests 包获取这张网页源代码的方式如下:

$ pip -V  # 这一行只是为了说明我使用 “pip”,但使用的 pip 是对应 python3 版本的!
.....Python 3.x.x....
$ pip install requests
## 如果你想要像我获取“网页源代码”到一个 string 中来测试接下来要实现的代码,
## 并且还没有安装 requests 库的话,那么现在就可以执行那一行命令了。
$ python -V
Python 3.x.x ....
$ python
>>> import requests
>>> r = requests.get("http://localhost:8028/basic/")
### 因为这是我自己本地启动的 web 服务器。
### 你可以使用 r = requests.get("https://www.baidu.com")
### 这样的形式抓取百度首页的网页源代码
>>> string = t.text
### 上面这行代码就将 string 关联到了“网页源代码”。
>>> string  # 在python交互解释器中查看 string 信息
'<!DOCTYPE html>\n<html>\n<head>\n\t<title>httpserver-cloud | basic</title>\n[...剩下的代码...]'

这段代码是一个示范,而且它是在 python 交互解释器中运行的。下面你就会看到我们将在代码文件中使用上面相同的获取网页源代码的代码。

早期版本 python 的 __item__ 实现可迭代

使用正则表达式(RE 库)将 string 按行分割

如果你了解正则表达式已经知道如何在 python 中使用正则表达式,那么这一小节当然可以跳过。

但是如果你之前没有接触过正则表达式,那么我将解释一下下面会用到的正则表达式。

完全介绍/解释正则表示式及其使用偏离了本文的主题。
并且由于博主目前使用频率不高,自认为不会比起其它专门讲解正则表达式的文章解释的更好,所以有兴趣的读者可以上网搜索正则表达式的文章学习。
本文只使用到一句正则表达式,理解了这句代码就足够对于阅读本文就不会有这个知识点上的障碍了。


  • 待补充

撰文要点笔记:导入库 -> 表达式符号含义 -> 对应的匹配 ->
.findall() 方法介绍 -> .findall() 返回值类型,继承关系 -> 如何使用返回值
直接转化返回值为列表对象 - 可迭代类型

实现 __item__ 得到可迭代对象

  • 只放代码,待补充细节!!!
import re
import reprlib

RE_LINE = re.compile('.+\\n')


class SuperOpen(object):
    def __init__(self, text):
        self.text = text
        self.words = RE_LINE.findall(text)

    def __getitem__(self, index):
        return self.words[index]

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

    def __repr__(self):
        return "SuperOpen(%s)" % reprlib.repr(self.text)


#
# Test/Usage
#
import requests

r = requests.get("http://localhost:8028/basic/")
string = r.text

tp = SuperOpen(string)
for line in tp:
    print(line, end="")

现代版本 python __iter__ 实现可迭代

import re
import reprlib

class SuperOpen(object):
    RE_LINE = re.compile('.+\\n')

    def __init__(self, text):
        self.text = text
        self.lines = self.RE_LINE.findall(text)

    def __iter__(self):
        return SuperOpenIterator(self.lines)

    def __repr__(self):
        return "SuperOpen(%s)" % reprlib.repr(self.text)


class SuperOpenIterator:

    def __init__(self, lines):
        self.lines = lines
        self.index = 0

    def __next__(self):
        try:
            line = self.lines[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return line

    def __iter__(self):
        return self

#
# Test/Usage
#
import requests

r = requests.get("http://localhost:8028/basic/")
string = r.text

tp = SuperOpen(string)
for line in tp:
    print(line, end="")

yield 版本的打开 string 可迭代

import re
import reprlib

class SuperOpen(object):
    RE_LINE = re.compile('.+\\n')
    def __init__(self, text):
        if __debug__:
            print("yield solution")
        self.text = text
        self.lines = self.RE_LINE.findall(text)

    def __repr__(self):
        return 'SuperOpen(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        for line in self.lines:
            yield line
        return
# 完成

#
# Test/Usage
#
if __name__ == "__main__":
    import requests

    r = requests.get("http://localhost:8028/basic/")
    string = r.text

    tp = SuperOpen(string)
    for line in tp:
        print(line, end="")

惰性实现 - 节省内存

import re
import reprlib

class SuperOpen(object):
    RE_LINE = re.compile('.+\\n')
    def __init__(self, text):
        if __debug__:
            print("lazy yield solution")
        self.text = text

    def __repr__(self):
        return 'SuperOpen(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        for match in self.RE_LINE.finditer(self.text):
            yield match.group()

#
# 测试代码同上
#

Reference

N/A

致谢

None for now.


  1. Test-Driven Development (测试驱动开发)。 ↩︎

  2. Django with TDD 对 load template 生成的 HTML 比较去除 CSRF 和空白符↩︎

  3. 非 Python 标准库的常用于获取网络上的网页源代码(网络数据/资源)的库。 ↩︎

猜你喜欢

转载自blog.csdn.net/qq_29757283/article/details/84982227
今日推荐