
总览

项目结构
1 | README.rst |
核心代码在./sample/
,如果核心的文件只有一个,可以直接放在项目根目录下./sample.py
。
License: 开源许可,也可以不要开源许可发布。
docs:包参考文档。
tests: 单元测试。
Makefile:管理任务。
混乱的代码结构
多重混乱的循环依赖
furn.py
1 | from workers import Carpenter |
workers.py
1 | from furn import Table,Chair |
如果非要使用循环依赖,那只能使用不太好的方式来导入:在method和function中导入使用。
隐藏耦合
因为有太多关联,每次修改Table的实现,都需要小心翼翼,容易造成Carpenter的代码逻辑问题。
大量使用全局变量或上下文
不显示的传递,而使用大量的全局状态,如height, width, type, wood,这些全局状态容易被代理快速的修改了。一旦被莫名修改后,还需要仔细的检查,能够访问这些全局变量的地方(或者是远程的代码修改了这些全局的状态)。
面条式代码(Spaghetti code)
源代码的控制流程复杂,条件句和循环语句中互相嵌套,混乱而难以理解, 大量重复的代码,没有适当的分割,被视为套管程序。python的缩进特性使得它很难维护这样的代码,最好的方式就是不要写太多这样的代码。
Python中更可能出现混沌代码(Ravioli code)
这类代码包含上百段相似的逻辑碎片,通常是缺乏合适结构的类或对象,如果写代码时弄不清具体的逻辑,就可能出现混沌代码。
模块
Python模块是最主要的抽象层之一,抽象层允许将代码分为不同部分,每个部分包含相关的数据与功能。
例如在项目中,一层控制用户操作相关接口,另一层处理底层数据操作。
为了保持风格的一致,模块名称应该保持简短,小写,不要使用特殊字符等。不叫用.符号,影响python路径查找(my.spam.py的模块名称误让python以为要找my文件夹下的spam)。
不要使用下划线来组织命名空间,使用子模块更好:
1 | # OK |
python导入模块原理
import modu
会从当前文件夹寻找modu.py文件,如未找到,python解释器会从’path’中递归去寻找,仍然未找到,则抛出ImportError。
找到该模块,解释器会在隔离的范围执行该模块,任何顶级modu声明都会被执行,包括其他的imports,模块中的函数和类的定义存储在模块的字典中,在模块命名空间的调用者,可以直接调用模块中的变量,函数,和类。
在其他很多语言中,导入文件的逻辑是,解释器会将该文件的代码复制一份到调用的文件中,这与python是有很大的不同的。python中,导入的module在一个独立的命名空间中,这表示,不需要担心覆盖了当前同名的函数等。
不要使用*导入所有的模块:from modu import *
,使用import *
使代码难以阅读和且无法很好的区分依赖。使用 from modu import func
清晰的说明想导入具体的哪个模块,将其放入全局的命名空间中。
糟糕的导入方法:
1 | [...] |
好一些的导入方法:
1 | from modu import sqrt |
最好的示范:
1 | import modu |
packages
python提供了非常直观的包系统,即简单地将模块管理机制扩展到一个目录上。任何包含了__init__.py
文件的目录,组成了python的包。
import pack.modu
首先找到pach目录下的__init__.py
文件,执行所有顶层声明,然后再找到pack/modu.py
文件,执行其所有顶层声明。执行完这些所有的操作,modu.py
中定义的variable, function, class,在pack.modu的命名空间中都变成了可用状态。
一个常见的问题是__init__.py
添加太多的代码,当项目的结构越来越复杂,包含子包,子包有包含更深层次的包,当导入深层次中的包时,就需要执行很多的__init__.py
文件。
留空__init__.py
是最好的做法,如果子包或者更深层次的包不需要共享任何代码时。
当要导入嵌套的生层次的包时,可以给包命个别名,之后使用别名,不使用冗长的包名
1 | import very.deep.module as mod |
面向对象
In Python, everything is an object。
Functions, classes, strings,其它任意类型都是对象,他们有类型,可以作为参数传递,有方法,有属性。
选择编程范式:
- 使用面向对象:当有对象(windows, buttons, avatars)需要相对长的生命周期在计算机的内存中时
- 使用纯函数:
- 纯函数的结果是确定的:给定一个输入,输出总是固定相同。
- 当需要重构或优化时,纯函数更易于更改或替换。
- 纯函数更容易做单元测试:很少需要复杂的上下文配置和之后的数据清除工作。
- 纯函数更容易操作、修饰和分发。
装饰器
装饰器是一个函数或类,它可以 包装(或装饰)一个函数或方法。被’装饰’的函数或方法会替换原来的函数或方法。
原始方法写装饰器
1 | def foo(): |
使用@decorators语法更清晰
1 |
|
这个机制对于分离概念和避免外部不相关逻辑“污染”主要逻辑很有用处。
您需要在table中储存一个函数的结果,并且下次能直接使用该结果,而不是再计算一次。这显然不属于函数的逻辑部分。
Context Managers
为人熟知的示例
1 | with open('file.txt') as f: |
两种方式实现
使用class(处理简单操作的情况建议用这种)
1 | class CustomOpen(object): |
使用generator(封装的逻辑量很大建议用这种)
1 | from contextlib import contextmanager |
动态类型
python是动态类型语言,变量没有固定的类型。变量不是计算机内存中的一段,而是指向该类型对象的某个名称或者tag。
例如:’a’ 设置指向value 1, 随后设置指向 value ‘a string’, 随后设置指向一个function.
要避免同一个变量指向不同的东西!
Bad
1 | a = 1 |
Good
1 | count = 1 |
可变和不可变类型
可变类型:可变类型允许内容的内部修改,有对应使其变化的对象函数,如:lists、dictionaries
不可变类型:无对应使其变化的对象函数,无对应使其变化的对象函数:tuple、x=2(变量x指向2)
string也是不可变类型,要连接字符串有以下几种方法:
最差:使用+
操作符,效率最差
1 | nums = "" |
好:使用append方法
1 | nums = [] |
更好: 列表推导式,使用join
1 | nums = [str(n) for n in range(20)] |
使用 join() 并不总是最好的选择, 要分情况:
1 | foo = 'foo' |
最好:使用map
1 | nums = map(str, range(20)) |
还可以使用格式化字符串连接确定数量的字符串字符:
1 | foo = 'foo' |
- 本文标题:更好的组织代码
- 创建时间:2018-07-13 10:10:11
- 本文链接:2018/07/13/python/更好地组织项目/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!