代码结构
什么是好的代码结构?
让人理解你说怎么,而不是你怎么说的。
简单的代码结构
README.rst
LICENSE
setup.py
requirements.txt
sample/__init__.py
sample/core.py
sample/helpers.py
docs/conf.py
docs/index.rst
tests/test_basic.py
tests/test_advanced.py
下面是详细解释
The Actual Module
Location | ./sample/ or ./sample.py |
---|---|
Purpose | The code of interest |
运行你程序的核心代码
License
Location | ./LICENSE |
---|---|
Purpose | Lawyering up. |
证书文字和版权信息,这部分为除了主代码之外最重要的代码
Setup.py
Location | ./setup.py |
---|---|
Purpose | Package and distribution management. |
版本管理
Requirements File
Location | ./requirements.txt |
---|---|
Purpose | Development dependencies. |
依赖包。注意,这里的不仅仅应该添加主代码中的依赖包,还需要添加测试和部署代码里面的依赖包。
Documentation
Location | ./docs/ |
---|---|
Purpose | Package reference documentation. |
解释文档
Test Suite
For advice on writing your tests, see Testing Your Code.
Location | ./test_sample.py or ./tests |
---|---|
Purpose | Package integration and unit tests. |
烂代码结构的特质
包循环依赖
这种循环依赖的话程序会在运行时一直循环调用。
隐藏耦合
在表里面任何一个列的名字改变,都会带来非常多的与那个表不相关的错误,这种问题需要非常细心地解决。
大量使用全局变量
全局变量会在任何地方被改动
意大利面代码
没有被正确分段的代码,阅读代码需要一片一片的读下去
混沌代码
项目里面包含非常多的小的逻辑代码片段,可能是类也可能是对象 你会很难知道FurnitureTable, AssetTable or Table, TableNew的区别是什么
模块
模块名
将模块名称保持为小写,小写,并确保避免使用点(。)或问号(?)等特殊符号。所以文件名my.spam.py
是你应该避免的!以这种方式命名将干扰Python查找模块的方式。
引用模块方式的比较
- 最烂
使用*来引用
from modu import *
[...]
x = sqrt(4) # Is sqrt part of modu? A builtin? Defined above?
你根本就不会知道sqrt是从哪里来的
- 稍微好一点
指明应用内容
[...]
x = sqrt(4) # sqrt may be part of modu, if not redefined in between
这种方式引用可能会被当前文件重写
- 最好
import modu
[...]
x = modu.sqrt(4) # sqrt is visibly part of modu's namespace
这种方式并不会增加内存损耗
包
任何带有__init__.py
文件的目录都被视为Python包。包中的不同模块以与普通模块类似的方式导入,但具有__init__.py
文件的特殊行为,用于收集所有包范围的定义。
执行顺序
在执行pacakge/module.py的代码之前,会先执行该模块下的init.py代码。
常见的问题
是向__init__.py
文件添加太多代码。当项目复杂性增加时,深层目录结构中可能存在子包和子子包。在这种情况下,从子子包导入单个项将需要__init__.py
在遍历树时执行所有 遇到的文件。
最佳建议
__init__.py
如果软件包的模块和子软件包不需要共享任何代码,则将文件保留为空是正常的,甚至是一种好的做法。
面向对象的编程
python的对象
首先,python的一切均为对象。
函数,类,字符串,甚至类本身都是对象。
Functions, classes, strings, and even types are objects in Python。
没有类强制
与Java不同,Python并没有将面向对象的编程强加为主要的编程范例,一个项目里面完全没有写类也是没有问题的。
模块分割带来的优势
python的模块引用机制提供了封装和分离,这是其他语言使用类的主要原因,而包就可以解决这个问题。
类的状态带来的问题
在某些体系结构(通常是Web应用程序)中,会生成多个Python进程实例,以响应可能同时发生的外部请求。在这种情况下,在实例化对象中保存一些状态,这意味着保留一些关于世界的静态信息,容易出现并发问题或竞争条件。有时,在对象状态的初始化之间(通常用__init__()
方法)和通过其中一种方法实际使用对象状态,外部环境可能已经改变,并且保留状态可能已经过时。例如,请求可以在内存中加载项目并将其标记为用户读取。如果另一个请求同时要求删除此项,则可能会在第一个进程加载项后实际发生删除,然后我们必须将读取标记为已删除的对象。这个问题和其他问题导致了使用无状态函数是更好的编程范例的原因。
使用逻辑或者纯函数的好处
纯函数是确定性的:给定固定输入,输出将始终相同。
如果需要重构或优化纯函数,则更容易更改或替换。
使用单元测试更容易测试纯函数:之后不再需要复杂的上下文设置和数据清理。
纯函数更易于操作,装饰和传递。
动态类型
Python是动态类型的,这意味着变量没有固定类型。事实上,在Python中,变量与许多其他语言中的变量非常不同,特别是静态类型的语言。变量不是计算机内存的一部分,其中写入了一些值,它们是指向对象的“标签”或“名称”。因此,变量'a'可以设置为值1,然后设置为值'a string',然后设置为函数。
可变和不可变类型
Python有两种内置或用户定义的类型。
可变类型是允许就地修改内容的类型。典型的可变列表是列表和词典:所有列表都有变异方法,如 list.append()
或list.pop()
,并且可以在适当的位置进行修改。词典也是如此。
不可变类型不提供改变其内容的方法。例如,设置为整数6的变量x没有“增量”方法。如果要计算x + 1,则必须创建另一个整数并为其指定名称。
my_list = [1, 2, 3]
my_list[0] = 4
print my_list # [4, 2, 3] <- The same list has changed
x = 6
x = x + 1 # The new x is another object
这种行为差异的一个后果是可变类型不是“稳定的”,因此不能用作字典键。