Python学习路程day6

shelve 模块

shelve模块是一个简单的k,v将内存数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据格式

 1 import shelve
 2  
 3 d = shelve.open('shelve_test') #打开一个文件
 4  
 5 class Test(object):
 6     def __init__(self,n):
 7         self.n = n
 8  
 9  
10 t = Test(123) 
11 t2 = Test(123334)
12  
13 name = ["alex","rain","test"]
14 d["test"] = name #持久化列表
15 d["t1"] = t      #持久化类
16 d["t2"] = t2
17  
18 d.close()

xml处理模块

xml是实现不同语言或程序之间进行数据交换的协议,跟json差不多,但json使用起来更简单,不过,古时候,在json还没诞生的黑暗年代,大家只能选择用xml呀,至今很多传统公司如金融行业的很多系统的接口还主要是xml。

xml的格式如下,就是通过<>节点来区别数据结构的:

 1 "1.0"?>
 2 
 3     "Liechtenstein">
 4         "yes">2
 5         2008
 6         141100
 7         "Austria" direction="E"/>
 8         "Switzerland" direction="W"/>
 9     
10     "Singapore">
11         "yes">5
12         2011
13         59900
14         "Malaysia" direction="N"/>
15     
16     "Panama">
17         "yes">69
18         2011
19         13600
20         "Costa Rica" direction="W"/>
21         "Colombia" direction="E"/>
22     
23 

xml协议在各个语言里的都 是支持的,在python中可以用以下模块操作xml   

import xml.etree.ElementTree as ET
 
tree = ET.parse("test.xml")
root = tree.getroot()
print(root.tag)
 
#遍历xml文档
for child in root:
    print(child.tag, child.attrib)       #打印文件节点和文件属性
    for i in child:
        print(i.tag,i.text)
 
#只遍历year 节点
for node in root.iter('year'):      #iter相当于过滤的意思,把所有的year过滤出来
    print(node.tag,node.text)

修改和删除xml文档内容

 1 import xml.etree.ElementTree as ET
 2  
 3 tree = ET.parse("test.xml")
 4 root = tree.getroot()
 5  
 6 #修改
 7 for node in root.iter('year'):
 8     new_year = int(node.text) + 1
 9     node.text = str(new_year)
10     node.set("updated","yes")
11  
12 tree.write("test.xml")
13  
14  
15 #删除node
16 for country in root.findall('country'):
17    rank = int(country.find('rank').text)
18    if rank > 50:
19      root.remove(country)
20  
21 tree.write('output.xml')

自己创建xml文档

 1 import xml.etree.ElementTree as ET
 2  
 3  
 4 new_xml = ET.Element("namelist")
 5 name = ET.SubElement(new_xml,"name",attrib={"enrolled":"yes"})
 6 age = ET.SubElement(name,"age",attrib={"checked":"no"})
 7 sex = ET.SubElement(name,"sex")
 8 sex.text = '33'
 9 name2 = ET.SubElement(new_xml,"name",attrib={"enrolled":"no"})
10 age = ET.SubElement(name2,"age")
11 age.text = '19'
12  
13 et = ET.ElementTree(new_xml) #生成文档对象
14 et.write("test.xml", encoding="utf-8",xml_declaration=True)
15  
16 ET.dump(new_xml) #打印生成的格式
View Code

PyYAML模块

Python也可以很容易的处理ymal文档格式,只不过需要安装一个模块,参考文档:http://pyyaml.org/wiki/PyYAMLDocumentation 

ConfigParser模块

用于生成和修改常见配置文档,当前模块的名称在 python 3.x 版本中变更为 configparser。

来看一个好多软件的常见文档格式如下

 1 [DEFAULT]
 2 ServerAliveInterval = 45
 3 Compression = yes
 4 CompressionLevel = 9
 5 ForwardX11 = yes
 6  
 7 [bitbucket.org]
 8 User = hg
 9  
10 [topsecret.server.com]
11 Port = 50022
12 ForwardX11 = no

如果想用python生成一个这样的文档怎么做呢?

 1 import configparser
 2  
 3 config = configparser.ConfigParser()
 4 config["DEFAULT"] = {'ServerAliveInterval': '45',
 5                       'Compression': 'yes',
 6                      'CompressionLevel': '9'}
 7  
 8 config['bitbucket.org'] = {}
 9 config['bitbucket.org']['User'] = 'hg'
10 config['topsecret.server.com'] = {}
11 topsecret = config['topsecret.server.com']
12 topsecret['Host Port'] = '50022'     # mutates the parser
13 topsecret['ForwardX11'] = 'no'  # same here
14 config['DEFAULT']['ForwardX11'] = 'yes'
15 with open('example.ini', 'w') as configfile:
16    config.write(configfile)

写完了还可以再读出来哈。

 1 >>> import configparser
 2 >>> config = configparser.ConfigParser()
 3 >>> config.sections()
 4 []
 5 >>> config.read('example.ini')
 6 ['example.ini']
 7 >>> config.sections()
 8 ['bitbucket.org', 'topsecret.server.com']
 9 >>> 'bitbucket.org' in config
10 True
11 >>> 'bytebong.com' in config
12 False
13 >>> config['bitbucket.org']['User']
14 'hg'
15 >>> config['DEFAULT']['Compression']
16 'yes'
17 >>> topsecret = config['topsecret.server.com']
18 >>> topsecret['ForwardX11']
19 'no'
20 >>> topsecret['Port']
21 '50022'
22 >>> for key in config['bitbucket.org']: print(key)
23 ...
24 user
25 compressionlevel
26 serveraliveinterval
27 compression
28 forwardx11
29 >>> config['bitbucket.org']['ForwardX11']
30 'yes'

 configparser增删改查语法

[section1]
k1 = v1
k2:v2
  
[section2]
k1 = v1
 
import ConfigParser
  
config = ConfigParser.ConfigParser()
config.read('i.cfg')
  
# ########## 读 ##########
#secs = config.sections()
#print secs
#options = config.options('group2')
#print options
  
#item_list = config.items('group2')
#print item_list
  
#val = config.get('group1','key')
#val = config.getint('group1','key')
  
# ########## 改写 ##########
#sec = config.remove_section('group1')
#config.write(open('i.cfg', "w"))
  
#sec = config.has_section('wupeiqi')
#config.add_section('luolingfeng')
#config.write(open('i.cfg', "w"))
  
  
#config.set('group2','k1','11111')
#config.write(open('i.cfg', "w"))
  
#config.remove_option('group2','age')
#config.write(open('i.cfg', "w"))

hashlib模块  

用于加密相关的操作,3.x里代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法

 1 import hashlib
 2  
 3 m = hashlib.md5()
 4 m.update(b"Hello")
 5 m.update(b"It's me")
 6 print(m.digest())
 7 m.update(b"It's been a long time since last time we ...")
 8  
 9 print(m.digest()) #2进制格式hash
10 print(len(m.hexdigest())) #16进制格式hash
11 '''
12 def digest(self, *args, **kwargs): # real signature unknown
13     """ Return the digest value as a string of binary data. """
14     pass
15  
16 def hexdigest(self, *args, **kwargs): # real signature unknown
17     """ Return the digest value as a string of hexadecimal digits. """
18     pass
19  
20 '''
21 import hashlib
22  
23 # ######## md5 ########
24  
25 hash = hashlib.md5()
26 hash.update('admin')
27 print(hash.hexdigest())
28  
29 # ######## sha1 ########
30  
31 hash = hashlib.sha1()
32 hash.update('admin')
33 print(hash.hexdigest())
34  
35 # ######## sha256 ########
36  
37 hash = hashlib.sha256()
38 hash.update('admin')
39 print(hash.hexdigest())
40  
41  
42 # ######## sha384 ########
43  
44 hash = hashlib.sha384()
45 hash.update('admin')
46 print(hash.hexdigest())
47  
48 # ######## sha512 ########
49  
50 hash = hashlib.sha512()
51 hash.update('admin')
52 print(hash.hexdigest())
View Code

还不够吊?python 还有一个 hmac 模块,它内部对我们创建 key 和 内容 再进行处理然后再加密

1 import hmac
2 h = hmac.new('wueiqi')
3 h.update('hellowo')
4 print h.hexdigest()

更多关于md5,sha1,sha256等介绍的文章看这里

https://www.tbs-certificates.co.uk/FAQ/en/sha256.html 

 Subprocess模块 

The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several older modules and functions:

os.system
os.spawn*

The recommended approach to invoking subprocesses is to use the run() function for all use cases it can handle. For more advanced use cases, the underlying Popen interface can be used directly.

The run() function was added in Python 3.5; if you need to retain compatibility with older versions, see the Older high-level API section.

subprocess.run(args*stdin=Noneinput=Nonestdout=Nonestderr=Noneshell=Falsetimeout=Nonecheck=False)

Run the command described by args. Wait for command to complete, then return a CompletedProcess instance.

The arguments shown above are merely the most common ones, described below in Frequently Used Arguments (hence the use of keyword-only notation in the abbreviated signature). The full function signature is largely the same as that of the Popen constructor - apart from timeoutinput and check, all the arguments to this function are passed through to that interface.

This does not capture stdout or stderr by default. To do so, pass PIPE for the stdout and/or stderr arguments.

The timeout argument is passed to Popen.communicate(). If the timeout expires, the child process will be killed and waited for. The TimeoutExpired exception will be re-raised after the child process has terminated.

The input argument is passed to Popen.communicate() and thus to the subprocess’s stdin. If used it must be a byte sequence, or a string if universal_newlines=True. When used, the internal Popen object is automatically created withstdin=PIPE, and the stdin argument may not be used as well.

If check is True, and the process exits with a non-zero exit code, a CalledProcessError exception will be raised. Attributes of that exception hold the arguments, the exit code, and stdout and stderr if they were captured.

 1 >>> subprocess.run(["ls", "-l"])  # doesn't capture output
 2 CompletedProcess(args=['ls', '-l'], returncode=0)
 3  
 4 >>> subprocess.run("exit 1", shell=True, check=True)
 5 Traceback (most recent call last):
 6   ...
 7 subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
 8  
 9 >>> subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE)
10 CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
11 stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n')

调用subprocess.run(...)是推荐的常用方法,在大多数情况下能满足需求,但如果你可能需要进行一些复杂的与系统的交互的话,你还可以用subprocess.Popen(),语法如下:

1 p = subprocess.Popen("find / -size +1000000 -exec ls -shl {} \;",shell=True,stdout=subprocess.PIPE)
2 print(p.stdout.read())

可用参数:

    • args:shell命令,可以是字符串或者序列类型(如:list,元组)
    • bufsize:指定缓冲。0 无缓冲,1 行缓冲,其他 缓冲区大小,负值 系统缓冲
    • stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
    • preexec_fn:只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
    • close_sfs:在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。
      所以不能将close_fds设置为True同时重定向子进程的标准输入、输出与错误(stdin, stdout, stderr)。
    • shell:同上
    • cwd:用于设置子进程的当前目录
    • env:用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
    • universal_newlines:不同系统的换行符不同,True -> 同意使用 \n
    • startupinfo与createionflags只在windows下有效
      将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等

终端输入的命令分为两种:

  • 输入即可得到输出,如:ifconfig
  • 输入进行某环境,依赖再输入,如:python

需要交互的命令示例

 1 import subprocess
 2  
 3 obj = subprocess.Popen(["python"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 4 obj.stdin.write('print 1 \n ')
 5 obj.stdin.write('print 2 \n ')
 6 obj.stdin.write('print 3 \n ')
 7 obj.stdin.write('print 4 \n ')
 8  
 9 out_error_list = obj.communicate(timeout=10)
10 print out_error_list

logging模块  

很多程序都有记录日志的需求,并且日志中包含的信息即有正常的程序访问日志,还可能有错误、警告等信息输出,python的logging模块提供了标准的日志接口,你可以通过它存储各种格式的日志,logging的日志可以分为 debug()info()warning()error() and critical() 5个级别,下面我们看一下怎么用。

最简单用法

1 import logging
2  
3 logging.warning("user [alex] attempted wrong password more than 3 times")
4 logging.critical("server is down")
5  
6 #输出
7 WARNING:root:user [alex] attempted wrong password more than 3 times
8 CRITICAL:root:server is down

对于等级:

1 CRITICAL = 50
2 FATAL = CRITICAL
3 ERROR = 40
4 WARNING = 30
5 WARN = WARNING
6 INFO = 20
7 DEBUG = 10
8 NOTSET = 0

看一下这几个日志级别分别代表什么意思

Level When it’s used
DEBUG Detailed information, typically of interest only when diagnosing problems.
INFO Confirmation that things are working as expected.
WARNING An indication that something unexpected happened, or indicative of some problem in the near future (e.g. ‘disk space low’). The software is still working as expected.
ERROR Due to a more serious problem, the software has not been able to perform some function.
CRITICAL A serious error, indicating that the program itself may be unable to continue running.

  

如果想把日志写到文件里,也很简单

1 import logging
2  
3 logging.basicConfig(filename='example.log',level=logging.INFO)
4 logging.debug('This message should go to the log file')
5 logging.info('So should this')
6 logging.warning('And this, too')

其中下面这句中的level=loggin.INFO意思是,把日志纪录级别设置为INFO,也就是说,只有比日志是INFO或比INFO级别更高的日志才会被纪录到文件里,在这个例子, 第一条日志是不会被纪录的,如果希望纪录debug的日志,那把日志级别改成DEBUG就行了。

1 logging.basicConfig(filename='example.log',level=logging.INFO)

感觉上面的日志格式忘记加上时间啦,日志不知道时间怎么行呢,下面就来加上!

1 import logging
2 logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
3 logging.warning('is when this event was logged.')
4  
5 #输出
6 12/12/2010 11:46:36 AM is when this event was logged.

如果想同时把log打印在屏幕和文件日志里,就需要了解一点复杂的知识 了

The logging library takes a modular approach and offers several categories of components: loggers, handlers, filters, and formatters.

  • Loggers expose the interface that application code directly uses.
  • Handlers send the log records (created by loggers) to the appropriate destination.
  • Filters provide a finer grained facility for determining which log records to output.
  • Formatters specify the layout of log records in the final output.
 1 import logging
 2  
 3 #create logger
 4 logger = logging.getLogger('TEST-LOG')
 5 logger.setLevel(logging.DEBUG)
 6  
 7  
 8 # create console handler and set level to debug
 9 ch = logging.StreamHandler()
10 ch.setLevel(logging.DEBUG)
11  
12 # create file handler and set level to warning
13 fh = logging.FileHandler("access.log")
14 fh.setLevel(logging.WARNING)
15 # create formatter
16 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
17  
18 # add formatter to ch and fh
19 ch.setFormatter(formatter)
20 fh.setFormatter(formatter)
21  
22 # add ch and fh to logger
23 logger.addHandler(ch)
24 logger.addHandler(fh)
25  
26 # 'application' code
27 logger.debug('debug message')
28 logger.info('info message')
29 logger.warn('warn message')
30 logger.error('error message')
31 logger.critical('critical message')

 对于格式,有如下属性可是配置:

面向对象学习

面向对象编程( Object-Oriented Programming )介绍
 
对于编程语言的初学者来讲,OOP不是一个很容易理解的编程方式,大家虽然都按老师讲的都知道OOP的三大特性是继承、封装、多态,并且大家也都知道了如何定义类、方法等面向对象的常用语法,但是一到真正写程序的时候,还是很多人喜欢用函数式编程来写代码,特别是初学者,很容易陷入一个窘境就是“我知道面向对象,我也会写类,但我依然没发现在使用了面向对象后,对我们的程序开发效率或其它方面带来什么好处,因为我使用函数编程就可以减少重复代码并做到程序可扩展了,为啥子还用面向对象?”。 对于此,我个人觉得原因应该还是因为你没有充分了解到面向对象能带来的好处,今天我就写一篇关于面向对象的入门文章,希望能帮大家更好的理解和使用面向对象编程。
 
无论用什么形式来编程,我们都要明确记住以下原则:
  1. 写重复代码是非常不好的低级行为
  2. 你写的代码需要经常变更 

开发正规的程序跟那种写个运行一次就扔了的小脚本一个很大不同就是,你的代码总是需要不断的更改,不是修改bug就是添加新功能等,所以为了日后方便程序的修改及扩展,你写的代码一定要遵循易读、易改的原则(专业数据叫可读性好、易扩展)。

 
如果你把一段同样的代码复制、粘贴到了程序的多个地方以实现在程序的各个地方调用 这个功能,那日后你再对这个功能进行修改时,就需要把程序里多个地方都改一遍,这种写程序的方式是有问题的,因为如果你不小心漏掉了一个地方没改,那可能会导致整个程序的运行都 出问题。 因此我们知道 在开发中一定要努力避免写重复的代码,否则就相当于给自己再挖坑。
 
还好,函数的出现就能帮我们轻松的解决重复代码的问题,对于需要重复调用的功能,只需要把它写成一个函数,然后在程序的各个地方直接调用这个函数名就好了,并且当需要修改这个功能时,只需改函数代码,然后整个程序就都更新了。
 
其实OOP编程的主要作用也是使你的代码修改和扩展变的更容易,那么小白要问了,既然函数都能实现这个需求了,还要OOP干毛线用呢? 呵呵,说这话就像,古时候,人们打仗杀人都用刀,后来出来了枪,它的主要功能跟刀一样,也是杀人,然后小白就问,既然刀能杀人了,那还要枪干毛线,哈哈,显而易见,因为枪能更好更快更容易的杀人。函数编程与OOP的主要区别就是OOP可以使程序更加容易扩展和易更改。
 
小白说,我读书少,你别骗我,口说无凭,证明一下,好吧,那我们就下面的例子证明给小白看。 
相信大家都打过CS游戏吧,我们就自己开发一个简单版的CS来玩一玩。 
 
暂不考虑开发场地等复杂的东西,我们先从人物角色下手, 角色很简单,就俩个,恐怖份子、警察,他们除了角色不同,其它基本都 一样,每个人都有生命值、武器等。 咱们先用非OOP的方式写出游戏的基本角色 
 1 #role 1
 2 name = 'Alex'
 3 role = 'terrorist'
 4 weapon = 'AK47'
 5 life_value = 100
 6  
 7 #rolw 2
 8 name2 = 'Jack'
 9 role2 = 'police'
10 weapon2 = 'B22'
11 life_value2 = 100

上面定义了一个恐怖份子Alex和一个警察Jack,但只2个人不好玩呀,一干就死了,没意思,那我们再分别一个恐怖分子和警察吧,

 1 #role 1
 2 name = 'Alex'
 3 role = 'terrorist'
 4 weapon = 'AK47'
 5 life_value = 100
 6 money = 10000
 7  
 8 #rolw 2
 9 name2 = 'Jack'
10 role2 = 'police'
11 weapon2 = 'B22'
12 life_value2 = 100
13 money2 = 10000
14  
15 #role 3
16 name3 = 'Rain'
17 role3 = 'terrorist'
18 weapon3 = 'C33'
19 life_value3 = 100
20 money3 = 10000
21  
22 #rolw 4
23 name4 = 'Eric'
24 role4 = 'police'
25 weapon4 = 'B51'
26 life_value4 = 100
27 money4 = 10000

4个角色虽然创建好了,但是有个问题就是,每创建一个角色,我都要单独命名,name1,name2,name3,name4…,后面的调用的时候这个变量名你还都得记着,要是再让多加几个角色,估计调用时就很容易弄混啦,所以我们想一想,能否所有的角色的变量名都是一样的,但调用的时候又能区分开分别是谁? 

当然可以,我们只需要把上面的变量改成字典的格式就可以啦。 

 1 roles = {
 2     1:{'name':'Alex',
 3        'role':'terrorist',
 4        'weapon':'AK47',
 5        'life_value': 100,
 6        'money': 15000,
 7        },
 8     2:{'name':'Jack',
 9        'role':'police',
10        'weapon':'B22',
11        'life_value': 100,
12         'money': 15000,
13        },
14     3:{'name':'Rain',
15        'role':'terrorist',
16        'weapon':'C33',
17        'life_value': 100,
18        'money': 15000,
19        },
20     4:{'name':'Eirc',
21        'role':'police',
22        'weapon':'B51',
23        'life_value': 100,
24        'money': 15000,
25        },
26 }
27  
28 print(roles[1]) #Alex
29 print(roles[2]) #Jack
很好,这个以后调用这些角色时只需要roles[1],roles[2]就可以啦,角色的基本属性设计完了后,我们接下来为每个角色开发以下几个功能
  1. 被打中后就会掉血的功能
  2. 开枪功能
  3. 换子弹
  4. 买枪
  5. 跑、走、跳、下蹲等动作
  6. 保护人质(仅适用于警察)
  7. 不能杀同伴
  8. 。。。
我们可以把每个功能写成一个函数,类似如下:
 1 def shot(by_who):
 2     #开了枪后要减子弹数
 3     pass
 4 def got_shot(who):
 5     #中枪后要减血
 6     who[‘life_value’] -= 10
 7     pass
 8 def buy_gun(who,gun_name):
 9     #检查钱够不够,买了枪后要扣钱
10     pass
11 ...
so far so good, 继续按照这个思路设计,再完善一下代码,游戏的简单版就出来了,但是在往下走之前,我们来看看上面的这种代码写法有没有问题,至少从上面的代码设计中,我看到以下几点缺陷:
  1. 每个角色定义的属性名称是一样的,但这种命名规则是我们自己约定的,从程序上来讲,并没有进行属性合法性检测,也就是说role 1定义的代表武器的属性是weapon, role 2 ,3,4也是一样的,不过如果我在新增一个角色时不小心把weapon 写成了wepon , 这个程序本身是检测 不到的
  2. terrorist 和police这2个角色有些功能是不同的,比如police是不能杀人质的,但是terrorist可能,随着这个游戏开发的更复杂,我们会发现这2个角色后续有更多的不同之处, 但现在的这种写法,我们是没办法 把这2个角色适用的功能区分开来的,也就是说,每个角色都可以直接调用任意功能,没有任何限制。
  3. 我们在上面定义了got_shot()后要减血,也就是说减血这个动作是应该通过被击中这个事件来引起的,我们调用get_shot(),got_shot()这个函数再调用每个角色里的life_value变量来减血。 但其实我不通过got_shot(),直接调用角色roles[role_id][‘life_value’] 减血也可以呀,但是如果这样调用的话,那可以就是简单粗暴啦,因为减血之前其它还应该判断此角色是否穿了防弹衣等,如果穿了的话,伤害值肯定要减少,got_shot()函数里就做了这样的检测,你这里直接绕过的话,程序就乱了。 因此这里应该设计 成除了通过got_shot(),其它的方式是没有办法给角色减血的,不过在上面的程序设计里,是没有办法实现的。 
  4. 现在需要给所有角色添加一个可以穿防弹衣的功能,那很显然你得在每个角色里放一个属性来存储此角色是否穿 了防弹衣,那就要更改每个角色的代码,给添加一个新属性,这样太low了,不符合代码可复用的原则

上面这4点问题如果不解决,以后肯定会引出更大的坑,有同学说了,解决也不复杂呀,直接在每个功能调用时做一下角色判断啥就好了,没错,你要非得这么霸王硬上弓的搞也肯定是可以实现的,那你自己就开发相应的代码来对上面提到的问题进行处理好啦。 但这些问题其实能过OOP就可以很简单的解决。 

之前的代码改成用OOP中的“类”来实现的话如下: 

 1 class Role(object):
 2     def __init__(self,name,role,weapon,life_value=100,money=15000):
 3         self.name = name
 4         self.role = role
 5         self.weapon = weapon
 6         self.life_value = life_value
 7         self.money = money
 8  
 9     def shot(self):
10         print("shooting...")
11  
12     def got_shot(self):
13         print("ah...,I got shot...")
14  
15     def buy_gun(self,gun_name):
16         print("just bought %s" %gun_name)
17  
18 r1 = Role('Alex','police','AK47’) #生成一个角色
19 r2 = Role('Jack','terrorist','B22’)  #生成一个角色
先不考虑语法细节,相比函数式写法,上面用面向对象中的类来写最直接的改进有以下2点:
  1. 代码量少了近一半
  2. 角色和它所具有的功能可以一目了然看出来

接下来我们一起分解一下上面的代码分别 是什么意思 

1 class Role(object): #定义一个类, class是定义类的语法,Role是类名,(object)是新式类的写法,必须这样写,以后再讲为什么
2     def __init__(self,name,role,weapon,life_value=100,money=15000): #初始化函数,在生成一个角色时要初始化的一些属性就填写在这里
3         self.name = name #__init__中的第一个参数self,和这里的self都 是什么意思? 看下面解释
4         self.role = role
5         self.weapon = weapon
6         self.life_value = life_value
7         self.money = money

上面的这个__init__()叫做初始化方法(或构造方法), 在类被调用时,这个方法(虽然它是函数形式,但在类中就不叫函数了,叫方法)会自动执行,进行一些初始化的动作,所以我们这里写的__init__(self,name,role,weapon,life_value=100,money=15000)就是要在创建一个角色时给它设置这些属性,那么这第一个参数self是干毛用的呢? 

 

初始化一个角色,就需要调用这个类一次: 

r1 = Role('Alex','police','AK47’) #生成一个角色 , 会自动把参数传给Role下面的__init__(...)方法
r2 = Role('Jack','terrorist','B22’)  #生成一个角色

我们看到,上面的创建角色时,我们并没有给__init__传值,程序也没未报错,是因为,类在调用它自己的__init__(…)时自己帮你给self参数赋值了

1 r1 = Role('Alex','police','AK47’) #此时self 相当于 r1 ,  Role(r1,'Alex','police','AK47’)
2 r2 = Role('Jack','terrorist','B22’)#此时self 相当于 r2, Role(r2,'Jack','terrorist','B22’)
为什么这样子?你拉着我说你有些犹豫,怎么会这样子?
你执行r1 = Role('Alex','police','AK47 )时,python的解释器其实干了两件事:
  1. 在内存中开辟一块空间指向r1这个变量名
  2. 调用Role这个类并执行其中的__init__(…)方法,相当于Role.__init__(r1,'Alex','police',AK47’),这么做是为什么呢? 是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,重要的事情说3次, 因为关联起来后,你就可以直接r1.name, r1.weapon 这样来调用啦。所以,为实现这种关联,在调用__init__方法时,就必须把r1这个变量也传进去,否则__init__不知道要把那3个参数跟谁关联呀。
  3. 明白了么哥?所以这个__init__(…)方法里的,self.name = name , self.role = role 等等的意思就是要把这几个值 存到r1的内存空间里。
如果还不明白的话,哥,去测试一下智商吧, 应该不会超过70,哈哈。
为了暴露自己的智商,此时你假装懂了,但又问, __init__(…)我懂了,但后面的那几个函数,噢 不对,后面那几个方法 为什么也还需要self参数么? 不是在初始化角色的时候 ,就已经把角色的属性跟r1绑定好了么? 
good question, 先来看一下上面类中的一个buy_gun的方法: 
1 def buy_gun(self,gun_name):
2     print(“%s has just bought %s” %(self.name,gun_name) )

上面这个方法通过类调用的话要写成如下: 

1 r1 = Role('Alex','police','AK47')
2 r1.buy_gun("B21”) #python 会自动帮你转成 Role.buy_gun(r1,”B21")
执行结果
#Alex has just bought B21 
依然没给self传值 ,但Python还是会自动的帮你把r1 赋值给self这个参数, 为什么呢? 因为,你在buy_gun(..)方法中可能要访问r1的一些其它属性呀, 比如这里就访问 了r1的名字,怎么访问呢?你得告诉这个方法呀,于是就把r1传给了这个self参数,然后在buy_gun里调用 self.name 就相当于调用r1.name 啦,如果还想知道r1的生命值 有多少,直接写成self.life_value就可以了。 说白了就是在调用类中的一个方法时,你得告诉人家你是谁。
 
好啦, 总结一下2点:
  1. 上面的这个r1 = Role('Alex','police','AK47)动作,叫做类的“实例化”, 就是把一个虚拟的抽象的类,通过这个动作,变成了一个具体的对象了, 这个对象就叫做实例
  2. 刚才定义的这个类体现了面向对象的第一个基本特性,封装,其实就是使用构造方法将内容封装到某个具体对象中,然后通过对象直接或者self间接获取被封装的内容

面向对象的特性:

封装

封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

继承

面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

通过继承创建的新类称为“子类”或“派生类”。

被继承的类称为“基类”、“父类”或“超类”。

继承的过程,就是从一般到特殊的过程。

要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。

在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。

继承概念的实现方式有三类:实现继承、接口继承和可视继承。

Ø         实现继承是指使用基类的属性和方法而无需额外编码的能力;
Ø         接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
Ø         可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。

在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。

抽象类仅定义将由子类创建的一般属性和方法。

OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。

继承示例
 1 #!_*_coding:utf-8_*_
 2 #__author__:"Alex Li"
 3  
 4  
 5  
 6 class SchoolMember(object):
 7     members = 0 #初始学校人数为0
 8     def __init__(self,name,age):
 9         self.name = name
10         self.age = age
11  
12     def  tell(self):
13         pass
14  
15     def enroll(self):
16         '''注册'''
17         SchoolMember.members +=1
18         print("\033[32;1mnew member [%s] is enrolled,now there are [%s] members.\033[0m " %(self.name,SchoolMember.members))
19      
20     def __del__(self):
21         '''析构方法'''
22         print("\033[31;1mmember [%s] is dead!\033[0m" %self.name)
23 class Teacher(SchoolMember):
24     def __init__(self,name,age,course,salary):
25         super(Teacher,self).__init__(name,age)
26         self.course = course
27         self.salary = salary
28         self.enroll()
29  
30  
31     def teaching(self):
32         '''讲课方法'''
33         print("Teacher [%s] is teaching [%s] for class [%s]" %(self.name,self.course,'s12'))
34  
35     def tell(self):
36         '''自我介绍方法'''
37         msg = '''Hi, my name is [%s], works for [%s] as a [%s] teacher !''' %(self.name,'Oldboy', self.course)
38         print(msg)
39  
40 class Student(SchoolMember):
41     def __init__(self, name,age,grade,sid):
42         super(Student,self).__init__(name,age)
43         self.grade = grade
44         self.sid = sid
45         self.enroll()
46  
47  
48     def tell(self):
49         '''自我介绍方法'''
50         msg = '''Hi, my name is [%s], I'm studying [%s] in [%s]!''' %(self.name, self.grade,'Oldboy')
51         print(msg)
52  
53 if __name__ == '__main__':
54     t1 = Teacher("Alex",22,'Python',20000)
55     t2 = Teacher("TengLan",29,'Linux',3000)
56  
57     s1 = Student("Qinghua", 24,"Python S12",1483)
58     s2 = Student("SanJiang", 26,"Python S12",1484)
59  
60     t1.teaching()
61     t2.teaching()
62     t1.tell()

 

转载于:https://www.cnblogs.com/luolingfeng/p/5224503.html

你可能感兴趣的:(Python学习路程day6)