如何设置一个Python项目?
我正在做一些比较复杂的命令行操作(其实不是基于网页的),而且我刚开始接触Python,所以我想知道应该怎么安排我的文件和文件夹等等。有没有那种“头文件”,可以把所有数据库连接的内容放在里面?
我应该在哪里定义类和对象呢?
5 个回答
你可以根据自己应用的需求来组织代码,怎么方便怎么来。我不太清楚你具体在做什么,所以不能确定对你来说最好的组织方式是什么,但你可以随意拆分代码,只需要导入你需要的部分就行。
你可以在任何文件中定义类,而且在一个脚本里可以定义任意数量的类(这和Java不一样)。没有官方的头文件(不像C或C++那样),但你可以使用配置文件来存储连接数据库的信息等等,然后用configparser
(这是一个标准库的功能)来整理这些信息。
把相似的东西放在同一个文件里是个好主意,比如如果你有一个图形用户界面(GUI),你可以为界面创建一个文件;如果你有一个命令行界面(CLI),可以把它单独放在一个文件里。文件的组织方式不是最重要的,关键是你的代码是如何组织成类和函数的。
每个Python源文件都是一个模块。Python没有“头文件”。基本的想法是,当你导入“foo”时,它会加载“foo.py”中的代码(或者是之前编译过的版本)。然后你可以通过说foo.whatever来访问foo模块里的内容。
在Python代码中,似乎有两种组织方式。一些项目使用扁平布局,也就是所有模块都在顶层。另一些则使用层级结构。你可以通过导入“foo.bar.baz”来导入foo/bar/baz.py。使用层级布局时一个重要的注意事项是,在适当的目录下要有__init__.py
文件(这个文件可以是空的,但必须存在)。
类的定义方式如下:
class MyClass(object):
def __init__(self, x):
self.x = x
def printX(self):
print self.x
要创建一个实例:
z = MyObject(5)
这里给你一个典型的Python模块的例子,里面有一些解释。这是一个名为“Dims.py”的文件。这里不是整个文件,只是一些部分,让你了解里面的内容。
#!/usr/bin/env python
这行是标准的开头,告诉系统如何执行这个文件。用/usr/bin/env python
代替/usr/bin/python
,是让系统通过用户的PATH来找到Python;你想要的Python可能在~/bin
或者/usr/local/bin
里。
"""Library for dealing with lengths and locations."""
如果文件的第一行是一个字符串,那就是这个模块的文档字符串。文档字符串是紧跟在某个项目开始后面的字符串,可以通过它的__doc__
属性访问。在这个例子中,因为它是模块的文档字符串,如果用户用import Dims
导入这个文件,那么Dims.__doc__
就会返回这个字符串。
# Units
MM_BASIC = 1500000
MILS_BASIC = 38100
IN_BASIC = MILS_BASIC * 1000
有很多好的格式和命名规范的指导,记录在一个叫做PEP 8的文档里。这些是模块级别的变量(实际上是常量),所以它们用全大写字母和下划线来书写。其实我并不是完全遵循所有规则;老习惯很难改。既然你是新手,尽量遵循PEP 8,除非你实在做不到。
_SCALING = 1
_SCALES = {
mm_basic: MM_BASIC,
"mm": MM_BASIC,
mils_basic: MILS_BASIC,
"mil": MILS_BASIC,
"mils": MILS_BASIC,
"basic": 1,
1: 1
}
这些模块级别的变量名字前面有下划线。这给它们提供了一定的“隐私”,也就是说import Dims
不会让你访问Dims._SCALING
。不过,如果你需要使用它,可以明确地写import Dims._SCALING as scaling
。
def UnitsToScale(units=None):
"""Scales the given units to the current scaling."""
if units is None:
return _SCALING
elif units not in _SCALES:
raise ValueError("unrecognized units: '%s'." % units)
return _SCALES[units]
UnitsToScale
是一个模块级别的函数。注意文档字符串和默认值及异常的使用。在默认值声明中,=
周围没有空格。
class Length(object):
"""A length. Makes unit conversions easier.
The basic, mm, and mils properties can be used to get or set the length
in the desired units.
>>> x = Length(mils=1000)
>>> x.mils
1000.0
>>> x.mm
25.399999999999999
>>> x.basic
38100000L
>>> x.mils = 100
>>> x.mm
2.54
"""
这是一个类的声明。注意文档字符串里有一些看起来像Python命令行命令的东西。这些叫做文档测试,因为它们是在文档字符串里的测试代码。稍后会详细讲。
def __init__(self, unscaled=0, basic=None, mm=None, mils=None, units=None):
"""Constructs a Length.
Default contructor creates a length of 0.
>>> Length()
Length(basic=0)
Length(<float>) or Length(<string>) creates a length with the given
value at the current scale factor.
>>> Length(1500)
Length(basic=1500)
>>> Length("1500")
Length(basic=1500)
"""
# Straight copy
if isinstance(unscaled, Length):
self._x = unscaled._x
return
# rest omitted
这是初始化器。和C++不同,你只有一个初始化器,但你可以使用默认参数让它看起来像有多个构造函数。
def _GetBasic(self): return self._x
def _SetBasic(self, x): self._x = x
basic = property(_GetBasic, _SetBasic, doc="""
This returns the length in basic units.""")
这是一个属性。它允许你在使用和访问其他数据成员相同的语法时,拥有获取和设置的功能,比如myLength.basic = 10
和myLength._SetBasic(10)
是一样的。因为可以这样做,所以你不应该默认为数据成员写获取和设置函数。直接操作数据成员就可以了。如果以后需要获取和设置函数,可以把数据成员转换为属性,这样模块的用户就不需要改他们的代码。注意文档字符串是在属性上,而不是在获取和设置函数上。
如果你有一个只读的属性,可以用property
作为装饰器来声明它。例如,如果上面的属性是只读的,我会这样写:
@property
def basic(self):
"""This returns the length in basic units."""
return self._x
注意,属性的名字就是获取方法的名字。在Python 2.6或更高版本中,你也可以用装饰器来声明设置方法。
def __mul__(self, other):
"""Multiplies a Length by a scalar.
>>> Length(10)*10
Length(basic=100)
>>> 10*Length(10)
Length(basic=100)
"""
if type(other) not in _NumericTypes:
return NotImplemented
return Length(basic=self._x * other)
这段代码重载了*
运算符。注意,你可以返回特殊值NotImplemented
来告诉Python这个操作没有实现(在这种情况下,如果你尝试用非数字类型,比如字符串来相乘)。
__rmul__ = __mul__
因为代码和其他值一样,所以你可以把一个方法的代码赋值给另一个。这行代码告诉Python,something * Length
操作使用和Length * something
相同的代码。不要重复自己。
现在类声明完了,我可以回到模块代码。在这个例子中,我有一些代码只想在这个文件被直接执行时运行,而不是作为模块被导入。所以我使用以下测试:
if __name__ == "__main__":
然后if
里的代码只有在直接运行时才会执行。在这个文件里,我有以下代码:
import doctest
doctest.testmod()
这段代码会遍历模块中的所有文档字符串,查找看起来像Python提示符后面跟命令的行。后面的行被认为是命令的输出。如果命令输出了其他内容,测试就被认为失败,实际输出会被打印出来。想了解更多细节,可以阅读doctest模块的文档。
最后一点关于文档测试的说明:它们很有用,但不是最灵活或全面的测试方法。想要更全面的测试,你可以了解一下单元测试(unittest模块)。