简明Python教程 10)编写 11)OO

27 篇文章 0 订阅

第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
 
=  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?' )
 
=  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)
 
=  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))
 
=  Teacher( 'Mrs. Shrividya' 40 30000 )
=  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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值