第10章 解决问题--编写一个python脚本
结合所有内容编写一个脚本:
问题 要一个可以为所有重要文件创建备份的程序;
分析 哪些文件? 保存在哪? 怎样存储?
设计
1) 需要备份的文件和目录由一个列表指定;
2) 备份保存在主备份目录中;
3) 文件备份成zip文件;
4) zip存档的名称是当前的日期和时间;
5) 使用标准的zip命令, 通常默认由Linux提供; Windows用户可以使用Info-zip [或7z]; 可以使用任何的存档命令, 只需要有命令行界面就够了, 可以通过脚本中传递参数;
解决方案
Solution Implement:
version 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import
os
import
time
# 1. The files and directories to be backed up are specified in a list.
source
=
[
'/home/swaroop/byte'
,
'/home/swaroop/bin'
]
# If you are using Windows, use source = [r'C:\Documents', r'D:\Work'] or something like that
# 2. The backup must be stored in a main backup directory
target_dir
=
'/mnt/e/backup/'
# Remember to change this to what you will be using
# 3. The files are backed up into a zip file.
# 4. The name of the zip archive is the current date and time
target
=
target_dir
+
time.strftime(
'%Y%m%d%H%M%S'
)
+
'.zip'
# 5. We use the zip command (in Unix/Linux) to put the files in a zip archive
zip_command
=
"zip -qr '%s' %s"
%
(target,
' '
.join(source))
# Run the backup
if
os.system(zip_command)
=
=
0
:
print
(
'Successful backup to'
, target)
else
:
print
(
'Backup FAILED'
)
|
经过测试和调试, 消除程序中的错误bug;
>输入os和time模块, 在source列表中指定需要备份的文件和目录; zip归档的名称是当前的日期和时间: time.strftime(); strftime()需要使用定制: %Y会被无世纪的年份替代, m%会被01到12之间的一个十进制月份数替代, 其他依次类推; (详细内容见<Python参考手册>) 这些定制和print语句的定制(%后跟元组)类似;
>使用加法操作符来 级连 字符串, 把两个字符串连接在一起返回一个新字符串;
zip命令: -q表示安静工作; -r表示对目录递归; 可以组合成 -qr; zip命令形式: zip -option target source;
os.system函数运行命令, 相当于在系统中运行命令, Linux对应的是shell--运行成功返回0, 失败则返回错误号;
Note Windows用户: 可以把source列表和target目录设置成任何文件和目录名, 但是Windows把反斜杠 \ 作为目录分隔符, 而python用反斜杠表示转义符; 所以要使用转义符表示反斜杠或使用自然字符串, e.g. C\\Documents 或 r'C:\Documents'; 不能直接使用'C:\Document', 这样会引入未知的转义序列\D;
[在python的string前加上r, 告诉解释器这个string是个raw string, 不要转义反斜杠backslash- \ ]
[Windows]
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import
os
import
time
source
=
[
'C:\\log.txt'
]
#如果字符串中有空格,需要使用双引号
target_dir
=
'D:\\Temp\\'
target
=
target_dir
+
time.strftime(
'%Y%m%d%H%M%S'
)
+
'.zip'
zip_command
=
"7z a %s %s"
%
(target, ''.join(source))
print
(zip_command)
# Run the backup
if
os.system(zip_command)
=
=
0
:
print
(
'Successful backup to'
, target)
else
:
print
(
'Backup FAILED'
)
|
实施: 对于Linux用户, 可以把py文件作为可执行文件, 可以在任何时间地点运行备份;
version 2
维护 优化: 采用更好的文件名机制--使用时间作为文件名, 日期作为目录名; 这样备份会以等级结构存储, 容易管理, 文件名的长度也能缩短;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import
os
import
time
#The files and directories to be backed up are specified in a list.
source
=
[
'C:\\log.txt'
]
target_dir
=
'D:\\Temp\\'
#The current day is the name of the subdirectory in the main directory
today
=
target_dir
+
time.strftime(
'%Y%m%d'
)
now
=
time.strftime(
'%H%M%S'
)
# Create the subdirectory if it isn't already there
if
not
os.path.exists(today):
os.mkdir(today)
print
(
'Sucessfully created directory'
, today)
target
=
today
+
os.sep
+
now
+
'.zip'
zip_command
=
"7z a %s %s"
%
(target, ''.join(source))
print
(zip_command)
# Run the backup
if
os.system(zip_command)
=
=
0
:
print
(
'Successful backup to'
, target)
else
:
print
(
'Backup FAILED'
)
|
>os.exists函数检验在主备份目录中是否有以当前日期作为名称的目录; 使用os.mkdir创建目录;
>os.sep变量: 会根据操作系统给出目录分隔符, 在Linux下是'/'. Windows下是'\\', Mac OS下是':' ; 使用os.sep代替字符可以使得程序具备移植性;
version 3
version2在备份较多的时候还不错, 但是如果要区分每个备份是干什么的, 会十分困难; 要把改动历史与zip归档名称联系起来, 可以通过在zip归档名上附带用户注释来实现;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import
os
import
time
#The files and directories to be backed up are specified in a list.
source
=
[
'C:\\log.txt'
]
target_dir
=
'D:\\Temp\\'
#The current day is the name of the subdirectory in the main directory
today
=
target_dir
+
time.strftime(
'%Y%m%d'
)
now
=
time.strftime(
'%H%M%S'
)
# Take a comment from the user to create the name of the zip file
comment
=
input
(
'Enter a comment-->'
)
if
len
(comment)
=
=
0
:
target
=
today
+
os.sep
+
now
+
'.zip'
else
:
target
=
today
+
os.sep
+
now
+
'_'
+
comment.replace(
' '
,
'_'
)
+
'.zip'
# Create the subdirectory if it isn't already there
if
not
os.path.exists(today):
os.mkdir(today)
print
(
'Sucessfully created directory'
, today)
#zip_command = "zip -qr {0} {1}".format(target, ' '.join(source))
zip_command
=
"7z a %s %s"
%
(target, ''.join(source))
print
(zip_command)
# Run the backup
if
os.system(zip_command)
=
=
0
:
print
(
'Successful backup to'
, target)
else
:
print
(
'Backup FAILED'
)
|
>运行报错, 语法错误;
修订: 对于target = today + os.sep + now + '_' + 一行, 必须指定物理行尾的反斜杠 \ 来表示逻辑行在下一个物理行继续; 否则语法错误;
version 4
将报错的一行修改:
1
|
target
=
today
+
os.sep
+
now
+
'_'
+
\
|
>input[py2中是raw_input]函数得到用户的注释, 通过len得到输入的长度, 确认用户输入, 如果用户直接按了回车(惯例修改), 就按之前的流程继续操作; 如果提供了注释, 就附加到zip归档名, 把空格换成下划线, 有利于文件处理;
进一步优化
使用-v选项可以使程序更具交互性; 或者直接将文件和目录通过命令行传递给脚本: 通过sys.argv获取输入; list类提供了extend方法把输入加到source列表中;
使用tar代替zip, 这样在结合使用tar和gzip命令的时候, 备份更快更小; 对于Windows, Winzip也能处理.tar.gz文件; tar在大多数Linux中是默认的; Windwos用户也能使用: http://gnuwin32.sourceforge.net/
1
|
tar
=
'tar -cvzf %s %s -X /home/swaroop/excludes.txt'
%
(target,
' '
.join(srcdir))
|
> -c 表示创建一个归档; -v 表示交互; -z 表四使用gzip滤波器; -f 表示强制创建归档, 如果有同名文件, 就替换; -X 表示旱灾指定文件名列表中的文件会被排除在备份之外; e.g. 在文件中指定*~, 将不会备份包括所有以~结尾的文件;
Note 理想的创建归档的方法是分别使用zipfile和tarfile; 他们是py标准库的一部分; 使用这些库可以避免使用os.system函数(不推荐使用的函数, 容易引发严重错误);
软件开发过程
1) 什么(分析) 2) 如何(设计) 3) 编写(实施) 4) 测试(测试和调试) 5) 使用(实施或开发) 6) 维护(优化)
Note 创建这个备份脚本的过程是编写程序的推荐方法--进行分析和设计; 从简单版本开始, 经过测试调试, 早增加特性, 根据需要重复 编写-测试-使用的周期; 软件是grow的, 不是build的;
第11章 面向对象的编程
简介
目前为止, 例子中都是根据操作数据的函数或语句块来设计程序, 这称为 面向过程的 编程; 还有一种把数据和功能结合起来, 用对象包裹组织程序的方法, 称为 面向对象的 编程; 大多数时候可以使用过程性编程, 但是当编写大型程序或寻求更合适的解决方案的时候, 就需要使用面向对象的编程;
两个主要方面: 类 创建一个类型; 对象 是类的实例; e.g. int类型变量, 这个存储整数的变量是int类的实例;
Note To 静态语言程序员 整数也被作为对象(int类); 和C++/Java把整数纯粹作为类型[原生数据类型]是不同的; 参见help(int); C#和Java1.5中类似 封装和解封装 的概念;
对象可以使用普通的属于对象的变量存储数据; 属于一个对象或类的变量被称为域/字段; 对象也可以使用属于类的函数来具有功能, 这样的函数被称为类的方法; 域和方法可以合称为类的属性;
域有两种--属于每个实例/对象的, 属于类本身的; 分别称为实例变量和类变量; [static的概念]
类使用class关键字创建, 类的域和方法被列在一个缩进块中;
self
类的方法与普通的函数有一个特别的区别: 必须有一个额外的第一个参数名称; 但是在调用这个方法的时候不用为这个参数赋值, python会提供这个值; 这个特别的变量指对象本身, 按照惯例他的名称是self;
虽然这个参数可以给任何名称, 但是强烈建议使用self; 使用标准名称可以迅速识别, 有些IDE也能帮助识别;
Note To C++/Java/C# python中的self等价于this指针/引用;
Python如何给self赋值, e.g. MyClass类实例MyObject, 当调用MyObject.method(arg1, arg2)的时候, 会由python自动转为MyClass.method(MyObject, arg1, arg2), 这就是self的原理; 这意味着如果你有一个不需要参数的方法, 还是要给这个方法定义一个self参数;
类
1
2
3
4
5
|
class
Person:
pass
# An empty block
p
=
Person()
print
(p)
|
>class加上类名来创建类; 缩进的语句块形成类体; pass表示空白块;
>类名加上圆括号创建对象/实例; 打印对象输出显示: __main__.Person instance at 0xf6fcb18c: 在main模块中有一个Person的实例; 存储对象的内存地址也会打印出来;
对象的方法
和普通函数/方法的区别是由一个额外的self变量/参数;
1
2
3
4
5
6
7
|
class
Person:
def
sayHi(
self
):
print
(
'Hello, how are you?'
)
p
=
Person()
p.sayHi()
# This short example can also be written as Person().sayHi()
|
>sayHi没有参数, 但仍然要在函数定义时有self;
__init__方法
__init_-方法在类的一个对象被建立/初始化时, 马上运行, 这个方法可以用来对对象做 初始化;
1
2
3
4
5
6
7
8
9
|
class
Person:
def
__init__(
self
, name):
self
.name
=
name
def
sayHi(
self
):
print
(
'嗨,我的名字是'
,
self
.name)
p
=
Person(
'Swaroop'
)
p.sayHi()
# 这个小程序也可以写成 Person('Swaroop').sayHi()
|
>__init__方法定义一个参数name, 创建一个新的域称为name; 用self.name可以区分它们: 对象的域vs局部变量;
我们没有显式地调用__init__方法, 而是在创建实例的时候, 把参数传递给类;
Note To C++/Java/C# __init__方法类似构造constructor;
类与对象的变量
类的数据, 是与类和对象的名称空间绑定的变量;
两种类型: 类的变量和对象的变量;
类变量由类的所有对象/实例共享使用; 类变量只有一份, 所以当某个对象对类变量做了改动, 所有的实例都会随之改变;
对象的变量由每个实例拥有; 每个对象都有一份拷贝, 不共享; 在同一个类的不同实例中, 即使对象的变量名称相同, 也是相互没有关系的;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
class
Robot:
'''表示人一机器人,有一个名字。'''
# 一个类变量,数机器人的数量
population
=
0
def
__init__(
self
, name):
'''初始化数据。'''
self
.name
=
name
print
(
'(初始化 {0})'
.
format
(
self
.name))
# 当创建一个人时,机器人
# 人口加1
Robot.population
+
=
1
def
__del__(
self
):
'''我将要死了。'''
print
(
'{0} 正在被毁!'
.
format
(
self
.name))
Robot.population
-
=
1
if
Robot.population
=
=
0
:
print
(
'{0}是最后一个。'
.
format
(
self
.name))
else
:
print
(
'还有{0:d}机器人在工作。'
.
format
(Robot.population))
def
sayHi(
self
):
'''机器人问候。
是的,它们能做作那个。'''
print
(
'你好,我的主人叫我'
.
format
(
self
.name))
def
howMany():
'''打印当前人口。'''
print
(
'我们有{0:d}个机器人。'
.
format
(Robot.population))
howMany
=
staticmethod
(howMany)
droid1
=
Robot(
'R2-D2'
)
droid1.sayHi()
Robot.howMany()
droid2
=
Robot(
'C-3PO'
)
droid2.sayHi()
Robot.howMany()
print
(
"\n机器人在这能做一些工作。\n"
)
print
(
"机器人已经完成了它们的工作,因此,让我们销毁它们。"
)
del
droid1
del
droid2
Robot.howMany()
|
>population属于Robot, 是类的变量; name属于对象(self赋值), 是对象的变量; 因此使用的时候是 Robot.populaction, self.name;
Note 一个对象变量与一个类变量名字相同时, 类变量将被隐藏;
>howMany是一个类方法, 不是对象方法, 我们可以将其定义成classmethod或staticmethod, 这取决于是否知道是哪个类;
Note 可以使用decorator(http://www.ibm.com/developerworks/linux/library/l-cpdecor.html)达到同样的目的:
1
2
3
4
|
@staticmethod
def
howMany():
'''打印当前人口。'''
print
(
'我们有{0:d}个机器人。'
.
format
(Robot.population))
|
[类似Java]
修饰符可以被想象成一个调用显式声明的快捷方式;
>__init__方法使用一个名字初始化Robot实例; self.name的值是针对每一个对象的, 这是对象变量的特性;
Note 你必须使用self引用同一对象的变量和方法--属性引用;
>文档字符串docstring: 可以通过Robot.__doc__访问类的文档字符串, 通过Robot.sayHi.__doc__访问方法的文档字符串;
>和__init__方法类似, 有一个特殊的方法__del__, 当对象销毁时调用; 对象销毁则对象不再被使用, 并且释放它所占用的计算机内存;
Note 当对象不再被使用时, __del__方法会运行; 但是很难保证这个方法在确定的时间运行; 因此你想要明确地释放对象, 就要显式地调用del;
Note To C++/Java/C# python中所有的类成员(包括数据成员)都是公共的, 所有方法都是虚拟virtual的;
只有一个例外, 如果你使用的数据成员名称以双下划线前缀, 例如__privatevar, python的名称管理体系会把它作为私有变量;
命名惯例: 如果某个变量指向在类或对象内使用, 用单下划线前缀, 其他的名称作为公共的, 可以被其他类/对象使用; 这只是作为惯例, 和python强制执行的双下划线前缀不同;
__del__类似析构函数destructor;
继承
面向对象编程带来的好处之一是代码重用; 实现重用的方法之一就是继承; 子类类型可以被替换成父类, 子类对象可以被视为父类的实例--多态;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
class
SchoolMember:
'''代表任何学校成员。'''
def
__init__(
self
, name, age):
self
.name
=
name
self
.age
=
age
print
(
'(初始化学校成员: {0})'
.
format
(
self
.name))
def
tell(
self
):
'''告诉我细节。'''
print
(
'Name:"{0}" Age:"{1}"'
.
format
(
self
.name,
self
.age), end
=
" "
)
class
Teacher(SchoolMember):
'''代表老师。'''
def
__init__(
self
, name, age, salary):
SchoolMember.__init__(
self
, name, age)
self
.salary
=
salary
print
(
'(初始化老师: {0})'
.
format
(
self
.name))
def
tell(
self
):
SchoolMember.tell(
self
)
print
(
'Salary: "{0:d}"'
.
format
(
self
.salary))
class
Student(SchoolMember):
'''代表学生。'''
def
__init__(
self
, name, age, marks):
SchoolMember.__init__(
self
, name, age)
self
.marks
=
marks
print
(
'(初始化学生: {0})'
.
format
(
self
.name))
def
tell(
self
):
SchoolMember.tell(
self
)
print
(
'Marks: "{0:d}"'
.
format
(
self
.marks))
t
=
Teacher(
'Mrs. Shrividya'
,
40
,
30000
)
s
=
Student(
'Swaroop'
,
25
,
75
)
print
()
# 打印一个空行
members
=
[t, s]
for
member
in
members:
member.tell()
# Teachers和Students都可以
|
>使用继承, 在类定义中, 类的名称后的元组中指定基类名称, 显式地调用基类的__init__方法, 初始化基类;
Note python不会自动调用基类的构造函数, 必须显式地调用它;
>在方法之前调用类前缀, 用来调用基类方法; 可以将参数和self传递给它;
>子类调用了tell()方法, 而不是基类; python总是首先查找对应类型的方法, 如果在子类中找不到, 才会在类定义的元组中按顺序逐个在基类中查找;
Note 如果在继承元组中列出一个以上的类, 称为多重继承;
>tell()方法中的end参数是用来将换行变为在print()调用结束后的空格;
---TBC---YC