From 79acfab8f089cf83aa0adf7671f868829abc03ed Mon Sep 17 00:00:00 2001 From: zwb <1465302821@qq.com> Date: Thu, 26 Dec 2024 10:00:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=20/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 11-Python日期时间.md | 232 ++++ 12-Python正则表达.md | 480 ++++++++ 13-Python异常处理.md | 445 ++++++++ 14-Python面向对象.md | 2548 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 3705 insertions(+) create mode 100644 11-Python日期时间.md create mode 100644 12-Python正则表达.md create mode 100644 13-Python异常处理.md create mode 100644 14-Python面向对象.md diff --git a/11-Python日期时间.md b/11-Python日期时间.md new file mode 100644 index 0000000..acadcaf --- /dev/null +++ b/11-Python日期时间.md @@ -0,0 +1,232 @@ +# 11-Python日期时间 + +time 和 calendar 模块可以用于格式化日期和时间。 +时间间隔是以秒为单位的浮点小数。 +每个时间戳都以自从1970年1月1日午夜(历元)经过了多长时间来表示。 + +例: + \#!/usr/bin/python + import time; # 引入time模块 + ticks = time.time() + print "当前时间戳为:", ticks + +​ 输出结果: +​ 当前时间戳为: 1459994552.51 +​ 时间戳单位最适于做日期运算。但是1970年之前的日期就无法以此表示了。太遥远的日期也不行,UNIX和Windows只支持到2038年。 + +**什么是时间元组?** +很多Python函数用一个元组装起来的9组数字处理时间: + +| 序号 | 字段 | 值 | +| ---- | ------------ | ------------------------------------ | +| 0 | 4位数年 | 2008 | +| 1 | 月 | 1 到 12 | +| 2 | 日 | 1到31 | +| 3 | 小时 | 0到23 | +| 4 | 分钟 | 0到59 | +| 5 | 秒 | 0到61 (60或61 是闰秒) | +| 6 | 一周的第几日 | 0到6 (0是周一) | +| 7 | 一年的第几日 | 1到366 (儒略历) | +| 8 | 夏令时 | -1, 0, 1, -1是决定是否为夏令时的旗帜 | + + +上述也就是struct_time元组。这种结构具有如下属性: + +| 序号 | 属性 | 值 | +| ---- | -------- | ------------------------------------ | +| 0 | tm_year | 2008 | +| 1 | tm_mon | 1 到 12 | +| 2 | tm_mday | 1 到 31 | +| 3 | tm_hour | 0 到 23 | +| 4 | tm_min | 0 到 59 | +| 5 | tm_sec | 0 到 61 (60或61 是闰秒) | +| 6 | tm_wday | 0到6 (0是周一) | +| 7 | tm_yday | 1 到 366(儒略历) | +| 8 | tm_isdst | -1, 0, 1, -1是决定是否为夏令时的旗帜 | + + + + +**获取当前时间** +从返回浮点数的时间辍方式向时间元组转换,只要将浮点数传递给如localtime之类的函数。 +\#!/usr/bin/python +import time +localtime = time.localtime(time.time()) +print "本地时间为 :", localtime + +输出结果: +本地时间为 : time.struct_time(tm_year=2016, tm_mon=4, tm_mday=7, tm_hour=10, tm_min=3, tm_sec=27, +tm_wday=3, tm_yday=98, tm_isdst=0) + + + +获取格式化的时间 + +可以根据需求选取各种格式,但是最简单的获取可读的时间模式的函数是asctime(): +\#!/usr/bin/python +import time +localtime = time.asctime( time.localtime(time.time()) ) +print "本地时间为 :", localtime + +输出结果: +本地时间为 : Thu Apr 7 10:05:21 2016 +\---------------------------- +可以直接使用ctime() + In [1]: from time import ctime + In [5]: ctime() + Out[5]: 'Fri Apr 14 10:44:46 2017' + +**格式化日期** +我们可以使用 time 模块的 strftime 方法来格式化日期: +time.strftime(format[, t]) + +\#!/usr/bin/python +import time +\# 格式化成2016-03-20 11:45:39形式 +print time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) +date + +\# 格式化成Sat Mar 28 22:24:24 2016形式 +print time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()) + +\# 将格式字符串转换为时间戳 +a = "Sat Mar 28 22:24:24 2016" +print time.mktime(time.strptime(a,"%a %b %d %H:%M:%S %Y")) + +输出结果: +2016-04-07 10:25:09 +Thu Apr 07 10:25:09 2016 +1459175064.0 + +python中时间日期格式化符号: + • %y 两位数的年份表示(00-99) + • %Y 四位数的年份表示(000-9999) + • %m 月份(01-12) + • %d 月内中的一天(0-31) + • %H 24小时制小时数(0-23) + • %I 12小时制小时数(01-12) + • %M 分钟数(00=59) + • %S 秒(00-59) + • %a 本地简化星期名称 + • %A 本地完整星期名称 + • %b 本地简化的月份名称 + • %B 本地完整的月份名称 + • %c 本地相应的日期表示和时间表示 + • %j 年内的一天(001-366) + • %p 本地A.M.或P.M.的等价符 + • %U 一年中的星期数(00-53)星期天为星期的开始 + • %w 星期(0-6),星期天为星期的开始 + • %W 一年中的星期数(00-53)星期一为星期的开始 + • %x 本地相应的日期表示 + • %X 本地相应的时间表示 + • %Z 当前时区的名称 + • %% %号本身 + +**获取某月日历** +Calendar模块有很广泛的方法用来处理年历和月历,例如打印某月的月历: + +```python +#!/usr/bin/python3.8 +import calendar +cal = calendar.month(2016, 1) +print "以下输出2016年1月份的日历:" +print cal +``` + +输出结果: +以下输出2016年1月份的日历: + January 2016 +Mo Tu We Th Fr Sa Su + 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 + +**Time 模块** +Time 模块包含了以下内置函数,既有时间处理相的,也有转换时间格式的: + +| 序号 | 函数及描述 | +| ---- | ------------------------------------------------------------ | +| 1 | time.altzone 返回格林威治西部的夏令时地区的偏移秒数。如果该地区在格林威治东部会返回负值(如西欧,包括英国)。对夏令时启用地区才能使用。 | +| 2 | time.asctime([tupletime]) 接受时间元组并返回一个可读的形式为"Tue Dec 11 18:07:14 2008"(2008年12月11日 周二18时07分14秒)的24个字符的字符串。 | +| 3 | time.clock( ) 用以浮点数计算的秒数返回当前的CPU时间。用来衡量不同程序的耗时,比time.time()更有用。 | +| 4 | time.ctime([secs]) 作用相当于asctime(localtime(secs)),未给参数相当于asctime() | +| 5 | time.gmtime([secs]) 接收时间辍(1970纪元后经过的浮点秒数)并返回格林威治天文时间下的时间元组t。注:t.tm_isdst始终为0 | +| 6 | time.localtime([secs]) 接收时间辍(1970纪元后经过的浮点秒数)并返回当地时间下的时间元组t(t.tm_isdst可取0或1,取决于当地当时是不是夏令时)。 | +| 7 | time.mktime(tupletime) 接受时间元组并返回时间辍(1970纪元后经过的浮点秒数)。 | +| 8 | time.sleep(secs) 推迟调用线程的运行,secs指秒数。 | +| 9 | time.strftime(fmt[,tupletime]) 接收以时间元组,并返回以可读字符串表示的当地时间,格式由fmt决定。 | +| 10 | time.strptime(str,fmt='%a %b %d %H:%M:%S %Y') 根据fmt的格式把一个时间字符串解析为时间元组。 | +| 11 | time.time( ) 返回当前时间的时间戳(1970纪元后经过的浮点秒数)。 | +| 12 | time.tzset() 根据环境变量TZ重新初始化时间相关设置。 | + + + +Time模块包含了以下2个非常重要的属性: + +| 序号 | 属性及描述 | +| ---- | ------------------------------------------------------------ | +| 1 | time.timezone 属性time.timezone是当地时区(未启动夏令时)距离格林威治的偏移秒数(>0,美洲;<=0大部分欧洲,亚洲,非洲)。 | +| 2 | time.tzname 属性time.tzname包含一对根据情况的不同而不同的字符串,分别是带夏令时的本地时区名称,和不带的。 | + + + +**日历(Calendar)模块** +此模块的函数都是日历相关的,例如打印某月的字符月历。 +星期一是默认的每周第一天,星期天是默认的最后一天。更改设置需调用calendar.setfirstweekday()函数。 +模块包含了以下内置函数: + +| 序号 | 函数及描述 | +| ---- | ------------------------------------------------------------ | +| 1 | calendar.calendar(year,w=2,l=1,c=6) 返回一个多行字符串格式的year年年历,3个月一行,间隔距离为c。 每日宽度间隔为w字符。每行长度为21* W+18+2* C。l是每星期行数。 | +| 2 | calendar.firstweekday( ) 返回当前每周起始日期的设置。默认情况下,首次载入caendar模块时返回0,即星期一。 | +| 3 | calendar.isleap(year) 是闰年返回True,否则为false。 | +| 4 | calendar.leapdays(y1,y2) 返回在Y1,Y2两年之间的闰年总数。 | +| 5 | calendar.month(year,month,w=2,l=1) 返回一个多行字符串格式的year年month月日历,两行标题,一周一行。每日宽度间隔为w字符。每行的长度为7* w+6。l是每星期的行数。 | +| 6 | calendar.monthcalendar(year,month) 返回一个整数的单层嵌套列表。每个子列表装载代表一个星期的整数。Year年month月外的日期都设为0;范围内的日子都由该月第几日表示,从1开始。 | +| 7 | calendar.monthrange(year,month) 返回两个整数。第一个是该月的星期几的日期码,第二个是该月的日期码。日从0(星期一)到6(星期日);月从1到12。 | +| 8 | calendar.prcal(year,w=2,l=1,c=6) 相当于 print calendar.calendar(year,w,l,c). | +| 9 | calendar.prmonth(year,month,w=2,l=1) 相当于 print calendar.calendar(year,w,l,c)。 | +| 10 | calendar.setfirstweekday(weekday) 设置每周的起始日期码。0(星期一)到6(星期日)。 | +| 11 | calendar.timegm(tupletime) 和time.gmtime相反:接受一个时间元组形式,返回该时刻的时间辍(1970纪元后经过的浮点秒数)。 | +| 12 | calendar.weekday(year,month,day) 返回给定日期的日期码。0(星期一)到6(星期日)。月份为 1(一月) 到 12(12月)。 | + + + +**其他相关模块和函数** +在Python中,其他处理日期和时间的模块还有: +• [datetime](http://docs.python.org/library/datetime.html#module-datetime)模块 +• [pytz](http://www.twinsun.com/tz/tz-link.htm)模块 +• [dateutil](http://labix.org/python-dateutil)模块 +Python实现查找指定文件夹内指定时间之后修改过的文件 + +```python +#!/usr/bin/python +import os +import time +import datetime + +time1 = '2010-4-1 12:33' #指定时间 +root1 = 'D:\\WWW' #指定文件夹 + +def txt_2_time(txt): + l = txt.replace(' ','-').replace(':','-').split('-') + l = map(int,l) + s = datetime.datetime(*l) + t = time.mktime(s.timetuple()) + return t + + +for root,dirs,files in os.walk(root1): + for file in files: + f = os.path.join(root,file) + mtime = os.path.getmtime(f) + if os.path.splitext(f)[1] in ('.html','.php') and mtime > txt_2_time(time1): + print f,time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(mtime)) +``` + + + + + +​ \ No newline at end of file diff --git a/12-Python正则表达.md b/12-Python正则表达.md new file mode 100644 index 0000000..716dcd6 --- /dev/null +++ b/12-Python正则表达.md @@ -0,0 +1,480 @@ +# 12-Python正则表达 + +**正则表达式** +wing忠告:搞运维的童鞋如果没有接触过shell里面的正则表达式,建议先跳过本章 + +**导语** +正则表达式是一个特殊的字符序列,用来检查一个字符串是否与某种模式匹配。 +Python提供的是 Perl 风格的正则表达式模式。 +re 模块使 Python拥有全部的正则表达式功能。 +compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。 +re 模块也提供与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。 + +**正则表达式的使用有两种方式:** + 1.使用正则之前先编译正则 + 2.使用正则之前不编译正则 + +**1.使用正则之前先编译正则** +正则表达式被编译成 'RegexObject' 实例,可以为不同的操作提供方法,如模式匹配搜索或 +字符串替换。 +\>>> import re +\>>> p=re.compile('n+') #先编译正则表达式,生成一个表达式对象 +\>>> p.match('nnnning').group() #再使用表达式去进行匹配 +'nnnn' + +group()返回被 RE 匹配的字符串 //'nnnn' +start()返回匹配开始的位置 //0 +end()返回匹配结束位置的下一个位置 //4 +span()返回一个元组包含匹配 (开始,结束) 的位置 //(0,4) 不包含下边界 + +**2.使用正则之前不编译正则** +利用match和search帮我们编译 + +**re.match():** +re.match 尝试从字符串的起始位置匹配一个模式,如果不是在起始位置匹配成功,match() +就返回none。 + +语法: +re.match(pattern, string, flags=0) + +参数说明: + +| 参数 | 描述 | +| ------- | ------------------------------------------------------------ | +| pattern | 匹配的正则表达式 | +| string | 要匹配的字符串 | +| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等 | + + +匹配成功re.match方法返回一个匹配的对象,否则返回None。 + +使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。 + +| 匹配对象方法 | 描述 | +| ------------ | ------------------------------------------------------------ | +| group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 | +| groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 | + + + +例 1: + \#!/usr/bin/python3 + import re + print(re.match('www', 'www.fklinux.com').span()) # 在起始位置匹配 + print(re.match('com', 'www.fklinux.com')) + +​ 输出结果: +​ (0, 3) +​ None + +例 2: + \#!/usr/bin/python + import re + line = "Cats are smarter than dogs" + matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I) + \#上面的re.M|re.I是正则表达式修饰符 - 可选标志,后面马上会讲到 + +​ if matchObj: +​ print "matchObj.group() : ", matchObj.group() +​ print "matchObj.group(1) : ", matchObj.group(1) +​ print "matchObj.group(2) : ", matchObj.group(2) +​ else: +​ print "No match!!" + +​ 执行结果: +​ matchObj.group() : Cats are smarter than dogs +​ matchObj.group(1) : Cats +​ matchObj.group(2) : smarter + +**re.search():** +re.search 扫描整个字符串并返回第一个成功的匹配。 + +函数语法: +re.search(pattern, string, flags=0) + +函数参数说明: + +| 参数 | 描述 | +| ------- | ------------------------------------------------------------ | +| pattern | 匹配的正则表达式 | +| string | 要匹配的字符串。 | +| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 | + + + +匹配成功re.search方法返回一个匹配的对象,否则返回None。 + +使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。 + +| 匹配对象方法 | 描述 | +| ------------ | ------------------------------------------------------------ | +| group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 | +| groups() | 返回一个包含所有小组字符串的元组 | + + + +例 1: + \>>> print(re.search('www', 'www.fklinux.com')) + <_sre.SRE_Match object at 0x7fbbe5816c60> #python2只显示内存地址 + <_sre.SRE_Match object; span=(0, 3), match='www'> #python3的显示结果 + +​ \>>> print(re.search('com', 'www.fklinux.com')) +​ <_sre.SRE_Match object at 0x7fbbe5816c60> #python2只显示内存地址 +​ <_sre.SRE_Match object; span=(12, 15), match='com'> #python3的显示结果 +​ \>>> print(re.search('www', 'www.fklinux.com').span()) #加上span()方法处理一下 +​ (0, 3) +​ \>>> print(re.search('com', 'www.fklinux.com').span()) +​ (12, 15) + +例 2: + \#!/usr/bin/python + import re + line = "Cats are smarter than dogs"; + searchObj = re.search( r'(.*) are (.*?) .*', line, re.M|re.I) #每个小括号一个分组 + +​ if searchObj: +​ print "searchObj.group() : ", searchObj.group() +​ print "searchObj.group(1) : ", searchObj.group(1) +​ print "searchObj.group(2) : ", searchObj.group(2) +​ else: +​ print "Nothing found!!" + +​ 执行结果: +​ searchObj.group() : Cats are smarter than dogs # +​ searchObj.group(1) : Cats +​ searchObj.group(2) : smarter + +例3: + \>>> print line + wig wing winng winnng winnnng + \>>> re.search('(wig)( +)(wing)',line).groups() #所有匹配的组的内容 + ('wig', ' ', 'wing') + \>>> re.search('(wig)( +)(wing)',line).group() #所有匹配的组的内容 + 'wig wing' + \>>> re.search('(wig)( +)(wing)',line).group(0) #所有匹配的组的内容 + 'wig wing' + \>>> re.search('(wig)( +)(wing)',line).group(1) #第一组匹配的内容 + 'wig' + \>>> re.search('(wig)( +)(wing)',line).group(2) #第二组匹配的内容 + ' ' + \>>> re.search('(wig)( +)(wing)',line).group(3) #第三组匹配的内容 + 'wing' + +**re.match与re.search的区别** +re.match + 只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None。 +re.search + 匹配整个字符串,直到找到一个匹配。 + +例: + \#!/usr/bin/python + import re + line = "Cats are smarter than dogs" + matchObj = re.match( r'dogs', line, re.M|re.I) + if matchObj: + print "match --> matchObj.group() : ", matchObj.group() + else: + print "No match!!" + +​ matchObj = re.search( r'dogs', line, re.M|re.I) +​ if matchObj: +​ print "search --> matchObj.group() : ", matchObj.group() +​ else: +​ print "No match!!" + +​ 运行结果: +​ No match!! +​ search --> matchObj.group() : dogs + +**正则表达式修饰符 - 可选标志** +正则表达式可以包含一些可选标志修饰符来控制匹配的模式 +修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。 +如: re.I | re.M 被设置成 I 和 M 标志 + +| 修饰符 | 描述 | +| ------ | ------------------------------------------------------------ | +| re.I | 使匹配对大小写不敏感 | +| re.L | 做本地化识别(locale-aware)匹配 | +| re.M | 多行匹配,影响 ^ 和 $ | +| re.S | 使 . 匹配包括换行在内的所有字符 | +| re.U | 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B. | +| re.X | 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。 | + + + +​ [root@wing python]# cat a.py +​ \#!/usr/bin/env python +​ import re +​ line='''winGa +​ winng +​ winnnga''' +​ r=re.compile(r'g.') #这里的点不包括换行,所以匹配到的是ga +​ print r.search(line).group() + +​ [root@wing python]# python a.py +​ ga + +​ \------------------------------------------------ +​ [root@wing python]# cat a.py +​ \#!/usr/bin/env python +​ import re +​ line='''winGa +​ winng +​ winnnga''' +​ r=re.compile(r'g.',re.S) #这里的点包括换行,所以匹配到的是g和换行 +​ print r.search(line).group() +​ [root@wing python]# python a.py +​ g + +​ [root@wing python]# +​ \----------------------------------------------- +​ [root@wing python]# cat a.py +​ \#!/usr/bin/env python +​ \#coding=utf-8 +​ import re +​ line='''winGa +​ winng +​ winnnga''' +​ r=re.compile(r'g. #hello,这是注释',re.X) #用了re.X后可以给正则表达式添加注释 +​ print r.search(line).group() +​ [root@wing python]# python a.py +​ ga + +====================================================== +**findall():** +findall()和 search()相似之处: + 在于二者都执行字符串搜索 + +findall()和 match()与search()不同之处: + findall()总返回一个列表(match和search返回的是元组) + 如果 findall()没有找到匹配的部分,会返回空列表 + 如果成功找到匹配部分,则返回所有匹配部分的列表(按从左到右出现的顺序排列) + +语法: + findall(rule , target [,flag] ) + rule 规则 + target 目标字符串 + flag 规则选项(选项功能和compile和search的flag一样)。返回结果是一个列表, 中间存放的是符合规则的字符串。如果没有符合规则的字符串被找到,就返回一个空列表。 + +例子: + \>>> re.findall('car', 'car') + ['car'] + \>>> re.findall('car', 'scary') + ['car'] + \>>> re.findall('car', 'carry the barcardi to the car') + ['car', 'car', 'car'] + +====================================================== +**检索和替换** +Python 的re模块提供了re.sub和re.subn用于替换字符串中的匹配项。 + + + +### re.sub() + +: +语法: + re.sub(pattern, repl, string, max=0) + 返回的字符串是在字符串中用 RE 最左边不重复的匹配来替换。如果模式没有发现,字符将被没有改变地返回。 + 可选参数 count 是模式匹配后替换的最大次数;count 必须是非负整数。缺省值是 0 表示替换所有的匹配。 + +例: + \#!/usr/bin/python + import re + phone = "2004-959-559 # This is Phone Number" + \# Delete Python-style comments + num = re.sub(r'#.*$', "", phone) + print "Phone Num : ", num + \# Remove anything other than digits + num = re.sub(r'\D', "", phone) + print "Phone Num : ", num + +​ 执行结果: +​ Phone Num : 2004-959-559 +​ Phone Num : 2004959559 +\-------------------------------------------- +**re.subn():** +subn()和 sub()一样,但它还返回一个表示替换次数的数字,替换后的字符串和表示替换次数的数字作为一个元组的元素返回。 +​ \>>> re.sub('[ae]', 'X', 'abcdef') +​ 'XbcdXf' +​ \>>> re.subn('[ae]', 'X', 'abcdef') +​ ('XbcdXf', 2) + + +================================================== + +## re.split(): + + +re 模块和正则表达式对象的方法 split()与字符串的 split()方法相似,前者是根据正则表达式模式分隔字符串,后者是根据固定的字符串分割,因此与后者相比,显著提升了字符分割的能力。如果你不想在每个模式匹配的地方都分割字符串,你可以通过设定一个值参数(非零)来指定分割的最大次数。 + +如果分隔符没有使用由特殊符号表示的正则表达式来匹配多个模式,那re.split()和string.split()的执行过程是一样的: +比如:在每一个冒号处分隔 + \>>> re.split(':', 'str1:str2:str3') + ['str1', 'str2', 'str3'] + +使用正则表达式做分割符: + \>>> line='who ----> are ----> you' + \>>> re.split(r'\s*-+>\s*',line) + ['who', 'are', 'you'] + +================================================== +**正则表达式模式** +模式字符串使用特殊的语法来表示一个正则表达式: +• 字母和数字表示他们自身 +• 多数字母和数字前加一个反斜杠时会拥有不同的含义 +• 标点符号只有被转义时才匹配自身,否则它们表示特殊的含义 +• 反斜杠本身需要使用反斜杠转义 +• 由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r'\t',等价于'\\t')匹配相应的特殊字符。 + +下表列出了正则表达式模式语法中的特殊元素。如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。 + +| 模式 | 描述 | +| ----------- | ------------------------------------------------------------ | +| ^ | 匹配字符串的开头 | +| $ | 匹配字符串的末尾。 | +| . | 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。 | +| [...] | 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k' | +| [^...] | 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。 | +| * | 匹配0个或多个的表达式。 | +| + | 匹配1个或多个的表达式。 | +| ? | 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 | +| {n} | 精确匹配n个前面表达式。 | +| {n,} | 匹配最少n个前面表达式。 | +| {n, m} | 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式 | +| a\|b | 匹配a或b | +| (re) | 既匹配括号内的表达式,也表示一个组 | +| (?: re) | 类似 (...), 但是不表示一个组 | +| (?#...) | 注释. | +| \w | 匹配字母数字及下划线 | +| \W | 匹配非字母数字及下划线 | +| \s | 匹配任意空白字符,等价于 [\t\n\r\f]. | +| \S | 匹配任意非空字符 | +| \d | 匹配任意数字,等价于 [0-9]. | +| \D | 匹配任意非数字 | +| \A | 匹配字符串开始,类似于^,但他主要是为了那些没有caret(^)字符的键盘准备的 | +| \Z | 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。类似于$,但他主要是为了那些没有($)字符的键盘准备的 | +| \b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 | +| \B | 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 | +| \n, \t, 等. | 匹配一个换行符。匹配一个制表符。等 | +| \1...\9 | 匹配第n个分组的子表达式。 | +| \10 | 匹配第n个分组的子表达式,如果它经匹配。否则指的是八进制字符码的表达式。 | + + + +注意: +1.上面有些正则如果不管用,就说明必须用原始字符串,比如'er\b'必须写成r'er\b'才会生效 +原因是 ASCII 字符和正则表达式特殊字符间所产生的冲突。比如,特殊符号“"b”"ASCII 字符中代表退格键,但同时"\b"也是一个正则表达式的特殊符号, 代表"匹配一个单词边界"。为了让 RE 编译器把两个字符"\b"当成你想要表达的字符串,而不是一个退格键,你需要用另一个反斜线对它进行转义,即可以这样写:"\\b"或者r"\b"。 + +2.‘*?’ ‘+?’ ‘??’ 最小匹配: +‘*’ ‘+’ ‘?’ 通常都是尽可能多的匹配字符。有时候我们希望它尽可能少的匹配。比如一个 c 语言的注释 ‘/* part 1 */ /* part 2 */’ ,如果使用最大规则: + +问号'?'有两种含义: +1)单独使用时表示匹配出现零次或一次的情况 +2)紧跟在表示重复的元字符后面时,表示要求搜索引擎匹配的字符串越短越好。例如:(+?),原本+表示1次或多次,现在只表示1次 +例: + \>>> re.sub(r'n+','A','wing winnng is a good student') + 'wiAg wiAg is a good studeAt' + \>>> re.sub(r'n+?','A','wing winnng is a good student') + 'wiAg wiAAAg is a good studeAt' + +3.‘(?:)’ 无捕获组 +当你要将一部分规则作为一个整体对它进行某些操作,比如指定其重复次数时,你需要将这部分规则用 ’(?:’ ‘)’ 把它包围起来,而不能仅仅只用一对括号,那样将得到绝对出人意料的结果。 +例:匹配字符串中重复的 ’ab’ + \>>> s=’ababab abbabb aabaab’ + \>>> re.findall( r’/b(?:ab)+/b’ , s ) + ['ababab'] +如果仅使用一对括号,看看会是什么结果: + \>>> re.findall( r’/b(ab)+/b’ , s ) + ['ab'] + +4.‘(?# )’ 注释 +Python 允许你在正则表达式中写入注释,在 ’(?#'')’ 之间的内容将被忽略。 + \>>> s=’ababab abbabb aabaab’ + \>>> re.findall('abab(?#注释)ab',s) + ['ababab'] + +5.(?iLmsux) 编译选项指定 +Python 的正则式可以指定一些选项,这个选项可以写在 findall 或 compile 的参数中,也可以写在正则式里,成为正则式的一部分。这在某些情况下会便利一些。具体的选项含义请看后面的 compile 函数的说明。 +此处编译选项 ’i’ 等价于 IGNORECASE ,L 等价于 LOCAL ,m 等价于 MULTILINE , s 等价于 DOTALL , u 等价于 UNICODE , x 等价于 VERBOSE 。 +请注意它们的大小写。在使用时可以只指定一部分,比如只指定忽略大小写,可写为 ‘(?i)’ ,要同时忽略大小写并使用多行模式,可以写为 ‘(?im)’ 。 +另外要注意选项的有效范围是整条规则,即写在规则的任何地方,选项都会对全部整条正则式有效。 + \>>> s=’ababab abbabb aabaab’ + \>>> re.findall('abAb(?#注释)**ab(?i)**',s) + ['ababab'] + +6.前向界定与后向界定 +有时候需要匹配一个跟在特定内容后面的或者在特定内容前面的字符串, Python 提供一个简便的前向界定和后向界定功能,或者叫前导指定和跟从指定功能。它们是: +‘(?<=…)’ 前向界定,括号中 ’…’ 代表你希望匹配的字符串的前面应该出现的字符串。 +‘(?=…)’ 后向界定,括号中的 ’…’ 代表你希望匹配的字符串后面应该出现的字符串。 + +例: 你希望找出 c 语言的注释中的内容,它们是包含在 ’/*’ 和 ’*/’ 之间,不过你并不希望匹配的结果把 ’/*’ 和 ’*/’ 也包括进来,那么你可以这样用: + \>>> s=r’/* comment 1 */ code /* comment 2 */’ + \>>> re.findall( r’(?<=/\*).+?(?=\*/)’ , s ) + [' comment 1 ', ' comment 2 '] +注意:这里我们仍然使用了最小匹配,以避免把整个字符串给匹配进去了。 +要注意的是,前向界定括号中的表达式必须是常值,即你不可以在前向界定的括号里写正则式。比如你如果在下面的字符串中想找到被字母夹在中间的数字,你不可以用前向界定: +例: + \>>> s = ‘aaa111aaa , bbb222 , 333ccc ‘ + \>>> re.findall( r’(?<=[a-z]+)\d+(?=[a-z]+)' , s ) # 错误的用法 + 它会给出一个错误信息: + error: look-behind requires fixed-width pattern + +不过如果你只要找出后面接着有字母的数字,你可以在后向界定写正则式: + \>>> re.findall( r’\d+(?=[a-z]+)’, s ) + ['111', '333'] + +如果你一定要匹配包夹在字母中间的数字,你可以使用组( group )的方式 + \>>> re.findall (r'[a-z]+(\d+)[a-z]+' , s ) + ['111'] + +除了前向界定前向界定和后向界定外,还有前向非界定和后向非界定,它的写法为: + ‘(?>> re.findall( r’\d+(?!\w+)’ , s ) + ['222'] + +​ 注意这里我们使用了 \w 而不是像上面那样用 [a-z] ,因为如果这样写的话,结果会是: +​ \>>> re.findall( r’\d+(?![a-z]+)’ , s ) +​ ['11', '222', '33'] +​ 这和我们期望的似乎有点不一样。它的原因,是因为 ’111’ 和 ’222’ 中的前两个数字也是满足这个要求的。 + +============================================= +**正则表达式实例** + +字符匹配: + +| 实例 | 描述 | +| ------ | -------------- | +| python | 匹配 "python". | + + + +字符类: + +| 实例 | 描述 | +| ----------- | --------------------------------- | +| [Pp]ython | 匹配 "Python" 或 "python" | +| rub[ye] | 匹配 "ruby" 或 "rube" | +| [aeiou] | 匹配中括号内的任意一个字母 | +| [0-9] | 匹配任何数字。类似于 [0123456789] | +| [a-z] | 匹配任何小写字母 | +| [A-Z] | 匹配任何大写字母 | +| [a-zA-Z0-9] | 匹配任何字母及数字 | +| [^aeiou] | 除了aeiou字母以外的所有字符 | +| [^0-9] | 匹配除了数字外的字符 | + + + +特殊字符类: + +| 实例 | 描述 | +| ---- | ------------------------------------------------------------ | +| . | 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。 | +| \d | 匹配一个数字字符。等价于 [0-9]。 | +| \D | 匹配一个非数字字符。等价于 [^0-9]。 | +| \s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 | +| \S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 | +| \w | 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。 | +| \W | 匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'。 | \ No newline at end of file diff --git a/13-Python异常处理.md b/13-Python异常处理.md new file mode 100644 index 0000000..74999de --- /dev/null +++ b/13-Python异常处理.md @@ -0,0 +1,445 @@ +# 13-Python异常处理 + +**异常处理** +**导语**: +在写代码的时候,经常会遇到异常。遇见异常并不是一件让人愉悦的事情。今天来一起详细了解异常。 +python提供了两个非常重要的功能来处理python程序在运行中出现的异常和错误,你可以使用该功能来调试python程序。 +\1. 异常处理 +\2. 断言(Assertions) + +**常用异常:** +Exception 它可以捕获任意(绝大部分)异常。 +AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x +IOError 输入/输出异常;基本上是无法打开文件 +ImportError 无法引入模块或包;基本上是路径问题或名称错误 +IndentationError 语法错误(的子类),代码没有正确对齐 +IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] +KeyError 试图访问字典里不存在的键 +KeyboardInterrupt Ctrl+C被按下 +NameError 使用一个还未被赋予对象的变量 +SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了) +TypeError 传入对象类型与要求的不符合 +UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它 +ValueError 传入一个调用者不期望的值,即使值的类型是正确的 + +**python标准异常**: +[![file://C:\Users\86186\AppData\Local\Temp\.2IK5Q0\1.png](assets/1.png)]() +[![file://C:\Users\86186\AppData\Local\Temp\.2IK5Q0\2.png](assets/2.png)]() +[![file://C:\Users\86186\AppData\Local\Temp\.2IK5Q0\3.png](assets/3.png)]() + +**什么是异常?** +异常即是一个事件,该事件会在程序执行过程中发生,影响程序的正常执行。 +一般,在Python无法正常处理程序时就会发生一个异常,异常是Python对象,表示一 +个错误。 + +**当Python脚本发生异常时需要捕获处理它,否则程序会终止执行。** +\#!/usr/bin/env python +\# -*- coding: UTF-8 -*- +try: + print "%d" % (5 / 0) +except ZeroDivisionError: + print "除数不能为零" +else: + print "没有报错" + +print "这是异常之后的代码" #如果没有上面的异常处理,下面的代码是不会执行的 +for i in range(10): + print i + +**捕捉异常**: + try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。 + 如果你不想在异常发生时结束你的程序,只需在try里捕获它。 + 语法: + try: + <语句> #运行别的代码 + except <名字>: + <语句> #如果在try部份引发了'name'异常 + except <名字>,<数据>: + <语句> #如果引发了'name'异常,获得附加的数据 + else: + <语句> #如果没有异常发生 + +**try的工作原理** +当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可 +以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。 +\1. 如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的 +except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异 +常)。 +\2. 如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的 +try,或者到程序的最上层(这样将结束程序,并打印缺省的出错信息)。 +\3. 如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的 +话),然后控制流通过整个try语句。 + [![file://C:\Users\86186\AppData\Local\Temp\.2IK5Q0\4.png](assets/4.png)]() + +例 +打开一个文件,在该文件中的写入内容,且并未发生异常: + try: + fh = open("testfile", "w") + fh.write("这是一个测试文件,用于测试异常!!") + except IOError: + print "Error: 没有找到文件或读取文件失败" + else: + print "内容写入文件成功" + fh.close() + +结果: + \# python test.py + 内容写入文件成功 + \# cat testfile # 查看写入的内容 + 这是一个测试文件,用于测试异常!! + +例 +打开一个文件,在该文件中的内容写入内容,但文件没有写入权限,发生了异常: + try: + fh = open("testfile", "w") + fh.write("这是一个测试文件,用于测试异常!!") + except IOError: + print "Error: 没有找到文件或读取文件失败" + else: + print "内容写入文件成功" + fh.close() + +在执行代码前为了测试方便,先去掉 testfile 文件的写权限 +再执行以上代码: + $ python test.py #注意这里用的是普通用户 + Error: 没有找到文件或读取文件失败 + +**使用except不带任何异常类型** + +### + + +你可以不带任何异常类型使用except,如下实例: + try: + 正常的操作 + ...................... + except: + 发生异常,执行这块代码 + ...................... + else: + 如果没有异常执行这块代码 +以上方式try-except语句捕获所有发生的异常。但这不是一个很好的方式,我们不能通过该程序 +识别出具体的异常信息。因为它捕获所有的异常。 + +**使用except带多种异常类型** +也可以使用相同的except语句来处理多个异常信息: + try: + 正常的操作 + ................ + except(Exception1[, Exception2[,...ExceptionN]]]): + 发生以上多个异常中的一个,执行这块代码 + ...................... + else: + 如果没有异常执行这块代码 + +**try-finally 语句** +try-finally 语句无论是否发生异常都将执行最后的代码。 + try: + <语句> + finally: + <语句> #退出try时总会执行 + +例1: + try: + fh = open("testfile", "w") + fh.write("这是一个测试文件,用于测试异常!!") + finally: + print "Error: 没有找到文件或读取文件失败" + +例2: +import time +try: + f=file("文件.py") + while True: + line = f.read() + if len(line)==0: + break + time.sleep(2) + print line, +finally: + f.close() + print "hello" + +例3: + try: + fh = open("testfile", "w") + try: + fh.write("这是一个测试文件,用于测试异常!!") + finally: + print "关闭文件" + fh.close() + except IOError: + print "Error: 没有找到文件或读取文件失败" + + +**异常的参数**: +一个异常可以带上参数,可作为输出的异常信息参数。 +你可以通过except语句来捕获异常的参数,如下所示: + try: + 正常的操作 + ...................... + except ExceptionType, Argument: + 你可以在这输出 Argument 的值... + +变量接收的异常值通常包含在异常的语句中。在元组的表单中变量可以接收一个或者多个 +值。 +元组通常包含错误字符串,错误数字,错误位置。 + +例 +以下为单个异常的实例: + \#!/usr/bin/python + def temp_convert(var): + try: + return int(var) + except ValueError, Argument: + print "参数没 有包含数字\n", Argument + +​ \# 调用函数 +​ temp_convert("xyz") + +以上程序执行结果如下: + $ python test.py + 参数没有包含数字 + invalid literal for int() with base 10: 'xyz' + +**触发异常** +可以使用raise语句自己触发异常 +raise语法格式: + raise [Exception [, args [, traceback]]] + +语句中Exception是异常的类型(例如,NameError)参数是一个异常参数值。该参数是可 +选的,如果不提供,异常的参数是"None"。 +最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。 + +例 +一个异常可以是一个字符串,类或对象。 Python的内核提供的异常,大多数都是实例化的 +类,这是一个类的实例的参数。 +定义一个异常: +def functionName( level ): + if level < 1: + raise Exception("Invalid level!", level) + \# 触发异常后,后面的代码就不会再执行 + +注意:为了能够捕获异常,"except"语句必须有用相同的异常来抛出类对象或者字符串。 +例如我们捕获以上异常,"except"语句如下: + try: + 正常逻辑 + except "Invalid level!": + 触发自定义异常 + else: + 其余代码 + +例 + \#!/usr/bin/python + def mye( level ): + if level < 1: + raise Exception("Invalid level!", level) + \# 触发异常后,后面的代码就不会再执行 + try: + mye(0) # 触发异常 + except "Invalid level!": + print 1 + else: + print 2 + +输出结果: + $ python test.py + Traceback (most recent call last): + File "test.py", line 11, in + mye(0) + File "test.py", line 7, in mye + raise Exception("Invalid level!", level) + Exception: ('Invalid level!', 0) + +**用户自定义异常:** +通过创建一个新的异常类,程序可以命名它们自己的异常。异常应该是典型的继承自 +Exception类,通过直接或间接的方式。 +以下为与RuntimeError相关的实例,实例中创建了一个类,基类为RuntimeError,用于在 +异常触发时输出更多的信息。 +在try语句块中,用户自定义的异常后执行except块语句,变量 e 是用于创建Networkerror +类的实例。 + class Networkerror(RuntimeError): + def __init__(self, arg): + self.args = arg + 在你定义以上类后,你可以触发该异常,如下所示: + try: + raise Networkerror("Bad hostname") + except Networkerror,e: + print e.args + +**万能异常** +在python的异常中,有一个万能异常:Exception,它可以捕获任意异常。 +例: + \#cat aa.py + s1 = 'hello' + try: + int(s1) + except Exception,e: + print e + +执行结果: + \#python aa.py + invalid literal for int() with base 10: 'hello' + +既然有这个万能异常,其他异常是不是就可以忽略了!当然不是,对于特殊处理或提醒的异常需要先定义,最后定义Exception来确保程序正常运行。 +例: + s1 = 'hello' + try: + int(s1) + except KeyError,e: + print '键错误' + except IndexError,e: + print '索引错误' + except Exception, e: + print '错误' + +**Python中何时使用断言 assert** +Python的assert是用来检查一个条件,如果它为真,就不做任何事。如果它为假,则会抛出AssertError并且包含错误信息。例如: +py> x = 23 +py> assert x > 0, "x is not zero or negative" +py> assert x%2 == 0, "x is not an even number" + +Traceback (most recent call last): + +File "", line 1, in + +AssertionError: x is not an even number +很多人用assert作为一个很快和容易的方法来在参数错误的时候抛出异常。但这样做是错的,非常错误,有两个原因。首先AssertError不是在测试参数时应该抛出的错误。你不应该像这样写代码: +if +not isinstance(x, int): + +raise AssertionError("not an int") +你应该抛出TypeError的错误,assert会抛出错误的异常。 + +但是,更危险的是,有一个关于assert的困扰:它可以被编译好然后从来不执行,如果你用 –O 或 –oo 选项运行Python,结果不保证assert表达式会运行到。当适当的使用assert时,这是未来,但是当assert不恰当的使用时,它会让代码用-O执行时出错。 + +那什么时候应该使用assert?没有特定的规则,断言应该用于: +防御型的编程 +运行时检查程序逻辑 +检查约定 +程序常量 +检查文档 +(在测试代码的时候使用断言也是可接受的,是一种很方便的单元测试方法,你接受这些测试在用-O标志运行时不会做任何事。我有时在代码里使用assert False来标记没有写完的代码分支,我希望这些代码运行失败。尽管抛出NotImplementedError可能会更好。) + +关于断言的意见有很多,因为它能确保代码的正确性。如果你确定代码是正确的,那么就没有用断言的必要了,因为他们从来不会运行失败,你可以直接移除这些断言。如果你确定检查会失败,那么如果你不用断言,代码就会通过编译并忽略你的检查。 + +在以上两种情况下会很有意思,当你比较肯定代码但是不是绝对肯定时。可能你会错过一些非常古怪的情况。在这个情况下,额外的运行时检查能帮你确保任何错误都会尽早地被捕捉到。 + +另一个好的使用断言的方式是检查程序的不变量。一个不变量是一些你需要依赖它为真的情况,除非一个bug导致它为假。如果有bug,最好能够尽早发现,所以我们为它进行一个测试,但是又不想减慢代码运行速度。所以就用断言,因为它能在开发时打开,在产品阶段关闭。 + +一个非变量的例子可能是,如果你的函数希望在它开始时有数据库的连接,并且承诺在它返回的时候仍然保持连接,这就是函数的不变量: + +def +some_function(arg): + +​ assert +not DB.closed() + +​ ... +\# code goes here + +​ assert +not DB.closed() + +​ return +result +断言本身就是很好的注释,胜过你直接写注释: + +\# when we reach here, we know that n > 2 + +你可以通过添加断言来确保它: + +assert n > 2 + +断言也是一种防御型编程。你不是让你的代码防御现在的错误,而是防止在代码修改后引发的错误。理想情况下,单元测试可以完成这样的工作,可是需要面对的现实是,它们通常是没有完成的。人们可能在提交代码前会忘了运行测试代码。有一个内部检查是另一个阻挡错误的防线,尤其是那些不明显的错误,却导致了代码出问题并且返回错误的结果。 + +加入你有一些if…elif 的语句块,你知道在这之前一些需要有一些值: + +\# target is +expected to be one of x, y, or z, and nothing else. + +if +target == x: + +​ run_x_code() + +elif target == y: + +​ run_y_code() + +else: + +​ run_z_code() +假设代码现在是完全正确的。但它会一直是正确的吗?依赖的修改,代码的修改。如果依赖修改成 target = w 会发生什么,会关系到run_w_code函数吗?如果我们改变了代码,但没有修改这里的代码,可能会导致错误的调用 run_z_code 函数并引发错误。用防御型的方法来写代码会很好,它能让代码运行正确,或者立马执行错误,即使你在未来对它进行了修改。 + +在代码开头的注释很好的一步,但是人们经常懒得读或者更新注释。一旦发生这种情况,注释会变得没用。但有了断言,我可以同时对代码块的假设书写文档,并且在它们违反的时候触发一个干净的错误 +assert target in +(x, y, z) + +if +target == x: + +​ run_x_code() + +elif target == y: + +​ run_y_code() + +else: + +​ assert target == z + +​ run_z_code() +这样,断言是一种防御型编程,同时也是一种文档。我想到一个更好的方案: + +if +target == x: + +​ run_x_code() + +elif target == y: + +​ run_y_code() + +elif target == z: + +​ run_z_code() + +else: + +​ \# This can never happen. But just in +case it does... + +​ raise RuntimeError("an unexpected error occurred") +按约定进行设计是断言的另一个好的用途。我们想象函数与调用者之间有个约定,比如下面的: + +“"果你传给我一个非空字符串,我保证传会字符串的第一个字母并将其大写。”" +如果约定被函数或调用这破坏,代码就会出问题。我们说函数有一些前置条件和后置条件,所以函数就会这么写: +def first_upper(astring): + +​ assert isinstance(astring, str) and len(astring) > 0 + +​ result = astring[0].upper() + +​ assert isinstance(result, str) and len(result) == 1 + +​ assert result == result.upper() + +​ return +result +按约定设计的目标是为了正确的编程,前置条件和后置条件是需要保持的。这是断言的典型应用场景,因为一旦我们发布了没有问题的代码到产品中,程序会是正确的,并且我们能安全的移除检查。 + +下面是我建议的不要用断言的场景: + +不要用它测试用户提供的数据 +不要用断言来检查你觉得在你的程序的常规使用时会出错的地方。断言是用来检查非常罕见的问题。你的用户不应该看到任何断言错误,如果他们看到了,这是一个bug,修复它。 +有的情况下,不用断言是因为它比精确的检查要短,它不应该是懒码农的偷懒方式。 +不要用它来检查对公共库的输入参数,因为它不能控制调用者,所以不能保证调用者会不会打破双方的约定。 +不要为你觉得可以恢复的错误用断言。换句话说,不用改在产品代码里捕捉到断言错误。 +不要用太多断言以至于让代码很晦涩。 + + + + + + \ No newline at end of file diff --git a/14-Python面向对象.md b/14-Python面向对象.md new file mode 100644 index 0000000..5b053ef --- /dev/null +++ b/14-Python面向对象.md @@ -0,0 +1,2548 @@ +# Python面向对象 + +## 面向对象 + +面向对象编程(Object-Oriented Programming,OOP)是一种程序设计范式,它以对象为中心,将数据和操作数据的方法封装在一起。在Python中,一切皆为对象,包括整数、字符串、函数等。 + +在Python中,类和OOP 都不是日常编程所必需的。尽管它从一开始设计就是面向对象的,并且结构上支持 OOP,但 Python 没有限定或要求你在你的应用中写 OOP 的代码。 +[![file://C:\Users\86186\AppData\Local\Temp\.2IK5Q0\1.png](assets/1.png)]()[![file://C:\Users\86186\AppData\Local\Temp\.2IK5Q0\2.png](assets/2.png)]() + +## 相关概念 + +**类(Class)** + +​ 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。 + +**方法** + +​ 类中定义的函数。 + +**类变量** + +​ 类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。 + +**实例变量** + +​ 定义在方法中的变量,只作用于当前实例 + +**数据成员** + +​ 类变量或者实例变量,用于处理类及其实例对象的相关的数据。 + +**方法重写** + +​ 如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。 + +**继承** + +​ 即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计 :一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例如,Dog是一个Animal)。 + +**实例化** + +​ 创建一个类的实例,类的具体对象。 + +**对象** + +​ 通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。 + +## 创建类和实例 + +使用class语句来创建一个新类,class之后为类的名称并以冒号结尾 + +```python +class ClassName: + '类的帮助信息' # 类文档字符串,类的帮助信息可以通过ClassName.__doc__查看 + class_suite # 类体,class_suite 由类成员,方法,数据属性组成。 +``` + +例 + +```python +#!/usr/bin/env python +class Employee: # 创建类 + '所有员工的基类' + empCount = 0 + + # 是一个类变量,它的值将在这个类的所有实例之间共享。可在内部类或外部类使用Employee.empCount访问。也叫 静态变量 静态数据 静态字段 静态属性 类数据属性,静态变量在内存中只保存一份,实例变量在每个对象中都保存一份 + def __init__(self, name, salary): + # 是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法 + self.name = name # 实例属性,也叫普通变量或实例变量 + self.salary = salary + Employee.empCount += 1 + + def displayCount(self): + print("Total Employee {}".format(Employee.empCount)) + + def displayEmployee(self): + print("Name : ", self.name, ", Salary: ", self.salary) +''' +创建实例对象 + 要创建一个类的实例,可以使用类的名称,并通过`__init__`方法接收参数。 +''' +emp1=Employee("wing",2000) # 创建 Employee 类的第一个对象 +emp2=Employee("lilei",3000) # 创建 Employee 类的第二个对象 + +# 访问属性 使用.来访问对象的属性 +emp1.displayEmployee() +emp2.displayEmployee() +print("Total Employee {}".format(Employee.empCount)) + +emp1.age = 7 # 添加一个 'age' 属性 +emp1.age = 8 # 修改 'age' 属性 +del emp1.age # 删除 'age' 属性 + +输出结果 +[root@www test]# python test.py +Name : wing , Salary: 2000 +Name : lilei , Salary: 3000 +Total Employee 2 +``` + +函数方式访问属性 + +```python +访问对象的属性 +getattr(obj, name[, default]) +例子 + getattr(emp1, 'age') # 返回 'age' 属性的值 + +检查是否存在一个属性 +hasattr(obj,name) +例子 + hasattr(emp1, 'age') # 如果存在 'age' 属性返回 True。 + +设置一个属性。如果属性不存在,会创建一个新属性 +setattr(obj,name,value) +例子 + setattr(emp1, 'age', 8) # 添加属性 'age' 值为 8 + +删除属性 +delattr(obj, name) +例 + delattr(emp1, 'age') # 删除属性 'age' +``` + +## 类属性与方法 + +**类的私有属性**(私有字段) + +`__private_attrs` + +​ 两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。 + +​ 在类内部的方法中使用时 self.__private_attrs + +**类的方法** + 在类地内部,使用def关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数self,且为第一个参数 + +**类的私有方法** + +`__private_method` + +​ 两个下划线开头,声明该方法为私有方法,不能在类地外部调用。 + +​ 在类的内部调用 self.__private_methods + +实例 + +```python +#!/usr/bin/python +class JustCounter: + __secretCount = 0 # 私有变量 + publicCount = 0 # 公开变量 + + def count(self): + self.__secretCount += 1 + self.publicCount += 1 + print self.__secretCount + +counter = JustCounter() +counter.count() +counter.count() +print counter.publicCount +print counter.__secretCount # 报错,实例不能访问私有变量Python 通过改变名称来包含类名: +1 +2 +2 +Traceback (most recent call last): + File "test.py", line 17, in + print counter.__secretCount # 报错,实例不能访问私有变量 +AttributeError: JustCounter instance has no attribute '__secretCount' + +Python不允许实例化的类访问私有数据,但你可以使用 object._className__attrName 访问属性,将如下代码替换以上代码的最后一行代码: +......................... +print counter._JustCounter__secretCount +执行结果: +1 +2 +2 +2 +``` + +**特殊类属性(Python内置类属性):** + C.__dict__ :类C的属性(包含一个字典,由类的数据属性组成) + C.__doc__ :类C的文档字符串 + C.__name__:类C类名(字符串) + C.__module__:类C定义所在的模块(类的全名是'__main__.className',如果类位于一个导入 +模块mymod中,那么className.__module__ 等于 mymod) + C.__bases__ : 类C的所有父类构成元素(包含了一个由所有父类组成的元组) + C.__class__:实例对应的类(仅新式类中) + +根据上面定义的类 MyClass,有如下结果: + \>>> MyClass.__name__ + 'MyClass' + +​ \>>> MyClass.__doc__ +​ 'MyClass class definition' + +​ \>>> MyClass.__bases__ +​ (,) + +​ \>>> print MyClass.__dict__ +​ {'__doc__': None, 'myVersion': 1, 'showMyVersion': +​ , '__module__': '__main__'} + +​ \>>> MyClass.__module__ +​ '__main__' +​ \>>> MyClass.__class__ +​ + +**__name__** + 是给定类的字符名字。 + 它适用于那种只需要字符串(类对象的名字),而非类对象本身的情况。一些内建的类型也有这个属性,我们将会用 +到其中之一来展示__name__字符串的益处。 + +​ 类型对象是一个内建类型的例子,它有__name__的属性。回忆一下,type()返回被调用对象的类型。 +​ 我们可以使用类型对象的__name__属性来取得相应的字符串名。 + +如下例示: + \>>> stype = type('What is your quest?') + \>>> stype # stype is a type object stype 是一个类型对象 + + \>>> stype.__name__ # get type as a string 得到类型名(字符串表示) + 'string' + +​ \>>> type(3.14159265) # also a type object 又一个类型对象 +​ +​ \>>> type(3.14159265).__name__ # get type as a string 得到类型名(字符串表示) +​ 'float' + + +**__doc__** + 是类的文档字符串,与函数及模块的文档字符串相似,必须紧随头行(header line) 后的字符串。 + 文档字符串不能被派生类继承,也就是说派生类必须含有它们自己的文档字符串。 + + +**__dict__** + 包含一个字典,由类的数据属性组成。 + 访问一个类属性的时候,Python 解释器将会搜索字典以得到需要的属性。 + 如果在__dict__中没有找到,将会在基类的字典中进行搜索, 采用“"度优先搜索”"序。 + 基类集的搜索是按顺序的,从左到右,按其在类定义时,定义父类参数时的顺序。 + 对类的修改会仅影响到此类的字典;基类的__dict__属性不会被改动的 + + +**__module__** + Python 支持模块间的类继承。为更清晰地对类进行描述,1.5 版本中引入了__module__, 这样类名就完全由模 +块名所限定。看一下下面的例子: + \>>> class C(object): + ... pass + ... + \>>> C + + +​ \>>> C.__module__ +​ '__main__' + +​ 类 C 的全名是"__main__.C",比如,source_module.class_name。 +​ 如果类 C 位于一个导入的模块中,如 mymod,像下面的: +​ \>>> from mymod import C + +​ \>>> C +​ + +​ \>>> C.__module__ +​ 'mymod' + +​ 在以前的版本中,没有特殊属性__module__,很难简单定位类的位置,因为类没有使用它们的全名。 + +**__class__** + 最后,由于类型和类的统一性,当访问任何类的__class__属性时,你将发现它就是一个类型对象的实例。换句话说,一个类已是一种类型了。因为经典类并不认同这种等价性(一个经典类是一 个类对象,一个类型是一个类型对象),对这些对象来说,这个属性并未定义。 +**__class__到底是什么意思?看例子** + \# cat a.py + \#!/usr/bin/python + \# -*- coding: UTF-8 -*- + class A: + def __init__(self,url): + self.url = url + def out(self): + return self.url + +​ a = A('news.163.com') +​ print a.out() + +​ b = a.__class__('www.bccn.net') +​ print b.out() + +print A + print a.__class__ + +​ \# python a.py +​ news.163.com +​ www.bccn.net +​ __main__.A +​ __main__.A + +**可以看出a.__class__就等效于a的类A** + +内置类属性调用实例: + \#!/usr/bin/python + class Employee: + '所有员工的基类' + empCount = 0 + +​ def __init__(self, name, salary): +​ self.name = name +​ self.salary = salary +​ Employee.empCount += 1 + +​ def displayCount(self): +​ print "Total Employee %d" % Employee.empCount + +​ def displayEmployee(self): +​ print "Name : ", self.name, ", Salary: ", self.salary + +​ print "Employee.__doc__:", Employee.__doc__ +​ print "Employee.__name__:", Employee.__name__ +​ print "Employee.__module__:", Employee.__module__ +​ print "Employee.__bases__:", Employee.__bases__ +​ print "Employee.__dict__:", Employee.__dict__ + +​ 输出结果: +​ Employee.__doc__: 所有员工的基类 +​ Employee.__name__: Employee +​ Employee.__module__: __main__ +​ Employee.__bases__: () +​ Employee.__dict__: {'__module__': '__main__', 'displayCount': , 'empCount': 0, 'displayEmployee': , '__doc__': '\xe6\x89\x80\xe6\x9c\x89\xe5\x91\x98\xe5\xb7\xa5\xe7\x9a\x84\xe5\x9f\xba\xe7\xb1\xbb', '__init__': } + +## 类的继承 + +面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。 +继承完全可以理解成类之间的类型和子类型关系。 + +继承语法 + class 派生类名(基类名): + +python继承中的特点: + 1:在继承中基类的构造(__init__()方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用。 + 2:在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数 + 3:Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。 + +多重继承 +如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。 +语法: +派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示: +class SubClassName (ParentClass1[, ParentClass2, ...]): + 'Optional class documentation string' + class_suite + +实例: + \#!/usr/bin/python + class Parent: # 定义父类 + parentAttr = 100 + def __init__(self): + print "调用父类构造函数" + +​ def parentMethod(self): +​ print '调用父类方法' + +​ def setAttr(self, attr): +​ Parent.parentAttr = attr + +​ def getAttr(self): +​ print "父类属性 :", Parent.parentAttr + +​ class Child(Parent): # 定义子类 +​ def __init__(self): +​ print "调用子类构造方法" + +​ def childMethod(self): +​ print '调用子类方法 child method' + +​ c = Child() # 实例化子类 +​ c.childMethod() # 调用子类的方法 +​ c.parentMethod() # 调用父类方法 +​ c.setAttr(200) # 再次调用父类的方法 +​ c.getAttr() # 再次调用父类的方法 + +以上代码执行结果如下: + 调用子类构造方法 + 调用子类方法 child method + 调用父类方法 + 父类属性 : 200 + +继承多个类 +class A: # 定义类 A +..... + +class B: # 定义类 B +..... + +class C(A, B): # 继承类 A 和 B +..... + +使用issubclass()或者isinstance()方法来检测。 +issubclass()布尔函数判断一个类是另一个类的子类或者子孙类,语法: + issubclass(sub,sup) + +例子: + def foo(): + pass + print issubclass(foo.__class__, object) + +​ 结果: +​ True + +​ 上述代码说明了Python 中的函数是 object 的子类 +​ +isinstance(obj, Class) 布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。 + +**方法重写** +如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法: + +实例: +\#!/usr/bin/python +class Parent: # 定义父类 + def myMethod(self): + print '调用父类方法' + +class Child(Parent): # 定义子类 + def myMethod(self): + print '调用子类方法' + +c = Child() # 子类实例 +c.myMethod() # 子类调用重写方法 + +输出结果: + 调用子类方法 + +基础重载方法 +下表列出了一些通用的功能,你可以在自己的类重写: + +| 序号 | 方法 描述 简单的调用 | +| ---- | ------------------------------------------------------------ | +| 1 | __init__ ( self [,args...] ) 构造函数 简单的调用方法: obj = className(args) | +| 2 | __del__( self ) 析构方法, 删除一个对象 简单的调用方法 : del obj | +| 3 | __repr__( self ) 转化为供解释器读取的形式 简单的调用方法 : repr(obj) | +| 4 | __str__( self ) 用于将值转化为适于人阅读的形式 简单的调用方法 : str(obj) | +| 5 | __cmp__ ( self, x ) 对象比较 简单的调用方法 : cmp(obj, x) | + + + +运算符重载 +Python同样支持运算符重载,实例如下: +\#!/usr/bin/python +class Vector: + def __init__(self, a, b): + self.a = a + self.b = b + + def __str__(self): + return 'Vector (%d, %d)' % (self.a, self.b) + + def __add__(self,other): + return Vector(self.a + other.a, self.b + other.b) + +v1 = Vector(2,10) +v2 = Vector(5,-2) +print v1 + v2 +以上代码执行结果如下所示: +Vector(7,8) + +**类、实例和其他对象的内建函数** +**issubclass()** +issubclass() 布尔函数判断一个类是另一个类的子类或子孙类。 +语法: + issubclass(sub, sup) + +issubclass() 返回True的情况: + \1. 给出的子类sub确实是父类sup的一个子类(反之,则为False)。 + \2. 这个函数也允许"不严格"的子类,意味着,一个类可视为其自身的子类,所以,这个函数如果当 sub 就是 sup,或者从 sup 派生而来,则返回 True。(一个"严格的"子类是严格意义上的从一个类派生而来的子类。) + \3. 从 Python 2.3 开始,issubclass()的第二个参数可以是可能的父类组成的 tuple(元组),这时, 只要第一个参数是给定元组中任何一个候选类的子类时,就会返回 True。 + +**isinstance()** +isinstance() 布尔函数在判定一个对象是否是另一个给定类的实例时,非常有用。 + +语法: + isinstance(obj1, obj2) + isinstance()在 obj1 是类 obj2 的一个实例,或者是 obj2 的子类的一个实例时,返回 True (反之,则为 False) + +例子: + \>>> class C1(object): pass + \>>> class C2(object): pass + \>>> c1 = C1() + \>>> c2 = C2() + \>>> isinstance(c1, C1) True + \>>> isinstance(c2, C1) False + \>>> isinstance(c1, C2) False + \>>> isinstance(c2, C2) True + \>>> isinstance(C2, c2) + Traceback (innermost last): + File "", line 1, in ? + isinstance(C2, c2) + TypeError: second argument must be a class + +注意:第二个参数应当是类;不然,你会得到一个 TypeError。但如果第二个参数是一个类型对象,则不会出现异常。这是允许的,因为你也可以使用 isinstance()来检查一个对象 obj1 是否是 obj2 的类型,比如: + \>>> isinstance(4, int) + True + \>>> isinstance(4, str) + False + \>>> isinstance('4', str) + True + +isinstance()也可以使用一个元组(tuple)作为第二个参数。如果第一个参数是第二个参数中给定元组的任何一个候选类型或类的实例时,就会返回 True。 + +**hasattr(), getattr(),setattr(), delattr()** +*attr()系列函数可以在各种对象下工作,不限于类(class)和实例(instances),因为在类和实例中使用极其频繁,就在这里列出来了。 + +hasattr()函数是 Boolean 型的,它的目的就是为了决定一个对象是否有一个特定的属性,一般用于访问某属性前先作一下检查。 + +getattr()取得对象的属性,会在你试图读取一个不存在的属性时,引发 AttributeError 异常,除非给出那个可选的默认参数。 + +setattr()将要么加入一个新的属性,要么取代一个已存在的属性。 + +delattr()函数会从一个对象中删除属性。 + +下面一些例子使用到了*attr()系列函数: +\>>> class myClass(object): +... def __init__(self): +... self.foo = 100 +\>>> myInst = myClass() +\>>> hasattr(myInst, 'foo') +True +\>>> getattr(myInst, 'foo') +100 +\>>> hasattr(myInst, 'bar') +False +\>>> getattr(myInst, 'bar') +Traceback (most recent call last): +File "", line 1, in ? +getattr(myInst, 'bar') +AttributeError: myClass instance has no attribute 'bar' +\>>> getattr(c, 'bar', 'oops!') +'oops!' +\>>> setattr(myInst, 'bar', 'my attr') +\>>> dir(myInst) +['__doc__', '__module__', 'bar', 'foo'] +\>>> getattr(myInst, 'bar') # same as myInst.bar +'my attr' +\>>> delattr(myInst, 'foo') +\>>> dir(myInst) +['__doc__', '__module__', 'bar'] +\>>> hasattr(myInst, 'foo') +False + +**dir()** +用 dir()列出一个模块所有属性的信息, dir()还可以用在对象上。 +dir()提供的信息比以前更加详尽。根据文档,"除了实例变量名和常用方法外,它还显示那些通过特殊标记来调用的方法,像__iadd__(+=),__len__(len()), __ne__(!=)。 +dir()作用在实例上(经典类或新式类)时,显示实例变量,还有在实例所在的类及所有它的基类中定义的方法和类属性. +dir()作用在类上(经典类或新式类)时,则显示类以及它的所有基类的__dict__中的内容。 但它不会显示定义在元类(metaclass)中的类属性. +dir()作用在模块上时,则显示模块的__dict__的内容. +dir()不带参数时,则显示调用者的局部变量. + +**super()** +super()函数的目的就是帮助程序员找出相应的父类, 然后方便调用相关的属性。一般情况下,程序员可能仅仅采用非绑定方式调用祖先类方法。使用 super()可以简化搜索一个合适祖先的任务,并且在调用它时,替你传入实例或类型对象。 +每个定义的类,都有一个名为__mro__的属性,它是一个元组,按照他们被搜索时的顺序,列出了备搜索的类。 +语法如下: + super(type[, obj]) +给出 type,super()"返回此 type 的父类"。如果你希望父类被绑定,你可以传入 obj 参数(obj 必须是 +type 类型的).否则父类不会被绑定。obj 参数也可以是一个类型,但它应当是 type 的一个子类。通常,当给出 obj 时: + \1. 如果obj是一个实例,isinstance(obj,type)就必须返回True + \2. 如果obj是一个类或类型,issubclass(obj,type)就必须返回True + +事实上,super()是一个工厂函数,它创造了一个 super object,为一个给定的类使用__mro__ 去查找相应的父类。很明显,它从当前所找到的类开始搜索 MRO。 +super()的主要用途,是来查找父类的属性,比如, super(MyClass,self).__init__()。如果你没有执行这样的查找,你可能不需要使用 super()。 + +**vars()** +vars()内建函数与 dir()相似,只是给定的对象参数都必须有一个__dict__属性。vars()返回一个字典,它包含了对象存储于其__dict__中的属性(键)及值。如果提供的对象没有这样一个属性, 则会引发一个 TypeError 异常。如果没有提供对象作为 vars()的一个参数,它将显示一个包含本地名字空间的属性(键)及其值的字典,也就是locals()。 + +例子,使用类实例调用 vars(): + \>>> class C(object): + ... pass + \>>> c = C() + \>>> c.foo = 100 + \>>> c.bar = 'Python' + \>>> c.__dict__ + {'foo': 100, 'bar': 'Python'} + \>>> vars(c) + {'foo': 100, 'bar': 'Python'} + +**Python新式类和经典类** +Python从2.2开始,引入了 new style class(新式类) +Python 2.x中默认都是经典类,只有显式继承了object才是新式类 +Python 3.x中默认都是新式类,不必显式的继承object + +新式类跟经典类的差别主要是以下几点: +• 新式类对象可以直接通过__class__属性获取自身类型:type +• 继承搜索的顺序发生了改变 +• 新式类增加了__slots__内置属性, 可以把实例属性的种类锁定到__slots__规定的范围之中 +• 新式类增加了__getattribute__方法 + +\1. 新式类对象可以直接通过__class__属性获取自身类型:type +\#cat test1.py +\#/usr/bin/env python2.7 + class E: + \#经典类 + pass + +​ class E1(object): +​ \#新式类 +​ pass + +​ e = E() +​ print "经典类" +​ print e +​ print type(e) +​ print e.__class__ + +​ print "新式类" +​ e1 = E1() +​ print e1 +​ print e1.__class__ +​ print type(e1) + +输出结果: + 经典类 + <__main__.E instance at 0x0000000002250B08> + + __main__.E + +​ 新式类 +​ <__main__.E1 object at 0x0000000002248710> +​ +​ + +​ E1是定义的新式类。那么输出e1的时候,不论是type(e1),还是e1.__class__都是输出的。 + +\2. 继承搜索的顺序发生了改变 +经典类多继承属性搜索顺序: 先深入继承树左侧,再返回,开始找右侧 +新式类多继承属性搜索顺序: 先水平搜索,然后再向上移动 +\#cat test2.py + class A(object): + '新式类 ,作为所有类的基类' + def foo(self): + print "class A" + +​ class A1(): +​ '经典类,作为所有类的基类 ' +​ def foo(self): +​ print "class A1" + +​ class C(A): +​ pass + +​ class C1(A1): +​ pass + +​ class D(A): +​ def foo(self): +​ print "class D" + +​ class D1(A1): +​ def foo(self): +​ print "class D1" + +​ class E(C, D): +​ pass + +​ class E1(C1, D1): +​ pass + +​ e = E() +​ e.foo() + +​ e1 = E1() +​ e1.foo() + +输出结果: + class D + class A1 + +因为A新式类,对于继承A类都是新式类,首先要查找类E中是否有foo(),如果没有则按顺序查找C->D->A。它是一种广度优先查找方式。 + +因为A1经典类,对于继承A1类都是经典类,首先要查找类E1中是否有foo(),如果没有则按顺序查找C1->A1->D1。它是一种深度优先查找方式。 + +\3. 新式类增加了__slots__内置属性, 可以把实例属性的种类锁定到__slots__规定的范围之中。 + +比如只允许对A实例添加name和age属性: +\#cat test3.py + class A(object): + __slots__ = ('name', 'age') + +​ class A1(): +​ __slots__ = ('name', 'age') + +​ a1 = A1() +​ a = A() + +​ a1.name1 = "a1" +​ a.name1 = "a" + +A是新式类添加了__slots__ 属性,所以只允许添加 name age +A1经典类__slots__ 属性没用 + +执行结果: + Traceback (most recent call last): + File "t.py", line 13, in + a.name1 = "a" + AttributeError: 'A' object has no attribute 'name1' +所以a.name是会出错的 + +通常每一个实例都会有一个__dict__属性,用来记录实例中所有的属性和方法,也是通过这个字典,可以让实例绑定任意的属性。 +__slots__属性作用就是,当类C有比较少的变量,而且拥有__slots__属性时,类C的实例就没有__dict__属性,而是把变量的值存在一个固定的地方。如果试图访问一个__slots__中没有的属性,实例就会报错。 +这样操作的好处:__slots__属性虽然令实例失去了绑定任意属性的便利,但是因为每一个实例没有__dict__属性,却能有效节省每一个实例的内存消耗,有利于生成小而精干的实例。 + +\4. 新式类增加了__getattribute__方法 +\#cat test4.py +class A(object): + def __getattribute__(self, *args, **kwargs): + print "A.__getattribute__" + +class A1(): + def __getattribute__(self, *args, **kwargs): + print "A1.__getattribute__" + +a1 = A1() +a = A() + +a.test +print "=========" +a1.test + +A.__getattribute__ +========= +Traceback (most recent call last): + File "t.py", line 18, in + a1.test +AttributeError: A1 instance has no attribute 'test' + +A是新式类,每次通过实例访问属性,都会经过__getattribute__函数, +A1不会调用__getattribute__所以出错了 + +​ 抽象/实现 +抽象指对现实世界问题和实体的本质表现、行为和特征**建模**,建立一个相关的子集,可以用于描绘程序 +结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。 对某种抽象 +的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户程序应当是透 +明而且无关的。 + +封装/接口 +封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对 +数据的访问、无视接口与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的一部分,客户 +端根本就不需要知道在封装之后,数据属性是如何组织的。在 Python 中,所有的类属性都是公开的, +但名字可能被"混淆"了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计 +时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。 + +合成 +合成扩充了对类的描述,使得多个不同的类合成为一个大的类,来解决现实问题。合成描述了一个异常 +复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在 +一起,彼此是"有一个"的关系。比如,RepairShop"有一个"技工(应该至少有一个吧),还"有一个"顾客(至 +少一个)。 + +这些组件要么通过联合关系组在一块,意思是说,对子组件的访问是允许的(对 RepairShop 来说,顾客 +可能请求一个 SmogCheck,客户程序这时就是与 RepairShop 的组件进行交互),要么是聚合在一起, +封装的组件仅能通过定义好的接口来访问,对于客户程序来说是透明的。继续我的例子, 客户程序可能 +会建立一个 SmogCheck 请求来代表顾客,但不能够同 RepairShop 的 SmogZone 部分进行交互, +因为 SmogZone 是由 RepairShop 内部控制的,只能通过 smogCheckCar()方法调用。Python 支 +持上述两种形式的合成。 + +派生/继承/继承结构 +派生描述了子类的创建,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义 +操作,都不会修改原类的定义。继承描述了子类属性从祖先类继承这样一种方式。从前面的例子中,技 +工可能比顾客多个汽车技能属性,但单独的来看,每个都"是一个"人,所以,不管 对谁而言调用 talk()都 +是合法得,因为它是人的所有实例共有的。继承结构表示多"代"派生,可 以描述成一个"族谱",连续的子 +类,与祖先类都有关系。 + +泛化/特化 +泛化表示所有子类与其父类及祖先类有一样的特点,所以子类可以认为同祖先类是"是一个" 的关系,因 +为一个派生对象(实例)是祖先类的一个"例子"。比如,技工"是一个"人,车"是一 个"交通工具,等等。在上 +面我们间接提到的族谱图中,我们可以从子类到祖先类画一条线,表示 "是一个"的关系。特化描述所有 +子类的自定义,也就是,什么属性让它与其祖先类不同。 + +多态 +多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的 类。多 +态表明了动态(又名,运行时)绑定的存在,允计重载及运行时类型确定和验证。 + +自省/反射 +自省表示给予你,程序员,某种能力来进行像"手工类型检查"的工作,它也被称为反射。这个性质展示了 +某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么 能力,这样的功能 +不是很好吗?这是一项强大的特性,在本章中,你会时常遇到。如果 Python 不支持某种形式的自省功 +能,dir()和 type()内建函数,将很难正常工作。请密切关注这些调用,还有那些特殊属性,像 +__dict__,__name__及__doc__。 + +​ **最简单的情况,类仅用作名称空间(namespaces) :** +意味着你把数据保存在变量中,对他们按名称空间进行分组,使得他们处于同样的关系空间中。 +所谓的关系是使用标准 Python 句点属性标识。 +比如,你有一个本身没有任何属性的类,使用它仅对数据提供一个名字空间。 +这样的类仅作为容器对象来共享名字空间。 + +例: + class MyData(object): + pass + 上面定义的类没有任何方法或属性。 + +​ 一个实例,它只使用类作为名称空间容器。 +​ \>>> mathObj = MyData() +​ \>>> mathObj.x = 4 +​ \>>> mathObj.y = 5 +​ \>>> mathObj.x + mathObj.y +​ 9 +​ \>>> mathObj.x * mathObj.y +​ 20 +当然也可以使用变量"x","y"来完成同样的事情。 +但本例中,实例名字 mathObj 将 mathObj.x 和 mathObj.y 关联起来。 +这就是我们所说的使用类作为名字空间容器。 +mathObj.x 和 mathObj.y 是实例属性,因为它们不是类 MyData 的属性,而是实例对象(mathObj)的独有属性。 +这些属性实质上是动态的:你不需要在构造器中,或其它任何地方为它们预先声明或者赋值 + +​ **静态变量和实例变量** +亦可称为静态数据(类数据属性,也叫静态字段,静态属性)。 +这些数据是与它们所属的类对象绑定的,不依赖于任何类实例。 +如果你熟悉 Java 或 C++,这种类型的数据相当于在一个变量声明前加上 static 关键字。 +静态成员通常仅用来跟踪与类相关的值。 +一般,我们会考虑用实例属性(也叫普通变量或实例变量),而不是类属性。 + +静态变量在内存中只保存一份 +实例变量在每个对象中都保存一份 + +应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态变量 +例1:类数据属性(foo): + \>>> class C(object): + ... foo = 100 + \>>> print C.foo + 100 + \>>> C.foo = C.foo + 1 + \>>> print C.foo + 101 + +注意,上面的代码中,看不到任何类实例的引用 + +例2: + class Province: + \# 静态变量 + country = '中国' + def __init__(self, name): + \# 实例变量 + self.name = name + \# 直接访问普通字段 + obj = Province('河北省') + print obj.name + \# 直接访问静态字段 + Province.country + +​ 要知道一个类有哪些属性,有两种方法。 +​ 1.最简单的是使用 dir()内建函数。 +​ 2.另外是通过访问类的 字典属性__dict__,这是所有类都具备的特殊属性之一。 + +例子: + \>>> class MyClass(object): + ... 'MyClass class definition' #MyClass 类定义 + ... myVersion = '1.1' # static data 静态数据 + ... def showMyVersion(self): # method 方法 + ... print MyClass.myVersion + ... + +​ 根据上面定义的类,让我们使用 dir()和特殊类属性__dict__来查看一下类的属性: +​ \>>> dir(MyClass) +​ ['__class__', '__delattr__', '__dict__', '__doc__', +​ '__getattribute__', '__hash__', '__init__', '__module__', +​ '__new__', '__reduce__', '__reduce_ex__', '__repr__', +​ '__setattr__', '__str__', '__weakref__', 'myVersion', +​ 'showMyVersion'] + +​ \>>> MyClass.__dict__ +​ +​ +​ \>>> print MyClass.__dict__ +​ {'showMyVersion': , +​ '__dict__': , +​ 'myVersion': '1.1', '__weakref__': , +​ '__doc__':'MyClass class definition'} + +​ 在新式类中,还新增加了一些属性,dir()也变得更健壮。 +​ 作为比较,可以看下经典类是什么样的: +​ \>>> dir(MyClass) +​ ['__doc__', '__module__', 'showMyVersion', 'myVersion'] +​ +​ \>>> MyClass.__dict__ +​ {'__doc__': None, 'myVersion': 1, 'showMyVersion': +​ , '__module__': +​ '__main__'} + +从上面可以看到,dir()返回的仅是对象的属性的一个名字列表,而__dict__返回的是一个字典, 它的键(keys)是属性 +名,键值(values)是相应的属性对象的数据值。 +结果还显示了 MyClass 类中两个熟悉的属性,showMyVersion 和 myVersion,以及一些新的属性。这些属 +性,__doc__及__module__,是所有类都具备的特殊类属性(另外还有__dict__)。 + +内建的vars()函数接受类对象作为参数,返回类的__dict__属性的内容。 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**内置类访问__dict__会失败,在内建类型中,不存在这个属性:** +In [1]: a=5 +In [3]: a.__dict__ +\--------------------------------------------------------------------------- +AttributeError Traceback (most recent call last) + in () +----> 1 a.__dict__ + +AttributeError: 'int' object has no attribute '__dict__' + +​ **特殊类属性:** +​ C.__name__ 类C的名字(字符串) +​ C.__doc__ 类C的文档字符串 +​ C.__bases__ 类C的所有父类构成的元组 +​ C.__dict__ 类C的属性 +​ C.__module__ 类C定义所在的模块 +​ C.__class__ 实例C对应的类(仅新式类中) + +根据上面定义的类 MyClass,有如下结果: + \>>> MyClass.__name__ + 'MyClass' + +​ \>>> MyClass.__doc__ +​ 'MyClass class definition' + +​ \>>> MyClass.__bases__ +​ (,) + +​ \>>> print MyClass.__dict__ +​ {'__doc__': None, 'myVersion': 1, 'showMyVersion': +​ , '__module__': '__main__'} + +​ \>>> MyClass.__module__ +​ '__main__' +​ +​ \>>> MyClass.__class__ +​ + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**__name__** + 是给定类的字符名字。 + 它适用于那种只需要字符串(类对象的名字),而非类对象本身的情况。一些内建的类型也有这个属性,我们将会用 +到其中之一来展示__name__字符串的益处。 + +​ 类型对象是一个内建类型的例子,它有__name__的属性。回忆一下,type()返回被调用对象的类型。 +​ 我们可以使用类型对象的__name__属性来取得相应的字符串名。 + +如下例示: + \>>> stype = type('What is your quest?') + \>>> stype # stype is a type object stype 是一个类型对象 + + \>>> stype.__name__ # get type as a string 得到类型名(字符串表示) + 'string' + +​ \>>> type(3.14159265) # also a type object 又一个类型对象 +​ +​ \>>> type(3.14159265).__name__ # get type as a string 得到类型名(字符串表示) +​ 'float' + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**__doc__** + 是类的文档字符串,与函数及模块的文档字符串相似,必须紧随头行(header line) 后的字符串。 + 文档字符串不能被派生类继承,也就是说派生类必须含有它们自己的文档字符串。 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**__dict__** + 包含一个字典,由类的数据属性组成。 + 访问一个类属性的时候,Python 解释器将会搜索字典以得到需要的属性。 + 如果在__dict__中没有找到,将会在基类的字典中进行搜索, 采用"深度优先搜索"顺序。 + 基类集的搜索是按顺序的,从左到右,按其在类定义时,定义父类参数时的顺序。 + 对类的修改会仅影响到此类的字典;基类的__dict__属性不会被改动的 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**__module__** + Python 支持模块间的类继承。为更清晰地对类进行描述,1.5 版本中引入了__module__, 这样类名就完全由模 +块名所限定。看一下下面的例子: + \>>> class C(object): + ... pass + ... + \>>> C + + + \>>> C.__module__ + '__main__' + +​ 类 C 的全名是"__main__.C",比如,source_module.class_name。 +​ 如果类 C 位于一个导入的模块中,如 mymod,像下面的: +​ \>>> from mymod import C + +​ \>>> C +​ + +​ \>>> C.__module__ +​ 'mymod' + +​ 在以前的版本中,没有特殊属性__module__,很难简单定位类的位置,因为类没有使用它们的全名。 +​ +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**__class__** +​ 最后,由于类型和类的统一性,当访问任何类的__class__属性时,你将发现它就是一个类型对象的实例。换句话说, +一个类已是一种类型了。因为经典类并不认同这种等价性(一个经典类是一 个类对象,一个类型是一个类型对象),对 +这些对象来说,这个属性并未定义。 +**__class__到底是什么意思?看例子** +​ \# cat a.py +​ \#!/usr/bin/python +​ \# -*- coding: UTF-8 -*- +​ class A: +​ def __init__(self,url): +​ self.url = url +​ def out(self): +​ return self.url + +​ a = A('news.163.com') +​ print a.out() + +​ b = a.__class__('www.bccn.net') +​ print b.out() + +​ print A +​ print a.__class__ + +​ \# python a.py +​ news.163.com +​ www.bccn.net +​ __main__.A +​ __main__.A + +​ **可以看出a.__class__就等效于a的类A** + +​ 实例属性 vs 类属性 +类属性仅是与类相关的数据值,和实例属性不同,类属性和实例无关。这些值像静态成员那样被引用,即使在多次实例化中调用类, 它们的值都保持不变。不管如何,静态成员不会因为实例而改变它们的值,除非实例中显式改变它们的值。 + +类和实例都是名字空间。类是类属性的名字空间,实例则是实例属性的。 +你可采用类来访问类属性,如果实例没有同名的属性的话,你也可以用实例来访问。 + +下面的示例中,类 C 在创建时,带一个 version 属性,这样通过类对象来访问它是很自然的了,比如,C.version。当实例 c 被创建后,对实例 c 而言,访问 c.version 会失败,不过 Python 首先会在实例中搜索名字 version,然后是类,再就是继承树中的基类。 +本例中,version 在类中被找到了: + \>>> class C(object): # define class 定义类 + ... version = 1.2 # static member 静态成员 ... + \>>> c = C() # instantiation 实例化 + \>>> C.version # access via class 通过类来访问 + 1.2 + \>>> c.version # access via instance 通过实例来访问 + 1.2 + \>>> C.version += 0.1 # update (only) via class 通过类(只能这样)来更新 + \>>> C.version # class access 类访问 + 1.3 + \>>> c.version # instance access, which 实例访问它,其值已被改变 + 1.3 # also reflected change + +只有当使用类引用 version 时,才能更新它的值,像上面的 C.version 递增语句。 如果尝试在实例中设定 +或更新类属性会创建一个实例属性 c.version,后者会阻止对类属性 C.versioin 的访问,因为第一个访问的就是 c.version,这样可以对实例有效地"遮蔽"类属性C.version,直到 c.version 被清除掉。 + +但在**类属性可变**的情况下,一切都不同了: + \>>> class Foo(object): + ... x = {2003: 'poe2'} + ... + \>>> foo = Foo() + \>>> foo.x + {2003: 'poe2'} + \>>> foo.x[2004] = 'valid path' + \>>> foo.x + {2003: 'poe2', 2004: 'valid path'} + \>>> Foo.x # it works!!! 生效了 + {2003: 'poe2', 2004: 'valid path'} + \>>> del foo.x # no shadow so cannot delete 没有遮蔽所以不能删除掉 + Traceback (most recent call last): File "", line 1, in ? + del foo.x + AttributeError: x + +**类属性持久性** +静态成员,如其名所言,任凭整个实例(及其属性)的如何进展,它都不理不采(因此独立于实例)。同时,当一个实例在类属性被修改后才创建,那么更新的值就将生效。 + +​ **方法**(也算是类的属性) +我们改进类的方式之一就是给类添加功能。类的功能有一个更通俗的名字叫方法。在 Python 中,方法定义在类定义中,但只能被实例所调用。 +调用一个方法的最终途径必须这样: +​ (1)定义类(和方法) +​ (2)创建一个实例 +​ (3)最后一步,用这个实例调用方法。 +​ +例如: +​ class MyDataWithMethod(object): # 定义类 +​ def printFoo(self): # 定义方法(实例方法) +​ print 'You invoked printFoo()!' + +self 参数: + 它在所有的方法(实例方法)声明中都存在。这个参数代表实例对象本身,当你用实例调用方法时,由解释器悄悄 + 地传递给方法,所以,你不需要自己传递 self 进来,因为它是自动传入的。 + 一般的方法会需要这个实例(self),而静态方法或类方法不会, 其中类方法需要类而不是实例。 + +现在我们来实例化这个类,然后调用那个方法: +\>>> myObj = MyDataWithMethod() # 创建实例 +\>>> myObj.printFoo() # 现在调用方法 +You invoked printFoo()! + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +​ Python 创建实例后,在实例化过程中,调用__init__()方法,当一个类被实例化时,就可以定义额外的行为,比如, +设定初始值或者运行一些初步诊断代码—主要是在实例被创建后, 实例化调用返回这个实例之前,去执行某些 +特定的任务或设置。 + +创建一个类(类定义) +class AddrBookEntry(object): # 类定义 + 'address book entry class' + def __init__(self, nm, ph): # 定义构造器 + self.name = nm # 设置 name + self.phone = ph # 设置 phone + print 'Created instance for:', self.name + def updatePhone(self, newph): # 定义方法 + self.phone = newph + print 'Updated phone for:', self.name + +创建实例(实例化) + \>>> john = AddrBookEntry('John Doe', '408-555-1212') #为 John Doe 创建实例 + \>>> jane = AddrBookEntry('Jane Doe', '650-555-1212') #为 Jane Doe 创建实例 + 如果不存在默认的参数,那么传给__init__()的两个参数在实例化时是必须的。 访问实例属性 + \>>> john + <__main__.AddrBookEntry instance at 80ee610> + \>>> john.name + 'John Doe' + \>>> john.phone + '408-555-1212' + \>>> jane.name + 'Jane Doe' + \>>> jane.phone + '650-555-1212' + +方法调用(通过实例) + \>>> john.updatePhone('415-555-1212') #更新 John Doe 的电话 + \>>> john.phone + '415-555-1212' + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**__init__()应当返回 None** +采用函数操作符调用类对象会创建一个类实例,也就是说这样一种调用过程返回的对象就是实例,下面示例可以看出: + \>>> class MyClass(object): ... pass + \>>> mc = MyClass() + \>>> mc + <__main__.MyClass instance at 95d390> + +如果定义了构造器,它不应当返回任何对象,因为实例对象是自动在实例化调用后返回的。相 应地,__init__()就不应 +当返回任何对象(应当为 None);否则,就可能出现冲突,因为只能返回实 例。试着返回非 None 的任何其它对象都会 +导致 TypeError 异常: + \>>> class MyClass: + ... def __init__(self): + ... print 'initialized' + ... return 1 + ... + \>>> mc = MyClass() + initialized + Traceback (innermost last): File "", line 1, in ? + mc = MyClass() + TypeError: __init__() should return None + +### python对象销毁(垃圾回收) + + +同Java语言一样,Python使用了引用计数这一简单技术来追踪内存中的对象。 +在Python内部记录着所有使用中的对象各有多少引用。 +一个内部跟踪变量,称为一个引用计数器。 +当对象被创建时,就创建了一个引用计数,当这个对象不再需要时,也就是说,这个对象的引用计数变为0 +时,它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。 + a = 40 # 创建对象 <40> + b = a # 增加引用, <40> 的计数 + c = [b] # 增加引用. <40> 的计数 + +​ del a # 减少引用 <40> 的计数 +​ b = 100 # 减少引用 <40> 的计数 +​ c[0] = -1 # 减少引用 <40> 的计数 +垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。 +循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。 +Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充,垃圾收集器也 +会留心被分配的总量很大(及未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试 +图清理所有未引用的循环。 + +例1: +析构函数 __del__ ,**__del__在****对象****销毁的时候被调用,当对象不再被使用时,__del__方法运行:** +​ \#!/usr/bin/python +​ \# -*- coding: UTF-8 -*- +​ class Point: +​ def __init__( self, x=0, y=0): +​ self.x = x +​ self.y = y +​ def __del__(self): +​ class_name = self.__class__.__name__ +​ print class_name, "销毁" + +​ pt1 = Point() +​ pt2 = pt1 +​ pt3 = pt1 +​ print id(pt1), id(pt2), id(pt3) # 打印对象的id +​ del pt1 +​ del pt2 +​ del pt3 + +以上实例运行结果如下: + 3083401324 3083401324 3083401324 + Point 销毁 + +例2: +\#!/usr/bin/env python +\#coding=utf8 +class InstCt(object): + count = 0 # count is class attr count 是一个类属性 + def __init__(self): # increment count 增加 count + InstCt.count += 1 + def __del__(self): # decrement count 减少 count + InstCt.count -= 1 + def howMany(self): # return count 返回 count + return InstCt.count + +a = InstCt() +b = InstCt() +print b.howMany() +print a.howMany() +del b +print a.howMany() +del a +print InstCt.count + +​ **绑定(绑定及非绑定方法)** +方法仅仅是类内部定义的函数(这意味着方法是类属性而不是实例属性) +为与 OOP 惯例保持一致,Python 严格要求,没有实例,方法是不能被调用的。这种限制即Python所描述的绑定概念(binding),当存在一个实例时,方法才被认为是绑定到那个实例了。没有实例时方法就是未绑定的。任何一个方法 +定义中的第一个参数都是变量 self,它表示调用此方法的实例对象。 + +实例: +In [10]: class A(object): + a=8 + def func(self): + print "这是方法,不能在没有实例的时候调用" + ....: + +In [11]: A.func() +\--------------------------------------------------------------------------- +TypeError Traceback (most recent call last) + in () +----> 1 A.func() + +TypeError: unbound method func() must be called with A instance as first argument (got nothing +instead) + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**self 是什么?** +self 变量用于在类实例方法中引用方法所绑定的实例。因为方法的实例在任何方法调用中总是 作为第一个参数传 +递的,self 被选中用来代表实例,你必须在方法声明中放上 self + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**调用绑定方法** +方法,不管绑定与否,都是由相同的代码组成的。唯一的不同在于是否存在一个实例可以调用此方法。在很多情况下, +程序员调用的都是一个绑定的方法。假定现在有一个 MyClass 类和此类的一个实例 mc,而你想调用 +MyClass.foo()方法。因为已经有一个实例,你只需要调用 mc.foo()就可 以。记得 self 在每一个方法声明中都是作 +为第一个参数传递的。当在实例中调用一个绑定的方法 时,self 不需要明确地传入了。这算是"必须声明 self 作 +为第一个参数"对你的报酬。当你还没有一个实例并且需要调用一个非绑定方法的时候你必须传递 self 参数。 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**调用非绑定方法** +调用非绑定方法并不经常用到。 +需要调用一个还没有任何实例的类中的方法的一个主要的场景是: + 你在派生一个子类,而且你要覆盖父类的方法,这时你需要调用那个父类中想要覆盖掉的构造方法。 + +例子: +class EmplAddrBookEntry(AddrBookEntry): +'Employee Address Book Entry class' # 员工地址记录条目 + def __init__(self, nm, ph, em): + AddrBookEntry.__init__(self, nm, ph) + self.empid = id + self.email = em + +EmplAddrBookEntry 是 AddrBookEntry 的子类,我们重载了构造器__init__()。我们想尽可能多地重用代码, 而 +不是去从父类构造器中剪切,粘贴代码。这样做还可以避免 BUG 传播,因为任何修复都可以传递给子类。这正是我 +们想要的 --- 没有必要一行一行地复制代码。只需要能够调用父类的构造器即可,但该怎么做呢? +我们在运行时没有 AddrBookEntry 的实例。那么我们有什么呢?我们有一个 EmplAddrBookEntry 的实例,它与 +AddrBookEntry 是那样地相似,我们难道不能用它代替呢?当然可以! +当一个 EmplAddrBookEntry 被实例化,并且调用 __init__() 时,其与 AddrBookEntry 的实例只有很少的差别,主 +要是因为我们还没有机会来自定义我们的 EmplAddrBookEntry 实例,以使它与 AddrBookEntry 不同。 +这是调用非绑定方法的最佳地方了。我们将在子类构造器中调用父类的构造器并且明确地传递 (父类)构造器所需要 +的 self 参数(因为我们没有一个父类的实例)。子类中 __init__() 的第一行就是对父类__init__()的调用。我们通过 +父类名来调用它,并且传递给它 self 和其他所需要的参数。一旦调用返回,我们就能定义那些与父类不同的仅存在我 +们的(子)类中的(实例)定制。 + +​ **静态方法:** +​ 仅是类中的函数(不需要实例调用) +​ 普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量的,一个不能访问实例变量和类变量的方法,其实相当于跟类本身已经没什么关系了,它与类唯一的关联就是需要通过类名来调用这个方法 +**类方法:** +​ 通常的方法需要一个实例(self)作为第一个参数,并且对于(绑定的)方法调用来 说,self 是自动传递给这个方法的。 +​ 而对于类方法而言,需要类而不是实例作为第一个参数,它是由解释器传给方法。类不需要特别地命名, 类似 self,不过很多人使用 cls 作为变量名字。(不需要实例调用) + +**staticmethod()和 classmethod()内建函数** +使用对应的内建函数转换它们为相应的类型,并且重新赋值给了相同的变量名。如果没有调用这两个函数,二者都会在 Python 编译器中产生错误,显示需要带 self 的常规方法声明。现在, 我们可以通过类或者实例调用这些函数这没什么不同: +\#!/usr/bin/python +\# -*- coding: UTF-8 -*- +class TestStaticMethod: + def foo(): + print 'calling static method foo()' + foo = staticmethod(foo) #把函数转换成静态方法 +class TestClassMethod: + def foo(cls): **#这里的cls(class缩写)可以是任何字符,只是约定写为cls** + print 'calling class method foo()' + print 'foo() is part of class:', cls.__name__ + foo = classmethod(foo) #把函数转换成类方法 + +TestStaticMethod.foo() **#静态方法可以直接被类调用,不用实例(普通方法没有实例不能被调用)** +a=TestStaticMethod() +a.foo() +TestClassMethod.foo() **#类方法可以直接被类调用,不用实例(普通方法没有实例不能被调用)** +b=TestClassMethod() +b.foo() + +**函数修饰符** +你可以用它把一个函数应用到另个函数对象上, 而且新函数对象依然绑定在原来的变量。我们正是需要它来整理语法。通过使用 decorators,我们可以避免像上面那样的重新赋值: +\#!/usr/bin/python +class TestStaticMethod: + @staticmethod + def foo(): + print 'calling static method foo()' +class TestClassMethod: + @classmethod + def foo(cls): + print 'calling class method foo()' + print 'foo() is part of class:', cls.__name__ + +TestStaticMethod.foo() **#静态方法可以直接被类调用,不用实例(普通方法没有实例不能被调用)** +a=TestStaticMethod() +a.foo() +TestClassMethod.foo() **#类方法可以直接被类调用,不用实例(普通方法没有实例不能被调用)** +b=TestClassMethod() +b.foo() + +​ 如果说类是一种数据结构定义类型,那么实例则声明了一个这种类型的变量。换言之,实例是有生命的类。就像设计完一张蓝图后,就是设法让它成为现实。实例是那些主要用在运行期时的对象,类被实例化得到实例,该实例的类型就是这个被实例化的类 + +初始化: + 通过调用类对象来创建实例 + 很多其它的 OO 语言都提供 new 关键字,通过 new 可以创建类的实例。Python 的方式更加简单。 一旦定义了一个类,创建实例比调用一个函数还容易------不费吹灰之力。 + +​ 实例化的实现,可以使用函数操作符,如下示: +​ \>>> class MyClass(object): # define class 定义类 +​ ... pass +​ \>>> mc = MyClass() # instantiate class 初始化类 + +​ 可以看到,仅调用("calling")类:MyClass(),就创建了类 MyClass 的实例 mc。返回的对象是你所调用类的一个实 +例。当使用函数记法来调用("call")一个类时,解释器就会实例化该对象 + +实例属性 +实例仅拥有数据属性(方法严格来说是类属性),后者只是与某个类的实例相关联的数据值,并且可以通过句点属性标 +识法来访问。这些值独立于其它实例或类。当一个实例被释放后,它的属性同时也被清除了。 + +"实例化"实例属性(或创建一个更好的构造器) +设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。构造器__init()__是设置这 +些属性的关键点之一。 +能够在"运行时"创建实例属性,是 Python 类的优秀特性之一,从 C++或 Java 转过来的人会被 小小的震惊一下,因 +为 C++或 Java 中所有属性在使用前都必须明确定义/声明。 +Python 不仅是动态类型,而且在运行时,允许这些对象属性的动态创建。这种特性让人爱不释手。当然,我们必须提 +醒读者,创建这样的属性时,必须谨慎。 +一个缺陷是,属性在条件语句中创建,如果该条件语句块并未被执行,属性也就不存在,而你在后面的代码中试着去访 +问这些属性,就会有错误发生。故事的精髓是告诉我们,Python 让你体验 从未用过的特性,但如果你使用它了,你还 +是要小心为好。 + +在构造器中首先设置实例属性 +构造器是最早可以设置实例属性的地方,因为__init__()是实例创建后第一个被调用的方法。 再没有比这更早的可以 +设置实例属性的机会了。一旦__init__()执行完毕,返回实例对象,即完成了实例化过程。 + +​ **组合** +​ 一个类被定义后,目标就是要把它当成一个模块来使用,并把这些对象嵌入到你的代码中去, 同其它数据类型及逻辑执行流混合使用。有两种方法可以在你的代码中利用类。 +​ 第一种是组合 (composition)。就是让不同的类混合并加入到其它类中,来增加功能和代码重用性。你可以在一个大点的类中创建你自已的类的实例,实现一些其它属性和方法来增强对原来的类对象。 +​ 另一种方法是通过派生 + +​ 举例来说,让我们想象创建的地址本类的加强性设计。如果在设计的过程中, 为 names,addresses 等等创建了单独的类。那么最后我们可能想把这些工作集成到 AddrBookEntry 类中去,而不是重新设计每一个需要的类。这样就节省了时间和精力,而且最后的结果是容易维护的代码 --- 一块代码中的 bugs 被修正,将反映到整个应用中。 +​ 这样的类可能包含一个 Name 实例,以及其它的像 StreetAddress, Phone(home, work, telefacsimile, +pager, mobile, 等等),Email (home, work, 等等。),还可能需要一些 Date实例 +(birthday,wedding,anniversary,等等)。 + +一个简单的例子: +class NewAddrBookEntry(object): # class definition 类定义 + 'new address book entry class' + def __init__(self, nm, ph): # define constructor 定义构造器 + self.name = Name(nm) # create Name instance 创建 Name 实例 + self.phone = Phone(ph) # create Phone instance 创建 Phone 实例 + print 'Created instance for:', self.name + +NewAddrBookEntry 类由它自身和其它类组合而成。这就在一个类和其它组成类之间定义了一种 "has-a / 有一个"的关系。比如,我们的 NewAddrBookEntry 类"有一个" Name 类实例和一个 Phone 实例。 +创建复合对象就可以实现这些附加的功能,并且很有意义,因为这些类都不相同。每一个类管理它们自己的名字空间和行为。不过当对象之间有更接近的关系时,派生的概念可能对你的应用程序来说更有意义,特别是当你需要一些相似的对象,但却有少许不同功能的时候。 + +​ **概念:** +当类之间有显著的不同,并且(较小的类)是较大的类所需要的组件时,组合表现得很好,但当你设计"相同的类但有 +一些不同的功能"时,派生就是一个更加合理的选择了。 + +OOP 的更强大方面之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响 系统中使用现存 +类的其它代码片段。OOD 允许类特征在子孙类或子类中进行继承。这些子类从基类(或 称祖先类,超类)继承它 +们的核心属性。而且,这些派生可能会扩展到多代。在一个层次的派生关 系中的相关类(或者是在类树图中垂直 +相邻)是父类和子类关系。从同一个父类派生出来的这些类 (或者是在类树图中水平相邻)是同胞关系。父类和所 +有高层类都被认为是祖先 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**创建子类** +创建子类的语法看起来与普通(新式)类没有区别,一个类名,后跟一个或多个需要从其中派生的父类: + class SubClassName (ParentClass1[, ParentClass2, ...]): + 'optional class documentation string' + class_suite + +如果你的类没有从任何祖先类派生,可以使用 object 作为父类的名字。经典类的声明唯一不同之处在于其没有 +从祖先类派生---此时,没有圆括号: + class ClassicClassWithoutSuperclasses: + pass + +例子: +class Parent(object): # define parent class 定义父类 + def parentMethod(self): + print 'calling parent method' + +class Child(Parent): # define child class 定义子类 + def childMethod(self): + print 'calling child method' + +\>>> p = Parent() # instance of parent 父类的实例 +\>>> p.parentMethod() +calling parent method +\>>> c = Child() # instance of child 子类的实例 +\>>> c.childMethod() # child calls its method 子类调用它的方法 +calling child method +\>>> c.parentMethod() # calls parent's method 调用父类的方法 +calling parent method + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**继承** +继承描述了基类的属性如何"遗传"给派生类。一个子类可以继承它的基类的任何属性,不管是数据属性还是方法。 +例子: +P 是一个没有属性的简单类。C 从 P 继承而来(因此是它的子类),也没有属性: + class P(object): # parent class 父类 pass + class C(P): # child class 子类 + pass + +​ \>>> c = C() # instantiate child 实例化子类 +​ \>>> c.__class__ # child "is a" parent 子类"是一个"父类 +​ +​ \>>> C.__bases__ # child's parent class(es) 子类的父类 +​ (,) + +因为 P 没有属性,C 没有继承到什么。下面我们给 P 添加一些属性: + class P: # parent class 父类 + 'P class' + def __init__(self): + print 'created an instance of', self.__class__.__name__ + +​ class C(P): # child class 子类 +​ pass + +现在所创建的 P 有文档字符串(__doc__)和构造器,当我们实例化 P 时它被执行,如下面的交互会话所示: + \>>> p = P() # parent instance 父类实例 + created an instance of P + \>>> p.__class__ # class that created us 显示p所属的类名 + + \>>> P.__bases__ # parent's parent class(es) 父类的父类 + (,) + \>>> P.__doc__ # parent's doc string 父类的文档字符串 + 'P class' + +我们现在来实例化 C,展示 __init__()(构造)方法在执行过程中是如何继承的: + \>>> c = C() # child instance 子类实例 + created an instance of C + \>>> c.__class__ # class that created us 显示c所属的类名 + + \>>> C.__bases__ # child's parent class(es) 子类的父类 + (,) + \>>> C.__doc__ # child's doc string 子类的文档字符串 + +C 没有声明__init__()方法,然而在类 C 的实例 c 被创建时,还是会有输出信息。原因在于 C 继承了 P 的 +__init__()。 +__bases__元组列出了其父类 P。 +需要注意的是文档字符串对类,函数/方法, 还有模块来说都是唯一的,所以特殊属性__doc__不会从基类中继承过 +来。 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**__bases__类属性** +__bases__类属性,对任何(子)类,它是一个包含其父类 (parent)的集合的元组。那些没有父类的类,它们的 +__bases__属性为空。 + +下面我们看一下如何使用__bases__: + \>>> class A(object): pass # define class A 定义类 A + \>>> class B(A): pass # subclass of A A 的子类 + \>>> class C(B): pass # subclass of B (and indirectly, A) B 的子类(A 的间接子类) + \>>> class D(A, B): pass # subclass of A and B A,B 的子类 + \>>> A.__bases__ + (,) + \>>> C.__bases__ + (,) + \>>> D.__bases__ + (, ) + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**通过继承覆盖(Overriding)方法** +我们在 P 中再写一个函数,然后在其子类中对它进行覆盖。 + class P(object): + def foo(self): + print 'Hi, I am P-foo()' + \>>> p = P() + \>>> p.foo() + Hi, I am P-foo() + +现在来创建子类 C,从父类 P 派生: + class C(P): + def foo(self): + print 'Hi, I am C-foo()' + \>>> c = C() + \>>> c.foo() + Hi, I am C-foo() +尽管 C 继承了 P 的 foo()方法,但因为 C 定义了它自已的 foo()方法,所以 P 中的 foo() 方法被覆盖。覆盖方法 +的原因之一是,你的子类可能需要这个方法具有特定或不同的功能。 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**调用被覆盖的基类方法:** +调用一个未绑定的基类方法,需要明确给出子类的实例,例如: + \>>> P.foo(c) + Hi, I am P-foo() +注意,上面已经有了一个 P 的实例 p,但上面的这个例子并没有用它。不需要 P 的实例调用 P 的方法,因为已经有 +一个 P 的子类的实例 c 可用。典型情况下,你不会以这种方式调用父类方法,你会在子类的重写方法里显式地调 +用基类方法。 + class C(P): + def foo(self): + P.foo(self) + print 'Hi, I am C-foo()' +注意,在这个(未绑定)方法调用中我们显式地传递了 self. 一个更好的办法是使用 super() 内建方法: + class C(P): + def foo(self): + super(C, self).foo() + print 'Hi, I am C-foo()' +super()不但能找到基类方法,而且还为我们传进 self,这样我们就不需要做这些事了。现在我们只要调用子类的 +方法,它会帮你完成一切: + \>>> c = C() + \>>> c.foo() + Hi, I am P-foo() Hi, I am C-foo() + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**重写__init__不会自动调用基类的__init__** +类似于上面的覆盖非特殊方法,当从一个带构造器 __init()__的类派生,如果你不去覆盖 __init__(),它将会被继承 +并自动调用。但如果你在子类中覆盖了__init__(),子类被实例化时, 基类的__init__()就不会被自动调用。这可 +能会让了解 JAVA 的朋友感到吃惊。 + class P(object): + def __init__(self): + print "calling P's constructor" + class C(P): + def __init__(self): + print "calling C's constructor" + \>>> c = C() + calling C's constructor + +如果你还想调用基类的 __init__(),你需要明确指出,使用一个子类的实例去调用基类(未绑定)方法。相应地更新 +类 C,会出现下面预期的执行结果: + class C(P): + def __init__(self): + P.__init__(self) + print "calling C's constructor" + +​ \>>> c = C() +​ calling P's constructor +​ calling C's constructor +上边的例子中,子类的__init__()方法首先调用了基类的的__init__()方法。这是相当普遍(不是强制)的做法,用来 +设置初始化基类,然后可以执行子类内部的设置。这个规则之所以有意义的原因是,你希望被继承的类的对象在 +子类构造器运行前能够很好地被初始化或作好准备工作,因为它 (子类)可能需要或设置继承属性。 + +**内建函数super()** +对 C++熟悉的童鞋,可能会在派生类构造器声明时,通过在声明后面加上冒号和所要调用的所有基类构造器这种 +形式来调用基类构造器。而在 JAVA 中,不管程序员如何处理,子类构造器都会去调用基类的的构造器。 + +Python 使用基类名来调用类方法,对应在 JAVA 中,是用关键字 super 来实现的,这就是 super() 内建函数引入 +到 Python 中的原因,这样你就可以"依葫芦画瓢"了: + class C(P): + def __init__(self): + super(C, self).__init__() + print "calling C's constructor" + +使用 super()的漂亮之处在于,你不需要明确给出任何基类名字..."跑腿事儿",它帮你干了! 使用 super()的重点,是你不需要明确提供父类。这意味着如果你改变了类继承关系,你只需要改一行代码(class 语句本身)而不必在大量代码中去查找所有被修改的那个类的名字。 + +​ 每个子类最好定义它自己的构造器,不然,基类的构造器会被调用。 +如果子类重写基类的构造器,基类的构造器就不会被自动调用了--这样,基类的构造器就必须显式写出才会被执行,像 +我们上面那样,用 AddrBookEntry.__init__()设置名字和电话号码。我们的子类在构造器后面几行还设置了另外两 +个实例属性:员工 ID 和 E-mail 地址。 +​ class EmplAddrBookEntry(AddrBookEntry): +​ 'Employee Address Book Entry class'#员工地址本类 +​ def __init__(self, nm, ph, id, em): +​ AddrBookEntry.__init__(self, nm, ph) +​ self.empid = id +​ self.email = em +​ def updateEmail(self, newem): +​ self.email = newem +​ print 'Updated e-mail address for:', self.name + +​ \>>> john = EmplAddrBookEntry('John Doe', '408-555-1212',42, 'john@spam.doe') +​ Created instance for: John Doe #给 John Doe 创建实例 +​ \>>> john +​ <__main__.EmplAddrBookEntry object at 0x62030> + +​ \>>> john.name +​ 'John Doe' +​ \>>> john.phone +​ '408-555-1212' +​ \>>> john.email +​ 'john@spam.doe' +​ \>>> john.updatePhone('415-555-1212') +​ Updated phone for: John Doe +​ \>>> john.phone +​ '415-555-1212' +​ \>>> john.updateEmail('john@doe.spam') +​ Updated e-mail address for: John Doe +​ \>>> john.email +​ 'john@doe.spam' + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +命名类、属性和方法 +类名通常由大写字母打头。这是标准惯例,可以帮助你识别类,特别是在实例化过程中(有时看起来像函数调用)。 +数据属性(变量或常量)应当是数据值的名字,方法名应当指出对应对象或值的行为。 + +另一种表达方式是:数据值应该使用名词作为名字,方法使用谓词(动词加对象)。数据项是操作的对象、方法应当表 +明程序员想要在对象进行什么操作。 +在上面我们定义的类中,遵循了这样的方针,数据值像"name","phone"和"email",行为如"updatePhone", +"updateEmail"。这就是常说的"混合记法(mixedCase)"或"骆驼记法(camelCase)"。Python 规范推荐使用骆驼 +记法的下划线方式,比如,"update_phone","update_email"。 +类也要细致命名, 像"AddrBookEntry","RepairShop"等等就是很好的名字。 + +​ 调用父类的构造函数 +子类(派生类)并不会自动调用父类(基类)的init方法,例如: + +class Foo(object): + def __init__(self): + self.val = 1 +class Foo2(Foo): + def __init__(self): + print self.val +if __name__ == '__main__': + foo2 = Foo2() + +运行时报错。 + +调用父类的init方法有两种: +第一种: +class Foo(object): + def __init__(self): + self.val = 1 +class Foo2(Foo): + def __init__(self): + Foo.__init__(self) + print self.val +if __name__ == '__main__': + foo2 = Foo2() + +第二种: +class Foo(object): + def __init__(self): + self.val = 1 +class Foo2(Foo): + def __init__(self): + super(Foo2,self).__init__() + print self.val +if __name__ == '__main__': + foo2 = Foo2() +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**super** +super用来执行父类中的函数,例如: +class Foo(object): + def hi(self): + print 'hi,Foo' +class Foo2(Foo): + def hi(self): + super(Foo2, self).hi() +if __name__ == '__main__': + foo2 = Foo2() + foo2.hi() + +运行结果: +hi,Foo + +注意,Foo类必须继承某个类(并且这个继承链开始于object类),否则会报错。如果改成下面的形式: +class Foo: + def hi(self): + print 'hi,Foo' +class Foo2(Foo): + def hi(self): + super(Foo2, self).hi() +if __name__ == '__main__': + foo2 = Foo2() + foo2.hi() + +运行时报错如下: +...... +TypeError: must be type, not classobj + +​ 用来对float做四舍五入操作: +In [4]: a=2.888888 + +In [5]: round(a,2) +Out[5]: 2.89 + +In [6]: round(a,3) +Out[6]: 2.889 + +In [7]: round(a,1) +Out[7]: 2.9 + +​ **标准类型派生** +经典类中,一个最大的问题是,不能对标准类型进行子类化。幸运的是,在 2.2 以后的版本中, 随着类型(types)和 +类(class)的统一和新式类的引入, 这一点已经被修正。 +下面,介绍两个子类化 Python 类型的相关例子,其中一个是可变类型,另一个是不可变类型。 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**不可变类型的例子** +假定你想在金融应用中,应用一个处理浮点数的子类。每次你得到一个贷币值(浮点数), 你都需要通过四舍五入, +变为带两位小数位的数值。你的类开始可以这样写: + class RoundFloat(float): + def __new__(cls, val): + return float.__new__(cls, round(val, 2)) +我们覆盖了__new__()特殊方法来定制我们的对象,使之和标准 Python 浮点数(float)有一些区别:我们使用 +round()内建函数对原浮点数进行舍入操作,然后实例化我们的 float,RoundFloat。 我们是通过调用父类的构 +造器来创建真实的对象的,float.__new__()。注意,所有的__new()__方法都是类方法,我们要显式传入类传为 +第一个参数,这类似于常见的方法如__init__()中需要的 self。 +现在的例子还非常简单,比如,我们知道有一个 float,我们仅仅是从一种类型中派生而来等等. 通常情况下,最好是 +使用 super()内建函数去捕获对应的父类以调用它的__new()__方法,下面,对它进行这方面的修改: + class RoundFloat(float): + def __new__(cls, val): + return super(RoundFloat, cls).__new__(cls, round(val, 2)) +这个例子还远不够完整,所以,请留意本章我们将使它有更好的表现。下面是一些样例输出: + \>>> RoundFloat(1.5955) + 1.6 + \>>> RoundFloat(1.5945) + 1.59 + \>>> RoundFloat(-1.9955) + -2.0 +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**可变类型的例子** +子类化一个可变类型与此类似,你可能不需要使用__new__() (或甚至__init__()),因为通常设置不多。一般情况 +下,你所继承到的类型的默认行为就是你想要的。 +下例中,我们简单地创建一 个新的字典类型,它的 keys()方法会自动排序结果: + class SortedKeyDict(dict): + def keys(self): + return sorted(super( SortedKeyDict, self).keys()) + +回忆一下,字典(dictionary)可以由 dict(),dict(mapping),dict(sequence_of_2_tuples), 或者 dict +(**kwargs)来创建,看看下面使用新类的例子: + +​ d = SortedKeyDict((('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))) +​ print 'By iterator:'.ljust(12), [key for key in d] +​ print 'By keys():'.ljust(12), d.keys() + +把上面的代码全部加到一个脚本中,然后运行,可以得到下面的输出: +By iterator: ['zheng-cai', 'xin-yi', 'hui-jun'] By keys(): ['xin-yi', 'hui-jun', 'zheng-cai'] +在上例中,通过 keys 迭代过程是以散列顺序的形式,而使用我们(重写的)keys()方法则将 keys 变为字母排序方 +式了。 +一定要谨慎,而且要意识到你正在干什么。如果你说,“"的方法调用 super()过于复杂",取而代之的是,你更喜欢 +keys()简简单单(也容易理解)....,像这样: + def keys(self): + return sorted(self.keys()) + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +多重继承 +同 C++一样,Python 允许子类继承多个基类。这种特性就是通常所说的多重继承。概念容易, 但最难的工作 +是,如何正确找到没有在当前(子)类定义的属性。当使用多重继承时,有两个不同的方面要记住。首先,还是要找 +到合适的属性。另一个就是当你重写方法时,如何调用对应父类方法以"发挥他们的作用",同时,在子类中处理好 +自己的义务。我们将讨论两个方面,但侧重后者, 讨论方法解析顺序。 + +方法解释顺序(MRO) +在 Python 2.2 以前的版本中,算法非常简单:深度优先,从左至右进行搜索,取得在子类中使用的属性。其它 +Python 算法只是覆盖被找到的名字,多重继承则取找到的第一个名字。 +由于类,类型和内建类型的子类,都经过全新改造, 有了新的结构,这种算法不再可行. 这样一种新的 MRO 算法被 +开发出来,在 2.2 版本中初次登场,是一个好的尝试,但有一个缺陷(看下面的核心笔记)。这在 2.3 版本中立即被 +修改,也就是今天还在使用的版本。 + +精确顺序解释很复杂,超出了本文的范畴,但你可以去阅读本节后面的参考书目提到的有关内 +容。这里提一下,新的查询方法是采用广度优先,而不是深度优先。 + +Python 2.2 使用一种唯一但不完善的 MRO +Python 2.2 是首个使用新式 MRO 的版本,它必须取代经典类中的算法,原因在上面已谈到过。 +在 2.2 版本中,算法基本思想是根据每个祖先类的继承结构,编译出一张列表,包括搜索到的类, 按策略删除重复 +的。然而,在 Python 核心开发人员邮件列表中,有人指出,在维护单调性方面失败过(顺序保存),必须使用新的 +C3 算法替换,也就是从 2.3 版开始使用的新算法。 +下面的示例,展示经典类和新式类中,方法解释顺序有什么不同。 +简单属性查找示例 +下面这个例子将对两种类的方案不同处做一展示。脚本由一组父类,一组子类,还有一个子孙类组成。 +class P1: #(object): # parent class 1 父类 1 def foo(self): + print 'called P1-foo()' +class P2: #(object): # parent class 2 父类 2 def foo(self): + print 'called P2-foo()' + def bar(self): + print 'called P2-bar()' +class C1(P1, P2): # child 1 der. from P1, P2 #子类 1,从 P1,P2 派生 pass +class C2(P1, P2): # child 2 der. from P1, P2 #子类 2,从 P1,P2 派生 def bar(self): + print 'called C2-bar()' +class GC(C1, C2): # define grandchild class #定义子孙类 pass # derived from C1 and C2 #从 C1,C2 派生 +[![file://C:\Users\86186\AppData\Local\Temp\.2IK5Q0\1.png](assets/1-1600310837640.png)]() +父类,子类及子孙类的关系图,还有它们各自定义的方法 + +在图 13-2 中,我们看到父类,子类及子孙类的关系。P1 中定义了 foo(),P2 定义了 foo()和 bar(), C2 定义了 +bar()。下面举例说明一下经典类和新式类的行为。 +经典类 +首先来使用经典类。通过在交互式解释器中执行上面的声明,我们可以验证经典类使用的解释顺序,深度优先,从 +左至右: +\>>> gc = GC() +\>>> gc.foo() # GC ==> C1 ==> P1 +called P1-foo() +\>>> gc.bar() # GC ==> C1 ==> P1 ==> P2 called P2-bar() +当调用 foo()时,它首先在当前类(GC)中查找。如果没找到,就向上查找最亲的父类,C1。查找未遂,就继续沿树上 +访到父类 P1,foo()被找到。 +同样,对 bar()来说,它通过搜索 GC,C1,P1 然后在 P2 中找到。因为使用这种解释顺序的缘故, C2.bar()根本就 +不会被搜索了。 +现在,你可能在想,"我更愿意调用 C2 的 bar()方法,因为它在继承树上和我更亲近些,这样才会更合适。"在这种 +情况下,你当然还可以使用它,但你必须调用它的合法的全名,采用典型的非绑定方式去调用,并且提供一个合法的 +实例: + \>>> C2.bar(gc) + called C2-bar() +新式类 +取消类 P1 和类 P2 声明中的对(object)的注释,重新执行一下。新式方法的查询有一些不同: +\>>> gc = GC() +\>>> gc.foo() # GC ==> C1 ==> C2 ==> P1 called P1-foo() +\>>> gc.bar() # GC ==> C1 ==> C2 called C2-bar() +与沿着继承树一步一步上溯不同,它首先查找同胞兄弟,采用一种广度优先的方式。当查找 foo(),它检查 GC,然 +后是 C1 和 C2,然后在 P1 中找到。如果 P1 中没有,查找将会到达 P2。foo() 的底线是,包括经典类和新式类都 +会在 P1 中找到它,然而它们虽然是同归,但殊途! +然而,bar()的结果是不同的。它搜索 GC 和 C1,紧接着在 C2 中找到了。这样,就不会再继续搜 索到祖父 P1 和 +P2。这种情况下,新的解释方式更适合那种要求查找 GC 更亲近的 bar()的方案。当然,如果你还需要调用上一 +级,只要按前述方法,使用非绑定的方式去做,即可。 + \>>> P2.bar(gc) + called P2-bar() +新式类也有一个__mro__属性,告诉你查找顺序是怎样的: + \>>> GC.__mro__ + (, , , , , ) +菱形效应为难 MRO +经典类方法解释不会带来很多问题。它很容易解释,并理解。大部分类都是单继承的,多重继承只限用在对两个 +完全不相关的类进行联合。这就是术语 mixin 类(或者"mix-ins")的由来。 +为什么经典类 MRO 会失败 +在版本 2.2 中,类型与类的统一,带来了一个新的"问题",波及所有从 object(所有类型的祖先类)派生出来的(根) +类,一个简单的继承结构变成了一个菱形。从 Guido van Rossum 的文章中得到下面的灵感,打个比方,你有经 +典类 B 和 C,C 覆盖了构造器,B 没有,D 从 B 和 C 继承而来: +class B: pass + class C: + def __init__(self): + print "the default constructor" + class D(B, C): + pass +当我们实例化 D,得到: + \>>> d = D() + the default constructor +图 13.3 为 B,C 和 D 的类继承结构,现在把代码改为采用新式类的方式,问题也就产生了: + class B(object): + pass + class C(object): + def __init__(self): + print "the default constructor" + [![file://C:\Users\86186\AppData\Local\Temp\.2IK5Q0\2.png](assets/2-1600310837640.png)]() + + 图 13.3 继承的问题是由于在新式类中,需要出现基类,这样就在继承结构中,形成了一个菱形。D 的实例上溯时,不应当错过 C,但不能两次上溯到 A(因为 B 和 C 都从 A 派生)。去读读 Guido van Rossum 的文章中有关"协作方法"的部分,可以得到更深地理解。 +代码中仅仅是在两个类声明中加入了(object),对吗?没错,但从图中,你可以看出,继承结构已变成了一个菱形;真正的问题就存在于 MRO 了。如果我们使用经典类的 MRO,当实例化 D 时,不再得到 C.__init__()之结果.....而是得到 object.__init__()!这就是为什么 MRO 需要修改的真正原因。 +尽管我们看到了,在上面的例子中,类 GC 的属性查找路径被改变了,但你不需要担心会有大量的代码崩溃。经典类将沿用老式 MRO,而新式类将使用它自己的 MRO。还有,如果你不需要用到新式类中的所有特性,可以继续使用经典类进行开发,不会有问题的。 + +总结 +经典类,使用深度优先算法。因为新式类继承自 object,新的菱形类继承结构出现,问题也就接着而来了,所以必 +须新建一个 MRO。 +你可以在下面的链接中读在更多有关新式类、MRO 的文章: +Guido van Rossum 的有关类型和类统一的文章: http://www.python.org/download/releases/2.2.3/descrintro +PEP 252:使类型看起来更像类 http://www.python.org/doc/peps/pep-0252 +"Python 2.2 新亮点" 文档 http://www.python.org/doc/2.2.3/whatsnew +论文:Python 2.3 方法解释顺序 http://python.org/download/releases/2.3/mro/ + Python的可变类型与不可变类型 + +Python的每个对象都分为可变和不可变,主要的核心类型中,数字、字符串、元组是不可变的,列表、字典是 +可变的。 +对不可变类型的变量重新赋值,实际上是重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的 +对象(如果没有其他变量引用原有对象的话(即引用计数为0),原有对象就会被回收)。 + +不可变类型以int类型为例:实际上 i += 1 并不是真的在原有的int对象上+1,而是重新创建一个value为6的int +对象,i引用自这个新的对象。 +\>>> i = 5 +\>>> i += 1 +\>>> i +6 +通过id函数查看变量i的内存地址进行验证(使用hex(id(i)) 可以查看16进制的内存地址) + +\>>> i = 5 +\>>> i += 1 +\>>> i +6 +\>>> id(i) +140243713967984 +\>>> i += 1 +\>>> i +7 +\>>> id(i) +140243713967960 +可以看到执行 i += 1 时,内存地址都会变化,因为int 类型是不可变的。 +再改改代码,但多个int类型的变量值相同时,看看它们内存地址是否相同。 +\>>> i = 5 +\>>> j = 5 +\>>> id(i) +140656970352216 +\>>> id(j) +140656970352216 +\>>> k = 5 +\>>> id(k) +140656970352216 +\>>> x = 6 +\>>> id(x) +140656970352192 +\>>> y = 6 +\>>> id(y) +140656970352192 +\>>> z = 6 +\>>> id(z) +140656970352192 + +对于不可变类型int,无论创建多少个不可变类型,只要值相同,都指向同个内存地址。同样情况的还有比较短 +的字符串。 + +对于其他类型则不同,以浮点类型为例,从代码运行结果可以看出它是个不可变类型:对i的值进行修改后,指 +向新的内存地址。 +\>>> i = 1.5 +\>>> id(i) +140675668569024 +\>>> i = i + 1.7 +\>>> i +3.2 +\>>> id(i) +140675668568976 +修改代码声明两个相同值的浮点型变量,查看它们的id,发现它们并不是指向同个内存地址,这点和int类型不 +同(这方面涉及Python内存管理机制,Python对int类型和较短的字符串进行了缓存,无论声明多少个值相同 +的变量,实际上都指向同个内存地址。)。 +\>>> i = 2.5 +\>>> id(i) +140564351733040 +\>>> j = 2.5 +\>>> id(j) +140564351733016 + +可变类型的话,以list为例。list在append之后,还是指向同个内存地址,因为list是可变类型,可以在原处修 +改。 +\>>> a = [1, 2, 3] +\>>> id(a) +4385327224 +\>>> a.append(4) +\>>> id(a) +4385327224 +改改代码,当存在多个值相同的不可变类型变量时,看看它们是不是跟可变类型一样指向同个内存地址 +\>>> a = [1, 2, 3] +\>>> id(a) +4435060856 +\>>> b = [1, 2, 3] +\>>> id(b) +4435102392 +从运行结果可以看出,虽然a、b的值相同,但是指向的内存地址不同。我们也可以通过b = a 的赋值语句,让 +他们指向同个内存地址: +\>>> a = [1, 2, 3] +\>>> id(a) +4435060856 +\>>> b = [1, 2, 3] +\>>> id(b) +4435102392 +\>>> b = a +\>>> id(b) +4435060856 +这个时候需要注意,因为a、b指向同个内存地址,而a、b的类型都是List,可变类型,对a、b任意一个List进 +行修改,都会影响另外一个List的值。 +\>>> b.append(4) +\>>> a +[1, 2, 3, 4] +\>>> b +[1, 2, 3, 4] +\>>> id(a) +4435060856 +\>>> id(b) +4435060856 +代码中,b变量append(4),对a变量也是影响的。输出他们的内存地址,还是指向同个内存地址。 + +​ __init__ 方法是什么? +__init__ 方法通常用在初始化一个类实例的时候,类似 java 等 OO 语言里的构造器。 +例如: +class Person(object): +​ """Silly Person""" +​ def __init__(self, name, age): +​ self.name = name +​ self.age = age + +​ def __str__(self): +​ return '' % (self.name, self.age) + +if __name__ == '__main__': + piglei = Person('piglei', 24) + print piglei +这样便是__init__最普通的用法了。但__init__其实不是实例化一个类的时候第一个被调用的方法。当使用 +Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法其实是 __new__ 方法。 + +__new__ 方法是什么? +__new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正 +是创建这个类实例的方法。 +class Person(object): + """Silly Person""" + def __new__(cls, name, age): + print '__new__ called.' + return super(Person, cls).__new__(cls, name, age) + +​ def __init__(self, name, age): +​ print '__init__ called.' +​ self.name = name +​ self.age = age + +​ def __str__(self): +​ return '' % (self.name, self.age) + +if __name__ == '__main__': + piglei = Person('piglei', 24) + print piglei +执行结果: +piglei@macbook-pro:blog$ python new_and_init.py +__new__ called. +__init__ called. + +通过运行这段代码,我们可以看到,__new__方法的调用是发生在__init__之前的。其实当 你实例化一个类的 +时候,具体的执行逻辑是这样的: + +p = Person(name, age) +首先执行使用name和age参数来执行Person类的__new__方法,这个__new__方法会 返回Person类的一个 +实例(通常情况下是使用 super(Persion, cls).__new__(cls, ... ...) 这样的方式), +然后利用这个实例来调用类的__init__方法,上一步里面__new__产生的实例也就是 __init__里面的的 self +所以,__init__ 和 __new__ 最主要的区别在于: + +__init__ 通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。 +__new__ 通常用于控制生成一个新实例的过程。它是类级别的方法。 +但是说了这么多,__new__最通常的用法是什么呢,我们什么时候需要__new__? + +3、__new__ 的作用 +依照Python官方文档的说法,__new__方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供 +给你一个自定义这些类的实例化过程的途径。还有就是实现自定义的metaclass。 + +首先我们来看一下第一个功能,具体我们可以用int来作为一个例子: + +假如我们需要一个永远都是正数的整数类型,通过集成int,我们可能会写出这样的代码。 +class PositiveInteger(int): + def __init__(self, value): + super(PositiveInteger, self).__init__(self, abs(value)) + +i = PositiveInteger(-3) +print i +但运行后会发现,结果根本不是我们想的那样,我们任然得到了-3。这是因为对于int这种 不可变的对象,我们 +只有重载它的__new__方法才能起到自定义的作用。 +这是修改后的代码: +class PositiveInteger(int): + def __new__(cls, value): + return super(PositiveInteger, cls).__new__(cls, abs(value)) + +i = PositiveInteger(-3) +print i +通过重载__new__方法,我们实现了需要的功能。 +另外一个作用,关于自定义metaclass。其实我最早接触__new__的时候,就是因为需要自定义 metaclass,但鉴于篇幅原因,我们下次再来讲python中的metaclass和__new__的关系。 + +4、用__new__来实现单例 + +事实上,当我们理解了__new__方法后,我们还可以利用它来做一些其他有趣的事情,比如实现 设计模式中的 单例模式(singleton) 。 +因为类每一次实例化后产生的过程都是通过__new__来控制的,所以通过重载__new__方法,我们 可以很简单的实现单例模式。 +class Singleton(object): + def __new__(cls): + \# 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象 + if not hasattr(cls, 'instance'): + cls.instance = super(Singleton, cls).__new__(cls) + return cls.instance + +obj1 = Singleton() +obj2 = Singleton() + +obj1.attr1 = 'value1' +print obj1.attr1, obj2.attr1 +print obj1 is obj2 +输出结果: +value1 value1 +True +可以看到obj1和obj2是同一个实例。 + +class Singleton(object): + __instance = None + +​ def __init__(self, *args, **kwargs): +​ pass + +​ def __new__(cls, *args, **kwargs): +​ if not cls.__instance: +​ \# if not hasattr(cls, 'instance'): +​ cls.__instance = super(Singleton, cls).__new__(cls, *args, **kwargs) +​ cls.__instance.aa = args[0] +​ print type(cls), type(cls.__instance), type(cls.__instance.aa) +​ return cls.__instance + +obj1 = Singleton(1, 2, 3, b=2) +obj2 = Singleton(1, 2, 3, b=2) + +obj1.attr1 = 'value1' +obj2.attr2 = 'value2' +print obj1.attr1, obj1.attr2 +print obj1 is obj2 +print obj1.aa, obj2.attr1 + +结果: + +value1 value2 +True +1 value1 + +​ **类、实例和其他对象的内建函数** +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**issubclass()** +issubclass() 布尔函数判断一个类是另一个类的子类或子孙类。 +语法: +​ issubclass(sub, sup) + +issubclass() 返回True的情况: + \1. 给出的子类sub确实是父类sup的一个子类(反之,则为False)。 + \2. 这个函数也允许"不严格"的子类,意味着,一个类可视为其自身的子类,所以,这个函数如果当 sub 就是 sup,或者从 sup 派生而来,则返回 True。(一个"严格的"子类是严格意义上的从一个类派生而来的子类。) + \3. 从 Python 2.3 开始,issubclass()的第二个参数可以是可能的父类组成的 tuple(元组),这时, 只要第一个参数是给定元组中任何一个候选类的子类时,就会返回 True。 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**isinstance()** +isinstance() 布尔函数在判定一个对象是否是另一个给定类的实例时,非常有用。 + +语法: + isinstance(obj1, obj2) + isinstance()在 obj1 是类 obj2 的一个实例,或者是 obj2 的子类的一个实例时,返回 True (反之,则为 False) + +例子: + \>>> class C1(object): pass + \>>> class C2(object): pass + \>>> c1 = C1() + \>>> c2 = C2() + \>>> isinstance(c1, C1) True + \>>> isinstance(c2, C1) False + \>>> isinstance(c1, C2) False + \>>> isinstance(c2, C2) True + \>>> isinstance(C2, c2) + Traceback (innermost last): + File "", line 1, in ? + isinstance(C2, c2) + TypeError: second argument must be a class + +注意:第二个参数应当是类;不然,你会得到一个 TypeError。但如果第二个参数是一个类型对象,则不会出现异常。这是允许的,因为你也可以使用 isinstance()来检查一个对象 obj1 是否是 obj2 的类型,比如: + \>>> isinstance(4, int) + True + \>>> isinstance(4, str) + False + \>>> isinstance('4', str) + True + +isinstance()也可以使用一个元组(tuple)作为第二个参数。如果第一个参数是第二个参数中给定元组的任何一个候选类型或类的实例时,就会返回 True。 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**hasattr(), getattr(),setattr(), delattr()** +*attr()系列函数可以在各种对象下工作,不限于类(class)和实例(instances),因为在类和实例中使用极其频繁,就在这里列出来了。 + +hasattr()函数是 Boolean 型的,它的目的就是为了决定一个对象是否有一个特定的属性,一般用于访问某属性前先作一下检查。 + +getattr()取得对象的属性,会在你试图读取一个不存在的属性时,引发 AttributeError 异常,除非给出那个可选的默认参数。 + +setattr()将要么加入一个新的属性,要么取代一个已存在的属性。 + +delattr()函数会从一个对象中删除属性。 + +下面一些例子使用到了*attr()系列函数: +\>>> class myClass(object): +... def __init__(self): +... self.foo = 100 +\>>> myInst = myClass() +\>>> hasattr(myInst, 'foo') +True +\>>> getattr(myInst, 'foo') +100 +\>>> hasattr(myInst, 'bar') +False +\>>> getattr(myInst, 'bar') +Traceback (most recent call last): +File "", line 1, in ? +getattr(myInst, 'bar') +AttributeError: myClass instance has no attribute 'bar' +\>>> getattr(c, 'bar', 'oops!') +'oops!' +\>>> setattr(myInst, 'bar', 'my attr') +\>>> dir(myInst) +['__doc__', '__module__', 'bar', 'foo'] +\>>> getattr(myInst, 'bar') # same as myInst.bar +'my attr' +\>>> delattr(myInst, 'foo') +\>>> dir(myInst) +['__doc__', '__module__', 'bar'] +\>>> hasattr(myInst, 'foo') +False + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**dir()** +用 dir()列出一个模块所有属性的信息, dir()还可以用在对象上。 +dir()提供的信息比以前更加详尽。根据文档,"除了实例变量名和常用方法外,它还显示那些通过特殊标记来调用的方法,像__iadd__(+=),__len__(len()), __ne__(!=)。 +dir()作用在实例上(经典类或新式类)时,显示实例变量,还有在实例所在的类及所有它的基类中定义的方法和类属性. +dir()作用在类上(经典类或新式类)时,则显示类以及它的所有基类的__dict__中的内容。 但它不会显示定义在元类(metaclass)中的类属性. +dir()作用在模块上时,则显示模块的__dict__的内容. +dir()不带参数时,则显示调用者的局部变量. + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**super()** +super()函数的目的就是帮助程序员找出相应的父类, 然后方便调用相关的属性。一般情况下,程序员可能仅仅采用非绑定方式调用祖先类方法。使用 super()可以简化搜索一个合适祖先的任务,并且在调用它时,替你传入实例或类型对象。 +每个定义的类,都有一个名为__mro__的属性,它是一个元组,按照他们被搜索时的顺序,列出了备搜索的类。 +语法如下: + super(type[, obj]) +给出 type,super()"返回此 type 的父类"。如果你希望父类被绑定,你可以传入 obj 参数(obj 必须是 +type 类型的).否则父类不会被绑定。obj 参数也可以是一个类型,但它应当是 type 的一个子类。通常,当给出 obj 时: + \1. 如果obj是一个实例,isinstance(obj,type)就必须返回True + \2. 如果obj是一个类或类型,issubclass(obj,type)就必须返回True + +事实上,super()是一个工厂函数,它创造了一个 super object,为一个给定的类使用__mro__ 去查找相应的父类。很明显,它从当前所找到的类开始搜索 MRO。 +super()的主要用途,是来查找父类的属性,比如, super(MyClass,self).__init__()。如果你没有执行这样的查找,你可能不需要使用 super()。 + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**vars()** +vars()内建函数与 dir()相似,只是给定的对象参数都必须有一个__dict__属性。vars()返回一 个字典,它包含了对象存储于其__dict__中的属性(键)及值。如果提供的对象没有这样一个属性, 则会引发一个 TypeError 异常。如果没有提供对象作为 vars()的一个参数,它将显示一个包含本地名字空间的属性(键)及其值的字典,也就是locals()。 + +例子,使用类实例调用 vars(): + \>>> class C(object): + ... pass + \>>> c = C() + \>>> c.foo = 100 + \>>> c.bar = 'Python' + \>>> c.__dict__ + {'foo': 100, 'bar': 'Python'} + \>>> vars(c) + {'foo': 100, 'bar': 'Python'} + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +​ **私有化** +默认情况下,属性在 Python 中都是"public",类所在模块和导入了类所在模块的其他模块的代码都可以访问到。很多 OO 语言给数据加上一些可见性,只提供访问函数来访问其值。这就是实现隐藏,是对象封装中的一个关键部分。 +大多数 OO 语言提供"访问控制符"来限定成员函数的访问。 + +双下划线开头双下划线结尾的是一些 Python 的特殊对象,如类成员的 __init__、__del__、__add__、__getitem__等。 Python 官方推荐永远不要将这样的命名方式应用于自己的变量或函数 +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**双下划线(__)** +Python 为类元素(属性和方法)的私有性提供初步的形式。 +由双下划线开始的属性在运行时被"混淆",所以直接访问是不允许的。实际上,会在名字前面加上下划线和类名。 +self.__num 属性为例: + 被"混淆"后,用于访问这个数据值的标识就变成了 self._NumStr__num。把类名加上后形成的新的"混淆"结果将可以防止在祖先类或子孙类中的同名冲突。 +尽管这样做提供了某种层次上的私有化,但算法处于公共域中并且很容易被"击败"。这更多的是一种对导入源代码无法获得的模块或对同一模块中的其他代码的保护机制. + +这种名字混淆的另一个目的,是为了保护__XXX 变量不与父类名字空间相冲突。如果在类中有一个__XXX 属性,它将不会被其子类中的__XXX 属性覆盖。(回忆一下,如果父类仅有一个 XXX 属性, 子类也定义了这个,这时,子类的 XXX 就是覆盖了父类的 XXX,这就是为什么你必须使用 PARENT.XXX 来调用父类的同名方法。) +使用__XXX,子类的代码就可以安全地使用__XXX,而不必担心它会影响到父类中的__XXX。 + +例1: +In [14]: class A(object): + ....: __name = "wing" + ....: + +In [15]: a=A() + +In [16]: a.__name +AttributeError Traceback (most recent call last) + in () +----> 1 a.__name +AttributeError: 'A' object has no attribute '__name' + +In [18]: a._A__name +Out[18]: 'wing' + +例2: +In [30]: class C(object): + ....: name = "wing" + +In [31]: class D(C): + ....: __name = "tom" + +In [32]: d = D() + +In [33]: d.name +Out[33]: 'wing' + +In [34]: d._D__name +Out[34]: 'tom' + +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**单下划线(_)** +简单的模块级私有化只需要在属性名前使用一个单下划线字符,防止模块的属性用"from mymodule +import *"来加载。这是严格基于作用域的,所以这同样适合于函数。 + +假设test.py内容如下: +import sys +_modulenum = 3 +__modulenum = 3 +__modulenum__ = 3 +pubnum = 3 +def _fun(): + print 'this is',sys._getframe().f_code.co_name +def __fun(): + print'this is ', sys._getframe().f_code.co_name +def __fun__(): + print'this is ', sys._getframe().f_code.co_name +def fun(): + print'this is ', sys._getframe().f_code.co_name + +\>>> from test import * + +\>>>_modulenum +... +NameError: name'_modulenum' is not defined + +\>>>__modulenum +... +NameError: name'__modulenum' is not defined + +\>>>__modulenum__ +... +NameError: name'__modulenum__' is not defined + +\>>>pubnum +3 + +\>>> _fun() +... +NameError: name'_fun' is not defined + +\>>> __fun() +... +NameError: name'__fun' is not defined + +\>>>__fun__() + +... +NameError: name'__fun__' is not defined + +\>>> fun() +this is fun + +\>>> import test +\>>>test._modulenum +3 +\>>>test.__modulenum +3 +\>>>test.__modulenum__ +3 +\>>>test.pubnum +3 +\>>>test._fun() +this is _fun +\>>>test.__fun() +this is __fun +\>>>test.__fun__() +this is __fun__ +\>>>test.fun() +this is fun +\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +注意,私有化都是针对外部而言,在类内部,依然可以使用正常的访问方式,在类的外部就必须进行"混淆"了。比如: +class test(object): + __cversion =1.1 + def__init__(self): + self.__iversion= 1.2 + deffun(self): + printself.__iversion + printtest.__cversion + printself._test__iversion + printself._test__cversion +\>>> from test import test +\>>> t1 = test() +\>>> t1.fun() +1.2 +1.1 +1.2 +1.1 +\>>> t1.__iversion +Traceback (most recent call last): + File"", line 1, in +AttributeError: 'test' object has no attribute'__iversion' +\>>> t1._test__iversion +1.2 +\>>> t1.__cversion +Traceback (most recent call last): + File"", line 1, in +AttributeError: 'test' object has no attribute'__cversion' +\>>> t1._test__cversion +1.1 + +​ 外翻 + +代码混淆 +代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和 +理解的形式的行为。代码混淆可以用于程序源代码,也可以用于程序编译而成的中间代码。执行代码混淆的程 +序被称作代码混淆器。目前已经存在许多种功能各异的代码混淆器。 + +将代码中的各种元素,如变量,函数,类的名字改写成无意义的名字。比如改写成单个字母,或是简短的无意 +义字母组合,甚至改写成"__"这样的符号,使得阅读的人无法根据名字猜测其用途。重写代码中的部分逻辑, +将其变成功能上等价,但是更难理解的形式。比如将for循环改写成while循环,将循环改写成递归,精简中间 +变量,等等。打乱代码的格式。比如删除空格,将多行代码挤到一行中,或者将一行代码断成多行等等。 + +代码混淆器也会带来一些问题。主要的问题包括: +被混淆的代码难于理解,因此调试以及除错也变得困难起来。开发人员通常需要保留原始的未混淆的代码用于 +调试。对于支持反射的语言,代码混淆有可能与反射发生冲突。代码混淆并不能真正阻止反向工程,只能增大 +其难度。因此,对于对安全性要求很高的场合,仅仅使用代码混淆并不能保证源代码的安全。 + +​ **Python新式类和经典类** +Python从2.2开始,引入了 new style class(新式类) +Python 2.x中默认都是经典类,只有显式继承了object才是新式类 +Python 3.x中默认都是新式类,不必显式的继承object + +新式类跟经典类的差别主要是以下几点: +• 新式类对象可以直接通过__class__属性获取自身类型:type +• 继承搜索的顺序发生了改变 +• 新式类增加了__slots__内置属性, 可以把实例属性的种类锁定到__slots__规定的范围之中 +• 新式类增加了__getattribute__方法 + +\1. 新式类对象可以直接通过__class__属性获取自身类型:type +\#cat test1.py +\#/usr/bin/env python2.7 + class E: + \#经典类 + pass + + class E1(object): + \#新式类 + pass + + e = E() + print "经典类" + print e + print type(e) + print e.__class__ + + print "新式类" + e1 = E1() + print e1 + print e1.__class__ + print type(e1) + +输出结果: + 经典类 + <__main__.E instance at 0x0000000002250B08> + + __main__.E + + 新式类 + <__main__.E1 object at 0x0000000002248710> + + + +​ E1是定义的新式类。那么输出e1的时候,不论是type(e1),还是e1.__class__都是输出的。 + +\2. 继承搜索的顺序发生了改变 +经典类多继承属性搜索顺序: 先深入继承树左侧,再返回,开始找右侧 +新式类多继承属性搜索顺序: 先水平搜索,然后再向上移动 +\#cat test2.py + class A(object): + '新式类 ,作为所有类的基类' + def foo(self): + print "class A" + + class A1(): + '经典类,作为所有类的基类 ' + def foo(self): + print "class A1" + + class C(A): + pass + + class C1(A1): + pass + + class D(A): + def foo(self): + print "class D" + + class D1(A1): + def foo(self): + print "class D1" + + class E(C, D): + pass + + class E1(C1, D1): + pass + + e = E() + e.foo() + + e1 = E1() + e1.foo() + +输出结果: + class D + class A1 + +因为A新式类,对于继承A类都是新式类,首先要查找类E中是否有foo(),如果没有则按顺序查找C->D->A。它是一种广度优先查找方式。 + +因为A1经典类,对于继承A1类都是经典类,首先要查找类E1中是否有foo(),如果没有则按顺序查找C1->A1->D1。它是一种深度优先查找方式。 + +\3. 新式类增加了__slots__内置属性, 可以把实例属性的种类锁定到__slots__规定的范围之中。 + +比如只允许对A实例添加name和age属性: +\#cat test3.py + class A(object): + __slots__ = ('name', 'age') + + class A1(): + __slots__ = ('name', 'age') + + a1 = A1() + a = A() + + a1.name1 = "a1" + a.name1 = "a" + +A是新式类添加了__slots__ 属性,所以只允许添加 name age +A1经典类__slots__ 属性没用 + +执行结果: + Traceback (most recent call last): + File "t.py", line 13, in + a.name1 = "a" + AttributeError: 'A' object has no attribute 'name1' +所以a.name是会出错的 + +通常每一个实例都会有一个__dict__属性,用来记录实例中所有的属性和方法,也是通过这个字典,可以让实例绑定任意的属性。 +__slots__属性作用就是,当类C有比较少的变量,而且拥有__slots__属性时,类C的实例就没有__dict__属性,而是把变量的值存在一个固定的地方。如果试图访问一个__slots__中没有的属性,实例就会报错。 +这样操作的好处:__slots__属性虽然令实例失去了绑定任意属性的便利,但是因为每一个实例没有__dict__属性,却能有效节省每一个实例的内存消耗,有利于生成小而精干的实例。 + +\4. 新式类增加了__getattribute__方法 +\#cat test4.py +class A(object): + def __getattribute__(self, *args, **kwargs): + print "A.__getattribute__" + +class A1(): + def __getattribute__(self, *args, **kwargs): + print "A1.__getattribute__" + +a1 = A1() +a = A() + +a.test +print "=========" +a1.test + +A.__getattribute__ +========= +Traceback (most recent call last): + File "t.py", line 18, in + a1.test +AttributeError: A1 instance has no attribute 'test' + +A是新式类,每次通过实例访问属性,都会经过__getattribute__函数, +A1不会调用__getattribute__所以出错了 \ No newline at end of file