软件开发目录规范及正则表达式
一、模块的绝对导入和相对导入
在程序中如果涉及到多个文件之间导入模块的情况,一律按照执行文件所在的路径为准。
-
绝对导入:
始终按照执行文件所在的sys.path查找模块
-
相对导入:
? 能够打破始终以执行文件为准的规则,只考虑两个文件之间的位置。
句点符(.)在相对导入中的作用:
. 表示当前文件路径;.. 表示上一层文件路径;
注意:相对导入只能用在模块文件中,不能在执行文件中使用。

二、软件开发目录规范
为了提高程序的可读性与可维护性,我们应该为软件设计良好的目录结构,这与规范的编码风格同等重要。软件的目录规范并无硬性标准,只要清晰可读即可,假设你的软件名(项目名)为foo,笔者推荐目录结构如下:
Foo/
|-- bin/
| |-- ...
|
|-- core/
| |-- core.py
|
|-- api/
| |-- api.py
|
|-- db/
| |-- db_handle.py
|
|-- lib/
| |-- common.py
|
|-- conf/
| |-- settings.py
|
|-- log/
| |-- log.txt
|
|-- run.py
|-- setup.py
|-- requirements.txt
|-- README
简要解释一下:
- bin/:存放一系列启动文件(当启动文件很少或者只有一个时也可以写在外面—run.py)
- core/:存放业务逻辑相关核心代码文件;
- api/:存放接口文件,接口主要用于为业务逻辑提供数据操作;
- db/:存放操作数据库相关文件,主要用于与数据库交互;
- lib/:存放程序中公共的常用自定义功能模块;
- conf/:存放一系列配置文件;
- log/:存放日志记录文件;
- run.py:程序的启动文件,一般放在项目的根目录下,因为在运行时会默认将运行文件所在的文件夹作为sys.path的第一个路径,这样就省去了处理环境变量的步骤;
- setup.py:安装、部署、打包的脚本;
- requirements.txt:存放软件依赖的外部第三方模块及版本号;
- README:项目说明文件。
除此之外,有一些地方给出了更加多的内容,比如LICENSE.txt,ChangeLog.txt文件等,主要是在项目需要开源时才会用到,请读者自行查阅。
关于README的内容,这个应该是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。它需要说明以下几个事项:
1、软件定位,软件的基本功能;
2、运行代码的方法: 安装环境、启动命令等;
3、简要的使用说明;
4、代码目录结构说明,更详细点可以说明软件的基本原理;
5、常见问题说明。
关于setup.py和requirements.txt:
一般来说,用setup.py来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools来管理这些事情,这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。
requirements.txt文件的存在是为了方便开发者维护软件的依赖库。我们需要将开发过程中依赖库的信息添加进该文件中,避免在 setup.py安装依赖时漏掉软件包,同时也方便了使用者明确项目引用了哪些Python包。
这个文件的格式是每一行包含一个包依赖的说明,通常是flask>=0.10这种格式,要求是这个格式能被pip识别,这样就可以简单的通过 pip install -r requirements.txt来把所有Python依赖库都装好了,具体格式参照https://pip.readthedocs.io/en/1.1/requirements.html

三、正则表达式
3.1 什么是正则表达式?
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
- 正则表达式是一门独立的语言,跟其他编程语言没有什么关系,就是匹配字符串内容的一种规则;
- 如果想在python代码中使用正则表达式,需要借助于内置模块re;
3.2 为什么要用正则表达式?
典型的搜索和替换操作要求您提供与预期的搜索结果匹配的确切文本。虽然这种技术对于对静态文本执行简单搜索和替换任务可能已经足够了,但它缺乏灵活性,若采用这种方法搜索动态文本,即使不是不可能,至少也会变得很困难。
通过使用正则表达式,可以:
- 测试字符串内的模式。
例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。 - 替换文本。
可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。 - 基于模式匹配从字符串中提取子字符串。
可以查找文档内或输入域内特定的文本。
例如,您可能需要搜索整个网站,删除过时的材料,以及替换某些 HTML 格式标记。在这种情况下,可以使用正则表达式来确定在每个文件中是否出现该材料或该 HTML 格式标记。此过程将受影响的文件列表缩小到包含需要删除或更改的材料的那些文件。然后可以使用正则表达式来删除过时的材料。最后,可以使用正则表达式来搜索和替换标记。

3.3 正则表达式语法
3.3.1 字符组
字符组:[字符组]
在同一个位置可能出现的各种字符组成了一个字符组,在正则表达式中用[]表示
字符分为很多类,比如数字、字母、标点等等。
假如你现在要求一个位置"只能出现一个数字",那么这个位置上的字符只能是0、1、2...9这10个数之一。
正则 | 待匹配字符 | 匹配结果 | 说明 |
---|---|---|---|
[0123456789] | 8 | True | 在一个字符组里枚举合法的所有字符,字符组里的任意一个字符和"待匹配字符"相同都视为可以匹配 |
[0123456789] | a | False | 由于字符组中没有"a"字符,所以不能匹配 |
[0-9] | 7 | True | 也可以用-表示范围,[0-9]就和[0123456789]是一个意思 |
[a-z] | s | True | 同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示 |
[A-Z] | B | True | [A-Z]就表示所有的大写字母 |
字符串默认只能单个单个字符匹配
3.3.2 特殊字符
元字符 | 匹配内容 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
\b | 匹配一个单词的结尾 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结尾 |
\W | 匹配非字母或数字或下划线 |
\D | 匹配非数字 |
\S | 匹配非空白符 |
a|b | 匹配字符a或字符b |
() | 匹配括号内的表达式,也表示一个组 |
[...] | 匹配字符组中的字符 |
[^...] | 匹配除了字符组中字符的所有字符 |
特殊符号默认也只能单个单个字符匹配
3.3.3 量词
量词 | 用法说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |

3.4 正则表达式案例练习
3.4.1 . ^ $
正则 | 待匹配字符 | 匹配 结果 | 说明 |
---|---|---|---|
海. | 海燕海娇海东 | 海燕海娇海东 | 匹配所有"海."的字符 |
^海. | 海燕海娇海东 | 海燕 | 只从开头匹配"海." |
海.$ | 海燕海娇海东 | 海东 | 只匹配结尾的"海.$" |
3.4.2 * + ? { }
正则 | 待匹配字符 | 匹配 结果 | 说明 |
---|---|---|---|
李.? | 李杰和李莲英和李二棍子 | 李杰 李莲 李二 | ?表示重复零次或一次,即只匹配"李"后面一个任意字符 |
李.* | 李杰和李莲英和李二棍子 | 李杰和李莲英和李二棍子 | *表示重复零次或多次,即匹配"李"后面0或多个任意字符 |
李.+ | 李杰和李莲英和李二棍子 | 李杰和李莲英和李二棍子 | +表示重复一次或多次,即只匹配"李"后面1个或多个任意字符 |
李.{1,2} | 李杰和李莲英和李二棍子 | 李杰和 李莲英 李二棍 | {1,2}匹配1到2次任意字符 |
注意:前面的*, +, ?等都是贪婪匹配,也就是尽可能多的匹配,后面加?号可以使其变成惰性匹配:
正则 | 待匹配字符 | 匹配 结果 | 说明 |
---|---|---|---|
李.*? | 李杰和李莲英和李二棍子 | 李 李 李 | 惰性匹配 |
3.4.3 字符集[][^]
正则 | 待匹配字符 | 匹配 结果 | 说明 |
---|---|---|---|
李[杰莲英二棍子]* | 李杰和李莲英和李二棍子 | 李杰 李莲英 李二棍子 | 表示匹配"李"字后面[杰莲英二棍子]的字符任意次 |
李[ ^和]* | 李杰和李莲英和李二棍子 | 李杰 李莲英 李二棍子 | 表示匹配一个不是"和"的字符任意次 |
[\d] | 456bdha3 | 4 5 6 3 | 表示匹配任意一个数字,匹配到4个结果 |
[\d]+ | 456bdha3 | 456 3 | 表示匹配任意个数字,匹配到2个结果 |
3.4.4 分组 ()与 或 |[^]
身份证号码是一个长度为15或18个字符的字符串,如果是15位则全部由数字组成,首位不能为0;如果是18位,则前17位全部是数字,末位可能是数字或x,下面我们尝试用正则来表示:
正则 | 待匹配字符 | 匹配 结果 | 说明 |
---|---|---|---|
^ [1-9]\d{13,16}[0-9x]$ | 110101198001017032 | 110101198001017032 | 表示可以匹配一个正确的身份证号 |
^ [1-9]\d{13,16}[0-9x]$ | 1101011980010170 | 1101011980010170 | 表示也可以匹配这串数字,但这并不是一个正确的身份证号码,它是一个16位的数字 |
^ [1-9]\d{14}(\d{2}[0-9x])?$ | 1101011980010170 | False | 现在不会匹配错误的身份证号了()表示分组,将\d{2}[0-9x]分成一组,就可以整体约束他们出现的次数为0-1次 |
^([1-9]\d{16}[0-9x]|[1-9]\d{14})$ | 110105199812067023 | 110105199812067023 | 表示先匹配[1-9]\d{16}[0-9x]如果没有匹配上就匹配[1-9]\d{14} |

3.4.5 转义符 \
在正则表达式中,有很多有特殊意义的是元字符,比如\n和\s等,如果要在正则中匹配正常的"\n"而不是"换行符"就需要对" \ " 进行转义,变成 '\ \ '。
在Python中,无论是正则表达式,还是待匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义。所以如果匹配一次"\n",字符串中要写成'\ \n',那么正则里就要写成"\ \ \ \n",这样就太麻烦了。这个时候我们就用到了r'\n'这个概念,此时的正则是r'\ \n'就可以了。
正则 | 待匹配字符 | 匹配 结果 | 说明 |
---|---|---|---|
\n | \n | False | 因为在正则表达式中\是有特殊意义的字符,所以要匹配\n本身,用表达式\n无法匹配 |
\ \n | \n | True | 转义\之后变成\,即可匹配 |
"\ \ \ \n" | '\ \n' | True | 如果在Python中,字符串中的''也需要转义,所以每一个字符串''又需要转义一次 |
r'\ \n' | r'\n' | True | 在字符串之前加r,让整个字符串不转义 |
3.5 贪婪匹配与非贪婪匹配
贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配。
正则 | 待匹配字符 | 匹配 结果 | 说明 |
---|---|---|---|
<.*> | < script>...< script> | < script>...< script> | 默认为贪婪匹配模式,会匹配尽量长的字符串 |
<.*?> | < script>...< script> | < script> < script> | 加上?为将贪婪匹配模式转为非贪婪匹配模式,会匹配尽量短的字符串 |
几个常用的非贪婪匹配Pattern:
- *?:重复任意次,但尽可能少重复;
- +?:重复1次或更多次,但尽可能少重复;
- ??:重复0到1次,但尽可能少重复;
- {n, m}?:重复n到m次,但尽可能少重复;
- {n, }?:重复n次或以上,但尽可能少重复;
.*?的用法:
. 是任意字符
* 是取 0 至 无限长度
? 是非贪婪模式。
合在一起就是:取尽量少的任意字符,一般不会这么单独写,大多用在:
.*?x
就是取前面任意长度的字符,直到一个x出现
