python概要笔记1

python概要笔记1
chenxin 2017/05 update

函数返回值
函数返回值可以是多个.本质上,是个tuple!但是,在语法上,返回一个tuple可以省略括号.
而多个变量可以同时接收一个tuple,按位置赋给对应的值.
所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。

参数

1.参数分类
位置参数(即必选参数)
默认参数(默认参数必须指向不变对象,如a=1,b=2)
可变参数 *args
关键字参数 **kwargs (提供的是键值对形式)
命名关键字参数 name, age, *, city, job 或者 name, age, *args, city, job.(提供的是指定名称的形式)

可变参数:
def calc(numbers):
pass
nums = [1, 2, 3]
calc(
nums)
调用(这种list或tupple方式非常常见)Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去.

关键字参数:
extra表示把extra这个dict的所有key-value用关键字参数传入到函数的kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。

命名关键字参数:
对关键字提供了名称.比如def func(a=1,b=2,c=3).
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个作为特殊分隔符。如果缺少,Python解释器将无法识别位置参数和命名关键字参数

2.参数的赋值顺序: 位置参数(必选参数)->默认参数->可变参数->命名关键字参数->关键字参数

3.万能的函数调用方式
对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的

小结
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:*args是可变参数,args接收的是一个tuple;**kw是关键字参数,kw接收的是一个dict。

调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过args传入:func((1, 2, 3));
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过kw传入:func()。

命名关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

递归

双面平行的镜子彼此映射就属于递归现象.
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰
如何使用"n-1的表达式"来表示"n表达式的值"的问题.也就是用函数自身来表达自身诉求.

递归的剖析
阶乘
n的值: 1 2 3 4 5 ...
f(n)的值:1 2 6 24 120 ...
... f(n-3) f(n-2) f(n-1) f(n)
f(n)如何由前面表示出来呢? 就是: f(n) = f(n-1)*n 这就是递归的核心分析方法和算法.

关键就是看第n个元素对应的结果是多少(假设x),第n-1个元素的结果(假设y),以及第n-2个元素的结果(假设z)...,x由y和z计算而来.这里的计算,就是f函数,也就是f(n-1)和f(n-2)如何来表示f(n).也可能只需要f(n-1)和n一起来表示f(n).也可能还需要用到f(n-2).

具体到这个阶乘的例子,则是f(n)=f(n-1)*n #注意,这里没有用到f(n-2)就可以做了.
最关键的就是把n从1往后列出来,并对应列出来第n个对应的值f(n),最后找出f(n)和f(n-1)的关系(这个关系就是递归主要执行的函数体内容).再比如

奇数这里需要执行的就是:
return func(n-1)+2
斐波那契额要执行的就是:
return fib(n-1) + fib(n-2)
汉诺塔这里需要执行的内容就是:
hannuota(n-1, a, c, b)
hannuota(1, a, b, c)
hannuota(n-1, b, a, c)

以下为网上内容摘贴:
每个递归都有一个递归终结点,否则就变成无穷递归了.
阶乘: jx(n) = n * jx(n-1)
def fb(n):
if n == 1:
return n
else:
return n * fb(n-1)
print(fb(3))

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

网友对递归的总结:
关于递归函数,就是将一个值n赋值给函数后,此时函数的值是f(n),然后它可以通过不断地调用前面的f(n-1),f(n-2),f(n-3)....f(n-i)......一直到f(2),f(1),再利用前面的f(1),f(2)的值逐步计算出后面的......f(n-3),f(n-2),f(n-1),最终计算出f(n)的值。因此,这里就必须给定一个基础的值,如f(1)的值,然后才能利用它逐步计算出后面的各个函数值。常规思维来看,我们会偏向于避免这种既费时又费力的计算方法,而计算机恰恰是通过这样的笨办法来计算的。
一个很好的例子就是汉诺塔的算法:
题目:请编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法。
分析:如果给定是n个盘子,那么问题就很明确了,分成3个步骤:
(1)把前n-1个盘子由 A 借助C移到 B;
(2)把剩下的1个盘子由 A 借助B移到 C;
(3)再把前n-1个盘子由 B 借助A移到 C。
然后再规定,只有1个盘子时,方法是直接从A移动到C 。接下来就是递归函数的计算问题了。

汉诺塔

def hannuota(n, a, b, c):
if n == 1:
print(a, '-->', c)
else:
hannuota(n-1, a, c, b)
hannuota(1, a, b, c)
hannuota(n-1, b, a, c)
hannuota(3, 'A', 'B', 'C')
关键是由n-1推导出n

自己想了很久的对列表求最大值的递归题

递归求最大值

def zuidazhi(L):
n = len(L)-1
if n == -1:
print('None')
exit()
if n == 0:
return L[0]
else:
if L[n] > L[n-1]:
L.pop(n-1)
return zuidazhi(L)
else:
L.pop()
return zuidazhi(L)
print(zuidazhi([1, -2, 5, -100, 20]))
这里的关键是每次比较,都将较小的值用pop给弹出去,接着用递归去比较剩下的值和当前值

切片
可以用于列表,元组,字符串.
(0,1,2,3,4,5)[:] #这个也是序列的复制
(0,1,2,3,4,5)[2:4] (0,1,2,3,4,5)[2:-1] (0,1,2,3,4,5)[:3] (0,1,2,3,4,5)[2:] [0,1,2,3,4,5,6,7][::2] 'I am Chenxin'[5:]
[起始:结束:带方向的步进]

迭代(可迭代对象)

默认情况下,dict迭代的是key。
如果要迭代dict的value,可以用for value in d.values().如果要同时迭代key和value,可以用for k, v in d.items().
可遍历的对象,就是可以迭代的.
检查对象是否是可迭代对象,使用collections模块的Iterable类型来判断:from collections import Iterable

枚举1个列表
for index, value in enumerate(['a','b','c']) #for循环可以同时引入2个变量.内置的enumerate函数(枚举的意思)可以把一个list变成索引-元素对(index是默认的0,1,2...).

列表生成式
列表生成式
list(range(1,11)) #输出 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[x * x for x in range(1, 11) if x % 2 == 0] #输出 [4, 16, 36, 64, 100]
[m + n for m in 'ABC' for n in 'XYZ'] #输出 ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
L = ['Abc', 'IBM', 18, None, 'Chenxin']
L2 = [s.lower() for s in L if isinstance(s, str)] #输出 ['abc', 'ibm', 'chenxin']

(i for i in range(5)) // 返回迭代器.这个不是列表生成式 -> <generator object at 0x7ff3e8f0d960>
[i for i in range(5)] // 返回list.列表解析,返回list.是列表生成式 -> [0, 1, 2, 3, 4]
在这里存在一个问题,那就是range(5)会返回一个长度为5的数据,如果是range(1000)那么就会占用一个1000大小的数组空间;如果我们采用生成器,在需要的时候产生一个数字,那么空间的占用情况就会降低,这里我们可以使用xrange()函数来实现(python3以后已经统一改为range了)。

迭代器
可以节省内存.

概念说明
可迭代对象: 包括集合数据类型(list、tuple、dict、set、str)+生成器+生成器函数,这些都可以使用for遍历.
生成器和生成器函数: 具有next方法.可以使用for遍历.
迭代器: 可以被next()函数调用并不断返回下一个值的对象称为迭代器(本身是一个数据流).

iterator那些行为上像迭代器的东西都可以叫做迭代器。python中每一种容器型别都必须提供自己的迭代器。事实上每一种容器都将其迭代器以嵌套的方式定义于内部。因此各种迭代器的接口相同,型号却不同。这直接导出了泛型程序设计的概念:所有操作行为都使用相同接口,虽然它们的型别不同。(感觉有点多态的意思)
迭代器使开发人员提供一个迭代器即可遍历类中的数据结构。当编译器检测到迭代器时,将自动生成相关的接口方法.

创建一个迭代器有3种方法

1为容器对象添加 iter() 和 next() 方法(Python 2.7 中是 next())
方法__iter__() 返回迭代器对象本身 self,next() 则返回每次调用 next() 或迭代时的元素;

2内置函数 iter() 将可迭代对象转化为迭代器.迭代器的创建跟函数无关,可以用iter([1,2])来创建。

3第三种就是生成器和生成器函数
生成器通过 yield语句快速生成迭代器,省略了复杂的 iter() & next() 方式.简单来说,yield 语句可以让普通函数变成一个生成器函数,并且相应的 next() 方法返回的是 yield 后面的值。

def container(start, end):
while start < end:
yield start
start += 1
c = container(0, 5)
print(type(c)) # <class 'generator'>
print(next(c)) #0
next(c) #这里调用next()后,yield又往后推了一次.#没有将1显示出来,但结果为1的是被"next(c)"调用过了.所以后面print直接是2的值.
for i in c:
print(i) #2 3 4 5 (所以这里没有显示"1")

在for循环中,Python将自动调用工厂函数iter()获得迭代器,自动调用next()获取元素,还完成了检查StopIteration异常的工作。

你也可以自己实现一个迭代器,如上所述,只需要在类的__iter__方法中返回一个对象,这个对象拥有一个next()方法,这个方法能在恰当的时候抛出StopIteration异常即可。但是需要自己实现迭代器的时候不多,即使需要,使用生成器会更轻松。

生成器
可以节省内存.

生成器用处:用yield就可以随心所欲地创造想要的迭代器了,且可以随意控制,更灵活多变。
带有yield的都是生成器.列表生成式都可以写成生成器,将中括号[]改为小括号()就可以了.

generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而函数变成generator后,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行.反复调用,能返回多次yield语句的值。把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代.

生成器的编写方法和函数定义类似,只是在return的地方改为yield。
生成器又是一个迭代器,每次迭代时返回为yield返回的值。
需要注意的是,生成器中不需要return语句,在生成器中已经存在默认的返回语句.

可迭代对象,迭代器 和 生成器 区别
都是Python特有的概念.

可迭代对象必须是定义了__iter__()方法的对象.

迭代器可以看作是一个特殊的对象,必须是定义了__iter__()方法和next()方法的对象.每次调用该对象时会返回自身的下一个元素,next()

生成器(或生成器函数)是能够返回一个迭代器的函数,含有yield,其最大的作用是将输入对象返回为一个迭代器。

Python中使用了迭代的概念,是因为当需要循环遍历一个较大的对象时,传统的内存载入方式会消耗大量的内存,不如需要时读取一个元素的方式更为经济快捷(迭代器的核心作用)。
事实上,生成器本质上就是迭代器,只不过在用户不可见的层面上被优化了,本意是让你写迭代器更方便(更灵活).
生成器用"函数"的方式把不停调用__next__()的过程封装在了一起,每当遇到yield的时候就相当于一次next调用的结束。这不仅仅是让迭代器写起来更简单,更能让迭代更加灵活。因为迭代器只能简单的不停的调用相同且固定的next函数,而生成器则可以对这个调用过程进行灵活的控制。

函数式编程(包括高阶函数 返回函数 匿名函数 闭包 装饰器 多个小括号 地址含义 等内容)

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数.
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

高阶函数
函数名在内存中只是一个指向一个计算过程的地址。
把函数作为参数传入,这样的函数称为高阶函数,也就是函数式编程.

高阶函数中常规使用的几个高阶函数说明

map/reduce
map接受2个参数,第一个参数是函数,第二个参数是个iterable.
reduce接受2个参数.第一个参数是函数,第二个参数是个list(这里可能错了,应该是个iterable就可以).且第一个参数(函数)必须接收2个参数的方式.(from functools import reduce).

filter
filter接收2个参数,第一个参数是个函数,第二个参数是个序列.
filter的关键在实现一个"筛选"函数.和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

filter的用法

def shaizi(n):
if n % 3 == 0:
return n
relt = filter(shaizi, [1, 2, 3, 5, 7, 9, 12])
print(list(relt))

sorted
sorted函数可以接收1-3个参数(序列,函数,关键字?),也可以接收2个参数(序列+key函数(其中key是指向某个函数的入口)),也可以接收3个参数(序列+key函数+顺序变量).
key函数只是作用于序列的每一个元素,得到要进行排序的值而已,排序规则则是由系统自己决定的.用sorted()排序的关键在于实现一个映射函数。
print(sorted([2, -3, 5, -9])) #[-9, -3, 2, 5]
print(sorted([2, -3, 5, -9], key=abs)) #[2, -3, 5, -9]
print(sorted(['Zoo', 'abc', 'Foo', 'hoo'], key=str.lower)) #['abc', 'Foo', 'hoo', 'Zoo']

关于 返回函数 名字 地址 小括号 函数名称后有多个小括号 的说明
这个是函数式编程和"闭包","装饰器"等概念的核心内容.

f()表示,f为函数的入口地址(类似指针),()代表的是当前就执行运算.
如果在其他函数调用f()的时候,就是调用的f函数的计算结果.
如果调用的是f,不带小括号,则是函数的入口地址(就是个指针),函数本身并不立即进行运算(只有在调用函数执行的时候,也就是有小括号的时候,才进行运算).

函数也是一个变量,只有把参数传进去了才会返回想要的结果。
参数如何传进去呢?就是通过小括号传进去,否则永远是一个变量,和其他普通变量一样可以任性的把它赋值给其他变量.

一个函数可以返回一个计算结果,也可以返回一个函数。返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。

调用一个函数是加一个括号。如果看见括号后还有一个括号,说明第一个函数返回了一个函数,如果后面还有括号,说明前面那个也返回了一个函数。

自己的理解: 函数名,类名,可以看做是一个内存入口地址. "函数名()"可以看做是一个对象整体.

闭包
闭包使得局部变量在函数外被访问成为可能。

一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是自由变量。

可以形象的把它理解为一个封闭的包裹,这个包裹就是一个函数(当然还有函数内部对应的逻辑,包裹里面的东西就是自由变量,自由变量可以在随着包裹到处游荡)。当然还得有个前提,这个包裹是被创建出来的。

基于上面的介绍,不知道读者有没有感觉这个东西和类有点相似,相似点在于他们都提供了对数据的封装。不同的是闭包本身就是个方法。和类一样,我们在编程时经常会把通用的东西抽象成类,以复用通用的功能。闭包也是一样,当我们需要函数粒度的抽象时,闭包就是一个很好的选择。

在这点上闭包可以被理解为一个只读的对象,你可以给他传递一个属性,但它只能提供给你一个执行的接口。
因此在程序中我们经常需要这样的一个函数对象——闭包,来帮我们完成一个通用的功能,比如后面会提到的——装饰器。

def print_msg(): # print_msg 是外围函数
msg = "zen of python"
def printer(): # printer 是嵌套函数
print(msg)
return printer #这里就是闭包的核心.返回了一个入口地址
another = print_msg()
another() # 输出 zen of python

一般情况下,函数中的局部变量仅在函数的执行期间可用,一旦 print_msg() 执行过后,我们会认为 msg变量将不再可用。然而,在这里我们发现 print_msg 执行完之后,在调用 another 的时候 msg 变量的值正常输出了,这就是闭包的作用,闭包使得局部变量在函数外被访问成为可能。

匿名函数 函数返回值 入口 括号

lambda:x2 就等价于 def g() return x2
lambda x:x2 就等价于 def g(x) return x2

一.例子:
1.def f(n):
return n * n
print(f(3)) -->9

2.print(list(map(lambda n: n * n, [3]))) -->[9]

3.def f(n):
return lambda: n * n #返回的是函数入口地址
print(f(3)()) -->9 #后面的空()代表执行运算

4.def f(n):
def g():
return n * n
return g
print(f(3)()) -->9
1返回的是个int值.
2-4返回的最初都是个函数,必须加了()才对函数进行计算.

二.例子:
for i in range(1,4):
fs.append(lambda i=i:i+i) #从这里就可以看出来,i=i,第一个和第二个是不同的.此i非彼i.(应该是作用域的问题,只是名称相同,实际不是一个变量).
网友的理解:
为什么一个i=i就可以让函数正常运行了,而lambda i:i+i却不可以呢
个人理解,i=i是赋值,前一个i是lambda参数,而后一个i则是for循环中的i。
lambda a=i:a+a,这样写可能更好理解。
lambda i:i+i,未给i赋值,需要传入值才可以正常运行。

一个总结(关于匿名函数,闭包,函数参数)
1.如果你定义一个有参数的函数,返回函数是一个无参函数,那么将定义的有参函数赋值给一个变量(赋值后变量指针指向函数,这时变量就是函数的别名)时,需要传递参数,调用函数变量就等于执行函数体
def build_return_func1(x, y):
def g():
return x2 + y2
return g # 返回函数
def build_return_lambda1(x, y):
return lambda: x ** 2 + y ** 2 #返回lambda匿名函数.无参数lambda匿名函数
f1 = build_return_func1(1, 2) # 有参函数调用
f2 = build_return_lambda1(2, 4) # 有参函数调用
print(f1()) #->5
print(f2()) #->20

2.如果你定义一个无参数的函数,返回函数是一个有参函数,那么将定义的无参函数赋值给一个变量(赋值后变量指针指向函数,这时变量就是函数的别名)时,不需要转递参数,调用函数变量时传递参数就等于执行函数体
def build_return_func2():
def g(x, y):
return x2 + y2
return g # 返回函数
def build_return_lambda2():
return lambda x, y: x ** 2 + y ** 2 #返回lambda匿名函数.有参数lambda匿名函数
f3 = build_return_func2() # 无参函数调用
f4 = build_return_lambda2() # 无参函数调用
print(f3(1, 2)) #->5
print(f4(2, 4)) #->20

装饰器
参考: https://foofish.net/python-decorator.html
在实际使用中,非常常见.装饰器详细信息请参考装饰器专门笔记内容.
小虫经过装饰器变大龙过程.

一个装饰器定义好后,可以给很多个不同的函数,对他们进行包装.
支持使用标识符@将装饰器应用在函数上,只需要在函数的定义前加上@和装饰器的名称。
(可以)将普通函数的入口作为装饰器的参数.
import logging

def use_logging(func):
def wrapper(args, **kwargs):
logging.warn("%s is running" % func.name)
return func(
args, **kwargs)
return wrapper

@use_logging
def foo(name, age=None, height=None, *args, **kwargs):
print("I am %s, age %s, height %s" % (name, age, height))

foo("Chenx", 20, 100, "Chongzh")

带参数的装饰器
def use_logging(level):
def decorator(func):
def wrapper(args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.name)
elif level == "info":
logging.info("%s is running" % func.name)
return func(
args, **kwargs)
return wrapper
return decorator

@use_logging(level="warn")
def foo(name='foo'):
print("I am %s" % name)

foo()

装饰器小结
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

偏函数
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
int2 = functools.partial(int, base=2)
创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数.

这里引用网上一个更加好理解的例子.
偏函数是将所要承载的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数后续的参数,除非使用关键字参数。

无关键字的例子:
我们实现一个取余函数,100%m的余数。
from functools import partial
def mod( n, m ):
return n % m
mod_by_100 = partial( mod, 100 )
print mod( 100, 7 ) # 2
print mod_by_100( 7 ) # 2

由于之前看到的例子一般选择加法或乘法来讲解,无法体会偏函数参数的位置问题,容易给人造成partial的第二个参数也是原函数的第二个参数的假象,所以我在这里选择mod来讲解。
而对于有关键字参数的情况下,就可以不按照原函数的参数位置和个数了。

安装第三方包/模块
1.基本使用: 略

2.参数解释# pip --help
Usage: pip [options]
pip install xxxpackage 安装包.
pip uninstall xxxpackage 卸载包.

3.模块搜索路径: 略

类和实例
关于类的一些详细资料,OOP资料,可以参考专门的<<Python面向对象编程-OOP>>笔记.
这里只是最核心的一些概念.那里还有一些稍深入的介绍.

类名通常是大写开头的单词,紧接着是(object)从object类继承下来的.
注意到__init__方法的第一个参数永远是self,表示创建的实例本身.因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。

数据封装
把数据和函数(方法)都放在类里面,叫做封装.
这样,我们从外部看Student类,就只需要知道,创建实例需要给出name和score.而如何打印,都是在Student类内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节.
OOP小结
各个实例拥有的数据都互相独立,互不影响;(也是封装)
方法就是与实例绑定的函数,方法可以直接访问实例的数据;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同.

访问限制(变量访问权限)
public
无下划线 public.公有public, 内外皆可随意引用

_private
单下划线前缀,表示非公有.不能用'from module import '导入,不导入所有以下划线开头的对象,包括包、模块、成员._x代表不能直接访问的类属性,需通过类提供的接口进行访问.父类的 _x 在子类中可以直接访问(虽然在类外也可以被类的用户直接访问), 姑且可以当做可以在类内或同类对象中使用的@protected成员

__private
双下划线开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
双下划线开头的命名对解释器有特定含义: Python会改写这些名称,以免与子类中定义的名称产生冲突。
双下划线开头的命名形式在 Python 的类成员中使用表示名字改编 (Name Mangling),即如果有一 Test 类里有一成员 __x,那么 dir(Test) 时会看到 _Test__x 而非 __x。这是为了避免该成员的名称与子类中的名称冲突。但要注意这要求该名称末尾没有下划线。
在类外通过_Test__x依然可以访问该变量__x
子类无法覆盖父类的__x, 只会定义一个自己的 _子类名__x
__x 在子类中需要通过 __父类名_x 来访问
比如:zhangsan._Student__score = 10 #张三得分10分.

doc
前后都有双下划线.指那些包含在用户无法控制的命名空间中的“魔术”对象或属性,如类成员的__name__、docinitimportfile、等。永远不要将这样的命名方式应用于自己的变量或函数。

class_
在解析时并没有特别的含义.一个后下划线:避免关键字冲突。比如我们需要一个变量 class,但 class 是 Python 的关键词,就可以以单下划线结尾写作 class_.

特别说明
私有变量的一个注意事项

bart.__name = 'New Name' # 设置__name变量!>>> bart.__name #'New Name'
表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。

例子解析:(含标准私有变量,非标准私有变量(单下划线),外部访问方法,外部赋值方法,实例新建自己的类以外的属性)
class Student(object):
def init(self, name, score, sex='Male'):
self.__name = name #标准私有变量(自己给的定义,暂时这么叫吧)
self.__score = score
self._sex = sex #非标准私有变量(约定俗成的格式)
def get_name(self): #对私有变量的访问方法
return self.__name
def get_score(self):
return self.__score
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('Bad value')
def get_sex(self):
return self._sex

zhangsan.set_score(150)
print(zhangsan.get_score()) #这里会报错,get_score方法会对值进行检查,如果不在0-100范围,则抛出异常
zhangsan._Student__score = 101
print(zhangsan.get_score()) #101.再赋值的时候,跳过了对象的方法中的检查过程,直接对私有变量进行了赋值(强烈建议不要这么干)
zhangsan.__score = 59 #其实这里就是前面讲到的,不是赋值,而是产生了一个新的对象的属性.这个属性叫做__score,而非_Student__score(原先定义的Student类根本没有这个属性).
print(zhangsan.get_score()) #101 所以这里的值仍然是之前的赋值
print(zhangsan.__score) #59 打印的是这个对象额外附加的一个属性值

print(dir(zhangsan)) #['_Student__name', '_Student__score', 'class', 'delattr', 'dict', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'gt', 'hash', 'init', 'le', 'lt', 'module', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', '__score', 'setattr', 'sizeof', 'str', 'subclasshook', 'weakref', '_sex', 'get_name', 'get_score', 'get_sex', 'set_score']

继承和多态
多态
面向对象的三大特性:封装、继承、多态。
从一定角度来看,封装和继承几乎都是为多态而准备的。
多态的定义:指允许不同类的对象对同一消息做出响应(接口的多种不同实现方式就叫多态)。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。
实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
多态的作用:消除类型之间的耦合关系。

静态语言 vs 动态语言
对于Java这样的静态语言来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了.
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

举例说明:
class Animal(object): #基类Animal
def run(self):
print('Animal is running!')
class Dog(Animal): #子类Dog继承基类
def run(self):
print('Dog is running!')
class Cat(Animal): #子类Cat只是继承了Animal类,没有自己再重新定义其他的东西.
pass

def run_twice(animal): # 这里为什么是小写的animal,而不是大些的呢?因为大些的会和类Animal名字冲突.
animal.run() #这里的'animal'只是个普通对象.跟Animal类没有任何关系
animal.run()
run_twice(Animal()) #Animal is running!Animal is running!
run_twice(Dog()) #Dog is running!Dog is running!
run_twice(Cat()) #Animal is running!Animal is running!

class Tort(Animal): #子类"乌龟"继承基类
def run(self):
print('Tort is running slowly...')
run_twice(Tort()) #Tort is running slowly...Tort is running slowly...

class Car(object): #这个Car类跟Animal类没有任何关系,只是定义了一个run方法而已.
def run(self): #但run_twice函数不会管这个事情,它只管你有没有定义run方法. -->这个就是动态语言的特性.java没有这个特性的,但java有多态
print('The Car is running')
run_twice(Car()) #The Car is runningThe Car is running

获取对象信息
type()
判断一个对象是否是函数呢?
import types
types.FunctionType
types.BuiltinFunctionType
types.LambdaType
types.GeneratorType

isinstance()

dir() 获取一个对象的所有属性和方法.返回一个包含字符串的list.
len('ABC')等价'ABC'len()
剩下的都是普通属性和方法,比如lower()返回小写的字符串.

操作一个对象: getattr(), setattr(), hasattr() #这3个方法,支持任意的对象.

小结
通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。
1.type()可以判断几乎任何类型,但是用来判断类之间的继承关系就很不方便
2.isinstance()用来判断类的继承关系就很方便了,type()可以判断的基本数据类型,isinstance也能判断
3.isinstance()还可以判断一个变量是否是某些类型的一种.isinstance([1,2,3],(list, tuple))#True
4.dir()获得一个对象的所有属性和方法
5.'ABC'.len() 等价于 len('ABC')
6.getattr() 如果对象中没有,可以设置返回默认值:getattr(obj,'z',404) #404

绑定方法 未绑定方法 区别(python3已经没有绑定和未绑定的区别了)

bound和unbound方法是个很简单的概念。在许多语言当中,类似于a.b()这样的调用方法是一个整体,但在Python中,它其实是两部分:获取属性a.b,调用()。所以也可以写成:
c = a.b
c()
跟直接调用a.b()是等效的。当a是某个类的实例,b是这个类的方法的时候,a.b的返回值就是bound method,也就是绑定方法。它实际上是个bound method对象,这个对象提前将self参数进行了绑定。实际演示一下就很容易懂了:

class A(object):
... def b(self):
... pass
a = A()
a.b
<bound method A.b of <main.A object at 0x0000000002C1ABA8>>
A.b

相应的unbound method是没有绑定self的对象。在Python 3中,它就是普通的函数,在Python 2中则是unbound method类型,不过区别不大。
我们知道像A.b这样的方法实际上跟一个普通定义的函数没有本质区别,这个函数有一个参数self,所以实际上完全可以用A.b(a)的方式来调用,也就是手工将self参数指定为a。这也就是unbound method的用法。而相应的,bound method是一个实现了__call__的对象,它自动将调用这个对象的过程重定向到A.b(a)上面,相当于通过functools.partial绑定了第一个参数的效果,所以叫做bound method。
内部实现机制上来说,类的方法通过descriptor机制在运行时返回bound method对象,跟@property 本质上是一样的

关于MethodType的说明
动态的给类加一个新功能,通常使用如下的第一种方式(第二种也可以,但注意区别)

比较:

from types import MethodType

def set_age(self, age):
self.age = age

class Stu(object):
pass
Stu.set_age = set_age #会有多块内存空间分别给不同实例用
A = Stu()
B = Stu()
A.set_age(10)
B.set_age(15)
print(A.age, B.age)
def set_age(self, age):
self.age = age

class Stu(object):
pass
Stu.set_age=MethodType(set_age,Stu) #指向方法的指针
A = Stu()
B = Stu()
A.set_age(10)
B.set_age(15)
print(A.age, B.age)
输出 10 15
输出 15 15

解析
Stu类本身并没有属性和方法,所以用这个类创建的实例也没有属性和方法。
用MethodType将set_age方法绑定到Stu类,并不是将这个方法直接写到Stu类内部,而是在Stu内存中创建一个link指向外部的方法,在创建Stu实例的时候这个link也会被复制。所以不管创建多少实例,这些实例和Stu类都指向同一个set_age方法。A.set_age(10)并没有在A这个实例内部创建age属性,而是将age属性创建在外部set_age方法的内存区中。因为A和B内部link都指向外部set_age方法的内存区,所以不管A还是B在调用set_age方法的时候改变的是set_age方法内存区里的age属性,所以A改了B也就改了,如果新建一个实例C在没有调用set_age方法的前提下也会有age属性,因为C的link指向的set_age方法的内存区,而set_age之前被A或者B调用过了。
如果这里MethodType分别绑定到实例(而非上面的Stu类):A.set_age=MethodType(set_age,A);B.set_age=MethodType(set_age,B)则结果也分别是10,15.

给类 绑定方法,可以直接使用Student.set_score = set_score或者Student.set_score = MethodType(set_score,Student)这两种方式(这两种是有差异的).
给实例绑定方法,只有通过s.set_age = MethodType(set_age, s)这一种方式.

正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
给实例绑定方法要用到: from types import MethodType ->s.set_age = MethodType(set_age, s) #注意这里对应的是实例
也可以给类绑定方法,以便所有实例都可以使用这个方法: Stu.set_score = set_score , 或者Stu.set_score=MethodType(set_age,Stu) #注意这里对应的是类,且2种有区别.

动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

限制给实例绑定属性/方法 slots
如果要限制后期动态的给类添加功能怎么办呢?
比如,只允许对Student的实例添加name和age属性。为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
class Student(object):
slots = ('name', 'age') # 用tuple定义允许绑定的属性名称.
slots__定义的属性仅对当前类实例起作用,对继承的子类不起作用.除非在子类中也定义__slots,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__.

@property 属性装饰器
参考: https://www.jb51.net/article/134148.htm python @property的用法及含义

@property使方法像属性一样调用,就像是一种特殊的属性.
将方法(获取和设置属性的方法,主要就是getter,setter)通过python的@property属性函数将函数转换成属性来使用.

@property使用场景1 - 防止别人无意或疏忽的错误赋值.防止别人疏漏造成变量被修改.
定义一个长方形类,然后设置长方形的长宽属性,通过实例化的方式调用长和宽,像如下代码一样。

class Rectangle(object):
def init(self):
self.width =10
self.height=20
r=Rectangle()
print(r.width,r.height)
此时输出结果为10 20

但是这样在实际使用中会产生一个严重的问题,init 中定义的属性是可变的,换句话说,是使用一个系统的所有开发人员在知道属性名的情况下,可以进行随意的更改(尽管可能是在无意识的情况下),但这很容易造成严重的后果。

...
r.width=1.0
print(r.width,r.height)
以上代码结果会输出宽1.0,可能是开发人员不小心点了一个小数点上去,但是会系统的数据错误,并且在一些情况下很难排查。

这是生产中很不情愿遇到的情况,这时候就考虑能不能将width属性设置为私有的,其他人不能随意更改的属性,如果想要更改只能依照我的方法来修改,@property就起到这种作用(类似于java中的private)

class Rectangle(object):
@property
def width(self):
return self.true_width #变量名不与方法名重复,改为true_width,下同
@property
def height(self):
return self.true_height
s = Rectangle()
s.width = 1024 #与方法名一致. 因为没有setter方法,执行到这里会报错: AttributeError: can't set attribute.另外,
s.height = 768 #注意,这里的赋值是把1024赋值给函数,就像赋值给一个普通变量一样.所以说@property是属性装饰器.
print(s.width,s.height)

正如上面代码执行结果显示,此时在外部想要给width重新赋值就会报AttributeError: can't set attribute的错误,这样就保证的属性的安全性。那么,真正需要修改属性的时候怎么办呢?为了解决对属性的操作,提供了封装方法的方式进行属性的修改(包括访问,修改,删除).

class Rectangle(object):

  @property
  def width(self): 
        return self.true_width       # 变量名不与方法名重复,改为true_width,下同
  @width.setter
  def width(self, input_width):
        self.true_width = input_width

  @property
  def height(self):
        return self.true_height
  @height.setter
  def height(self, input_height):      #与property定义的方法名要一致
        self.true_height = input_height

s = Rectangle()
s.width = 1024 # 与方法名一致.看着就像是变量赋值一样.如果不想让别人赋值,就不给@width.setter那段就可以了.这样在赋值的时候则会报上例的错误.
s.height = 768
print(s.width,s.height)

上面就是对“属性”进行赋值操作,同样的方法还del,用处是删除属性.

@property使用场景2 - 到底是属性还是函数(像访问属性一样来访问方法,即把方法当成属性来用的办法)
开始我们定义了一个简单的类,可以直接对类里的属性赋值.后来,客户要求我们赋值的时候需要做检查,我们答应后,使用方法来做检查.但这时,其他的代码也要跟着修改,以便兼容将以前的属性变成方法.为了避免修改其他代码的工作,python引入了property(属性函数)这个功能.

class Student(object):
@property #这个就是getter了(读)
def score(self): #第1次定义score函数
return self._score #可以用私有变量
@score.setter #这个就是setter了(写)
def score(self, value): #这是第2次定义score函数了
pass
self._score = value

还可以定义只读属性-只定义getter方法,不定义setter方法就是一个只读属性(这样别人在调用的时候,就没办法赋值了):比如下面的age函数(属性).
class Student(object):
@property # 这里定义了一个只读属性
def birth(self):
return self._birth
@birth.setter #这里定义了一个只写属性
def birth(self, value):
self._birth = value
@property #这里只定义了一个只读属性(相当于只有getter方法,没有定义setter方法)
def age(self):
return 2015 - self._birth
上面的birth是可读写属性,而age就是一个只读属性(因为age可以根据birth和当前时间计算出来)

小结:
@property提供了可读可写可删除的操作,如果像只读效果,就只需要定义@property就可以,不定义代表禁止其他操作。
@property 是继承或者说限定一个不变的属性,他只能读取,不能接收(也就是getter)
@继承者.setter 是将上面的不变属性进行数据接收并且进行数据传递的(也就是setter)

反射(reflect反射)
参考: http://www.liujiangblog.com/course/python/48 https://www.cnblogs.com/vipchenwei/p/6991209.html
反射就是通过字符串的形式,导入模块;通过字符串的形式,去模块寻找指定函数,并执行。利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动!

比如,Django路由的使用,就是典型的反射.
def run():
inp = input("请输入您想访问页面的url: ").strip()
modules, func = inp.split("/")
obj = import("lib." + modules, fromlist=True) # 使用Python内置的__import__(字符串参数)函数,可以实现类似getattr()的反射功能。import()方法会根据字符串参数,动态地导入同名的模块。注意fromlist参数.
if hasattr(obj, func): #那就是如果用户输入一个非法的url,比如jpg,由于在commons里没有同名的函数,肯定会产生运行错误.python提供了一个hasattr()的内置函数,用法和getattr()基本类似,它可以判断commons中是否具有某个成员,返回True或False。
func = getattr(obj, func) #func = getattr(commons,inp)语句是关键,通过getattr()函数,从commons模块里,查找到和inp字符串“外形”相同的函数名,并将其返回,然后赋值给func变量。变量func此时就指向那个函数,func()就可以调用该函数。getattr()函数的使用方法:接收2个参数,前面的是一个类或者模块,后面的是一个字符串,注意了!是个字符串!这个过程就相当于把一个字符串变成一个函数名的过程。这是一个动态访问的过程,一切都不写死,全部根据用户输入来变化。
func()
else:
print("404")
if name == 'main':
run()

多重继承
通过继承,子类就可以扩展父类的功能.通过多重继承,一个子类就可以同时获得多个父类的所有功能。
MixIn
为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。让某个动物同时拥有好几个MixIn:(多个父类)
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

小结
由于Python允许使用多重继承,MixIn就是一种常见的设计。只允许单一继承的语言(如Java)不能使用MixIn的设计。
第一个继承是生父,后面的都是继父,所以,几个父类里有重复的方法,首先选择生父的优先.
使用slots要注意,slots定义的属性仅对当前类起作用,对继承的子类是不起作用的,如果子类中也定义slots,这样,子类允许定义的属性就是自身的slots加上父类的slots,如果子类中没有定义slots,父类的slots不会对子类造成影响.
不过用了slots就不推荐做继承了,容易搞乱.

定制类
str
repr
iter next
getitem
getattr
注意,只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。
这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。
这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。
class Chain(object):
def init(self, path=''):
self._path = path
def getattr(self, item):
return Chain('%s/%s' % (self._path, item))
def str(self):
return self._path
repr = str
print(Chain().a1.a2.b3) #输出 /a1/a2/b3

call

s() # self参数不要传入
My name is Michael.

那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例

枚举概念 与 使用枚举类
枚举(pascal)
枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举.
事先考虑到某一变量可能取的值,尽量用自然语言含义清楚的单词来表示它的每一个值,这种方法称为枚举方法,用这种方法定义的类型称枚举类型。
from enum import Enum
Month = Enum('Month2', 'A, B, C, D, E, F')
for name, member in Month.members.items():
print(name, '->', member, ',', member.value)

输出:
A -> Month2.A , 1
B -> Month2.B , 2
C -> Month2.C , 3
D -> Month2.D , 4
E -> Month2.E , 5
F -> Month2.F , 6

from enum import Enum, unique
@unique #@unique装饰器可以帮助我们检查保证没有重复值。
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
for name, member in Weekday.members.items():
print(name, member, member.value)
输出:
Sun Weekday.Sun 0
Mon Weekday.Mon 1
Tue Weekday.Tue 2
Wed Weekday.Wed 3
Thu Weekday.Thu 4
Fri Weekday.Fri 5
Sat Weekday.Sat 6

元类
type() 要创建一个class对象,type()函数依次传入3个参数:
class的名称;
继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
正常情况下,我们都用class Xxx...来定义类.

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

错误,调试,测试
错误处理 异常 try expect else finally
使用except而不带任何异常类型
try:
正常的操作
except:
发生异常,执行这块代码 #捕获所有异常
else:
如果没有异常执行这块代码

使用except而带多种异常类型
try:
正常的操作
except(Exception1[, Exception2[,...ExceptionN]]]):
发生以上多个异常中的一个,执行这块代码
else:
如果没有异常执行这块代码

try-finally 语句
无论是否发生异常都将执行最后的代码.
try:
正常的操作
finally:
最终的操作 #退出try时总会执行.不管try是否会产生异常

完整方式
try:
...
except:
...
except TypeError as e:
...
else:
...
finally:
...
如果在try里有return语句(程序遇到return就会跳出来),后面的else就没办法执行了.但finally始终都会执行.
Python的错误其实也是class,所有的错误类型都继承自BaseException(派生的),所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。
使用try...except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用foo(),foo()调用bar(),结果bar()出错了,这时,只要main()捕获到了,就可以处理.
except Exception as e 这个可以捕获所有的exception.

raise
raise 自己来触发一个异常并抛出去(终止程序的继续执行).
raise 的意思是将捕获到的错误往上层抛(切记).抛给它调用的那个函数(就像是子类把异常往上报给父类一样).
示例,定义一个异常非常简单,如下所示:
def functionName( level ):
if level < 1:
raise Exception("Invalid level!", level) # 触发异常后,后面的代码就不会再执行.
print(functionName(0)) #执行到这里,就会将上面的异常给抛出来,程序终止执行

捕获以上异常,"except"语句如下所示:
try:
functionName(0) # 正常逻辑
except(Exception): # 触发自定义异常
print("Err")
else: # 其余代码
print("wright")

调用堆栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。我们从上往下可以看到整个错误的调用函数链.

记录错误
我们能捕获错误,也可以把错误堆栈打印出来,同时,让程序继续执行下去(在出错的情况下,让程序继续执行下去.且程序的退出代码是'0')。
Python内置的logging模块可以非常容易地记录错误信息.
import logging
... except Exception as e:
logging.exception(e)
抛出错误
因为错误是class,捕获一个错误就是捕获到该class的一个实例。
如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例.
只有在必要的时候才定义我们自己的错误类型。(尽量使用Python内置的错误类型.)
class Zidingyierror(ValueError): ...
... raise Zidingyierror('invalid value: %s' % s)
raise语句如果不带参数,就会把当前错误原样抛出。此外,在except中raise一个Error,还可以把一种类型的错误转化成另一种类型:
try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
只要是合理的转换逻辑就可以,但是,决不应该把一个IOError转换成毫不相干的ValueError。

调试
1.print 弊端是代码里太多不需要的print语句,看着很乱.

2.assert Python内置了一个assert关键字,表示断言。凡是用print()来辅助查看的地方,都可以用断言来替代。它会对后面的表达式进行判断,如果表达式为True,那就什么都不做,程序接着往下走;如果False,那么就会弹出异常。
assert是一个非常有用的技巧,通过选择关键因子,对因子的状态进行判定,可以将程序一块一块的进行划分,逐步的缩小问题范围。
但是,程序中如果到处充斥着assert,和print()相比也好不到哪去。不过,启动Python解释器时可以用-O参数来关闭assert。关闭后,所有的assert语句相当于pass语句。

3.logging

import logging
logging.basicConfig(level=logging.INFO)
...
logging.info('n = %d' % n)
...
这就是logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别.
logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。

4.pdb

5.pdb.set_trace()

6.IDE (最常用的方式了吧,pycharm的甲虫按钮)

单元测试
单元测试测的不是语法问题,而是业务逻辑是否正确的问题。单元测试是软件开发过程中非常重要的一个环节。
Python中的单元测试框架和工具,比如unittest、testtools、subunit、coverage和testrepository等等,并且运行单元测试也有很多种方法。
由于它是测试工作而非功能实现,很多人都不太重视甚至忽略了单元测试。但是作为一个优秀的程序员,不仅要写好功能代码,写好测试代码一样重要。
测试用例的概念就用在这里.根据测试用例,编写测试程序.

关于super
super: 调用我的父类的某个方法(比如构造方法).父类代码修改后,子类无需修改.

class A(object): # A must be new-style class
def init(self):
print('enter A')
print('leave A')
class B(A):
def init(self):
print('enter B')
super(B, self).init() # 这里就相当于A.init(self) 目的是为了当B的父类由A变成C的时候,B的代码无需更改(当代码量很大时,就非常方便了).
print('leave B')
test = B()
输出:
enter B
enter A
leave A
leave B

对于super(B, self).init()是这样理解的:
super(B, self)首先找到B的父类(就是类A),然后把类B的对象self转换为类A的对象,然后“被转换”的类A对象调用自己的__init__函数。

posted @ 2020-04-20 14:23  ChanixChen  阅读(271)  评论(0编辑  收藏  举报