python初学者

python工程结构

2020-01-21  本文已影响0人  寇寇寇先森

在一个健康的开发周期中,代码风格,API设计和自动化是非常关键的。同样的,对于工程的架构 ,仓库的结构也是关键的一部分。
当一个潜在的用户和贡献者登录到您的仓库页面时,他们会看到这些:

如果您的仓库的目录是一团糟,没有清晰的结构,他们可能要到处寻找才能找到您写的漂亮的文档。

仓库样例

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

以下是一些细节介绍:

tests/test_basic.py
tests/test_advanced.py

当然,这些测试例子需要导入我们的包来进行测试,有几种方式来处理:
1.将我们的包安装到site-packages中。
2.通过简单直接的路径设置来解决导入的问题。
推荐后者。如果使用 setup.py develop 来测试一个持续更新的代码库,需要为每一个版本的代码库设置一个独立的测试环境.太麻烦了。
可以先创建一个包含上下文环境的文件 tests/context.py。 file:

import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

import sample

然后,在每一个测试文件中,导入:
from .context import sample
这样就能够像期待的那样工作,而不用采用安装的方式。

init:
    pip install -r requirements.txt
test:
    py.test tests
PHONY: init test

一些其他的常规管理脚本(比如 manage.py 或者 fabfile.py),也放在仓库的根目录下.

结构是一把钥匙

得益于Python提供的导入与管理模块的方式,结构化Python项目变得相对简单。 这里说的简单,指的是结构化过程没有太多约束限制而且模块导入功能容易掌握。 因而您只剩下架构性的工作,包括设计、实现项目各个模块,并整理清他们之间 的交互关系。

模块

Python模块是最主要的抽象层之一,并且很可能是最自然的一个。抽象层允许将代码分为 不同部分,每个部分包含相关的数据与功能。请尽量保持模块名称简单,以无需分开单词。 最重要的是,不要使用下划线命名空间,而是使用子模块。

# ok
import library.plugin.foo
# not OK
import library.foo_plugin

from modu import *
x = sqrt(4)  # sqrt是模块modu的一部分么?或是内建函数么?上文定义了么?

稍好

from modu import sqrt
x = sqrt(4)  # 如果在import语句与这条语句之间,sqrt没有被重复定义,它也许是模块modu的一部分。

最好

import modu
[...]
x = modu.sqrt(4)  # sqrt显然是属于模块modu的。

除了简单的单文件项目外,其他项目需要能够明确指出类和方法 的出处,例如使用 modu.func 语句,这将显著提升代码的可读性和易理解性。

Python提供非常简单的包管理系统,即简单地将模块管理机制扩展到一个目录上(目录扩展为包)。
任意包含 init.py 文件的目录都被认为是一个Python包。导入一个包里不同模块的方式和普通的导入模块方式相似,特别的地方是 init.py 文件将集合所有包范围内的定义。

pack/目录下的modu.py文件通过 import pack.modu语句导入。 该语句会在 pack 目录下寻找 init.py 文件,并执行其中所有顶层语句。以上操作之后,modu.py 内定义的所有变量、方法和类在pack.modu命名空间中均可看到。

一个常见的问题是往 init.py 中加了过多代码,随着项目的复杂度增长, 目录结构越来越深,子包和更深嵌套的子包可能会出现。在这种情况下,导入多层嵌套 的子包中的某个部件需要执行所有通过路径里碰到的 init.py文件。如果包内的模块和子包没有代码共享的需求,使用空白的 init.py 文件是正常甚至好的做法。

最后,导入深层嵌套的包可用这个方便的语法:import very.deep.module as mod。 该语法允许使用 mod 替代冗长的 very.deep.module。

面向对象编程

在Python中一切都是对象,并且能按对象的方式处理。这么说的意思是,例如函数是一等对象。 函数、类、字符串乃至类型都是Python对象:与其他对象一样,他们有类型,能作为函数参数传递,并且还可能有自己的方法和属性。这样理解的话,Python是一种面向对象语言。
然而,与Java不同的是,Python并没有将面向对象编程作为最主要的编程范式。非面向对象的Python项目(比如,使用较少甚至不使用类定义,类继承,或其它面向对象编程的机制)也是完全可行的。
在一些情况下,需要避免不必要的面向对象。当我们想要将状态与功能结合起来,使用标准类定义是有效的。但正如函数式编程所讨论的那个问题,函数式的“变量”状态与类的状态并不相同。

动态类型

Python是动态类型语言,这意味着变量并没有固定的类型。实际上,Python中的变量和其他语言有很大的不同,特别是静态类型语言。变量并不是计算机内存中被写入的某个值,它们只是指向内存的 ‘标签’ 或 ‘名称’ 。因此可能存在这样的情况,变量 'a' 先代表值1,然后变成字符串'a string' , 然后又变为指向一个函数。

Python 的动态类型常被认为是它的缺点,的确这个特性会导致复杂度提升和难以调试的代码。 命名为 'a' 的变量可能是各种类型,开发人员或维护人员需要在代码中追踪命名,以保证它 没有被设置到毫不相关的对象上。

避免发生类似问题的参考方法:

a = 1
a = 'a string'
def a():
    pass  # 实现代码

count = 1
msg = 'a string'
def func():
    pass  # 实现代码

使用简短的函数或方法能降低对不相关对象使用同一个名称的风险。即使是相关的不同 类型的对象,也更建议使用不同命名

重复使用命名对效率并没有提升:赋值时无论如何都要创建新的对象。然而随着复杂度的 提升,赋值语句被其他代码包括 'if' 分支和循环分开,使得更难查明指定变量的类型。 在某些代码的做法中,例如函数编程,推荐的是从不重复对同一个变量命名赋值。Java 内的实现方式是使用 'final' 关键字。Python并没有 'final' 关键字。尽管如此,避免给同一个变量命名重复赋值仍是是个好的做法,并且有助于掌握 可变与不可变类型的概念。

可变和不可变类型

Python提供两种内置或用户定义的类型。可变类型允许内容的内部修改。典型的动态类型 包括列表与字典:列表都有可变方法,如 list.append() 和 list.pop(), 并且能就地修改。字典也是一样。不可变类型没有修改自身内容的方法。比如,赋值为整数 6的变量 x 并没有 "自增" 方法,如果需要计算 x + 1,必须创建另一个整数变量并给其命名。

my_list = [1, 2, 3]
my_list[0] = 4
print my_list  # [4, 2, 3] <- 原列表改变了

x = 6
x = x + 1  # x 变量是一个新的变量

这种差异导致的一个后果就是,可变类型是不 '稳定 '的,因而不能作为字典的键使用。合理地 使用可变类型与不可变类型有助于阐明代码的意图。例如与列表相似的不可变类型是元组, 创建方式为 (1, 2)。元组是不可修改的,并能作为字典的键使用。

Python 中一个可能会让初学者惊讶的特性是:字符串是不可变类型。这意味着当需要组合一个字符串时,将每一部分放到一个可变列表里,使用字符串时再组合 ('join') 起来的做法更高效。 而且,使用列表推导的构造方式比在循环中调用append()来构造列表更好也更快。

# 创建将0到19连接起来的字符串 (例 "012..1819")
nums = ""
for n in range(20):
    nums += str(n)   # 慢且低效
print nums

# 创建将0到19连接起来的字符串 (例 "012..1819")
nums = []
for n in range(20):
    nums.append(str(n))
print "".join(nums)  # 更高效

更好

# 创建将0到19连接起来的字符串 (例 "012..1819")
nums = [str(n) for n in range(20)]
print "".join(nums)

最好Best

# 创建将0到19连接起来的字符串 (例 "012..1819")
nums = map(str, range(20))
print "".join(nums)

除了 str.join() 和 +,也可以使用 % 格式运算符来连接确定数量的字符串,不过PEP 3101 建议使用 str.format() 替代 % 操作符。

foo = 'foo'
bar = 'bar'

foobar = '%s%s' % (foo, bar) # 可行
foobar = '{0}{1}'.format(foo, bar) # 更好
foobar = '{foo}{bar}'.format(foo=foo, bar=bar) # 最好
上一篇 下一篇

猜你喜欢

热点阅读