python之递归函数、二分查找、面向对象、封装(6)
本节内容:递归函数、二分查找、面向对象、什么是类、什么是对象、组合、面向对象的三大特征
1.递归函数
2.二分查找
3.面向对象
3.1.什么是类
3.2.什么是对象
3.3.类名称空间、对象名称空间
4.组合
5.面向对象的三大特征
1、递归函数
递归函数:在一个函数里在调用这个函数本身。
递归的最大深度:998
正如你们刚刚看到的,递归函数如果不受到外力的阻止会一直执行下去。但是我们之前已经说过关于函数调用的问题,每一次函
数调用都会产生一个属于它自己的名称空间,如果一直调用下去,就会造成名称空间占用太多内存的问题,于是python为了杜绝
此类现象,强制的将递归层数控制在了997(只要997!你买不了吃亏,买不了上当...).
拿什么来证明这个“998理论”呢?这里我们可以做一个实验:
def foo(n): print(n) n += 1 foo(n) foo(1)
由此我们可以看出,未报错之前能看到的最大数字就是998.当然了,997是python为了我们程序的内存优化所设定的一个默认值,
我们当然还可以通过一些手段去修改它:
import sys sys.setrecursionlimit(100000) count = 0 def func1(): global count count += 1 print(count) func1() func1()
我们可以通过这种方式来修改递归的最大深度,刚刚我们将python允许的递归深度设置为了10w,至于实际可以达到的深度就取
决于计算机的性能了。不过我们还是不推荐修改这个默认的递归深度,因为如果用997层递归都没有解决的问题要么是不适合使
用递归来解决要么是你代码写的太烂了~~~
1.1 递归函数的示例
这里我们又要举个例子来说明递归能做的事情。
例一:
现在你们问我,alex老师多大了?我说我不告诉你,但alex比 egon 大两岁。
你想知道alex多大,你是不是还得去问egon?egon说,我也不告诉你,但我比武sir大两岁。
你又问武sir,武sir也不告诉你,他说他比太白大两岁。
那你问太白,太白告诉你,他23了。
这个时候你是不是就知道了?alex多大?
首先,你是不是问alex的年龄,结果又找到egon、武sir、太白,你挨个儿问过去,一直到
拿到一个确切的答案,然后顺着这条线再找回来,才得到最终alex的年龄。这个过程已经
非常接近递归的思想。我们就来具体的我分析一下,这几个人之间的规律。你为什么能知道的?
""" alex 他比佩奇 大两岁。 4 age(3) + 2 佩奇 他比日天 大两岁。 3 age(2) + 2 日天 他比太白 大两岁。 2 age(1) + 2 太白:我今年23. 1 23 """ def age(n): if n == 1: return 23 else: return age(n-1) + 2 print(age(4)) """ def age(4): if n == 1: return 23 else: return age(3) + 2 23 + 2 + 2 + 2 def age(3): if n == 1: return 23 else: return age(2) + 2 23 + 2 + 2 def age(2): if n == 1: return 23 else: return age(1) + 2 23 + 2 def age(1): if n == 1: return 23 else: return age(0) + 2 """ def age(n): if n == 1: return 23 else: age(n-1) + 2 """ def age(2): if n == 1: return 23 else: age(1) + 2 23 + 2 def age(1): if n == 1: return 23 else: age(1) + 2 """ print(age(2))
2、二分查找
如果有这样一个列表,让你从这个列表中找到66的位置,你要怎么做?
l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88]
你说,so easy!
l.index(66)...
我们之所以用index方法可以找到,是因为python帮我们实现了查找方法。如果,index方法不给你
用了。。。你还能找到这个66么?
l=[2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88] i = 0 for num in l: if num == 66: print(i) i+=1
上面这个方法就实现了从一个列表中找到66所在的位置了。
2.1 二分查找示例
l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88]
你观察这个列表,这是不是一个从小到大排序的有序列表呀?
如果这样,假如我要找的数比列表中间的数还大,是不是我直接在列表的后半边找就行了?
简单版二分查找
l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88] def func(l,aim): mid = (len(l)-1)//2 if l: if aim > l[mid]: func(l[mid+1:],aim) elif aim < l[mid]: func(l[:mid],aim) elif aim == l[mid]: print("bingo",mid) else: print('找不到') func(l,66) func(l,6)
升级版二分查找
l1 = [1, 2, 4, 5, 7, 9] def two_search(l,aim,start=0,end=None): end = len(l)-1 if end is None else end mid_index = (end - start) // 2 + start if end >= start: if aim > l[mid_index]: return two_search(l,aim,start=mid_index+1,end=end) elif aim < l[mid_index]: return two_search(l,aim,start=start,end=mid_index-1) elif aim == l[mid_index]: return mid_index else: return '没有此值' else: return '没有此值' print(two_search(l1,9))
3、面向对象
实际工作中,python 都是面向对象,写代码,或者 面向对象+函数写代码。
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,
面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以
及Apache HTTP Server等。
面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向
对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
了解一些名词:类、对象、实例、实例化
类:具有相同特征的一类事物(人、狗、老虎)
对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)
实例化:类——>对象的过程
3.1 什么是类
声明
def functionName(args): '函数文档字符串' 函数体 ''' class 类名: '类的文档字符串' 类体 ''' #我们创建一个类 class Data: pass 声明函数vs声明类
属性
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def walk(self): #人都可以走路,也就是有一个走路方法 print("person is walking...") print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用
实例化:类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def __init__(self,name): self.name = name # 每一个角色都有自己的昵称; def walk(self): #人都可以走路,也就是有一个走路方法 print("person is walking...") print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用
实例化的过程就是类——>对象的过程
原本我们只有一个Person类,在这个过程中,产生了一个egg对象,有自己具体的名字、攻击力和生命值。
语法:对象名 = 类名(参数)
class Person: # class 关键字,定义了一个类 ''' 类里面的所有内容 ''' animal = '高级动物' # 静态变量 soup = '有思想' # 静态变量 def __init__(self,name,sex,eye,high,weight,): # 构造方法 self.eye = eye # 属性 self.name = name self.sex = sex self.high = high self.weight = weight print(666) def work(self): # 动态变量,动态方法,方法 print(self) # self.job = 'IT' print('人会工作....')
类操作静态变量有两种方式: 1,类名.__dict__方法 只能查看,不能增删改。 # print(Person.__dict__) # print(Person.__dict__['animal']) # Person.__dict__['name'] = 'alex' # Person.__dict__['animal'] = '低级动物' 2,类名.变量名 可增删改查 # print(Person.animal) # print(Person.soup) # Person.kind = '有性格' # Person.animal = '低等动物' # del Person.kind # print(Person.__dict__) # 一般你想查询全部的静态变量时,用__dict__ 其他全部都用类名.变量名。 类操作方法有两种方式: 1,类名.__dict__[方法名]() # print(Person.__dict__['work'](11)) 2,类名.方法名 # Person.work(11) # 如果类操作方法:类名.方法名() # 只要创建一个类,里面的内容就已经加载到内存。 # print(Person.__dict__)
self
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,
但是正常人都不会这么做。因为你瞎改别人就不认识
一:我们定义的类的属性到底存到哪里了?有两种方式查看 dir(类名):查出的是一个名字列表 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 二:特殊的类属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
3.2 什么是对象
对象: 类名() 实例化一个对象
只要实例化一个对象,自动触发__init___
内部进行三步:
1,实例化一个对象,在内存中产生一个对象空间。
2,自动执行init方法,并将这个空间对象。 <__main__.Person object at 0x0000000001F5ABE0> 传给self
3,通过构造方法里的代码给空间对象添加一些属性,并返回给对象。
对象如何调用查看静态变量,动态变量
对象操作属性变量有两种方式: 1,对象.__dict__方法 只能查看,不能增删改。 # print(p1.__dict__) 2,对象.变量名 可增删改查 # print(p1.name) # print(p1.eye) # p1.color = '黄皮肤' # print(p1.color) # print(p1, type(p1)) 3,可以访问类的静态变量 # print(p1.__dict__) # print(p1.animal) # print(p1.soup) # 一般你想查询全部的静态变量时,用__dict__ 其他全部都用类名.变量名。 # 对象操作方法有两种方式: 1,对象.方法名() # p1.work() # print(p1) # print(p1.__dict__) 2,类名.方法名(对象) # Person.work(111) # Person.work(p1)
对象的相关知识
class 类名: def __init__(self,参数1,参数2): self.对象的属性1 = 参数1 self.对象的属性2 = 参数2 def 方法名(self):pass def 方法名2(self):pass 对象名 = 类名(1,2) #对象就是实例,代表一个具体的东西 #类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法 #括号里传参数,参数不需要传self,其他与init中的形参一一对应 #结果返回一个对象 对象名.对象的属性1 #查看对象的属性,直接用 对象名.属性名 即可 对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可
''' 练习一:在终端输出如下信息 小明,10岁,男,上山去砍柴 小明,10岁,男,开车去东北 小明,10岁,男,最爱大保健 老李,90岁,男,上山去砍柴 老李,90岁,男,开车去东北 老李,90岁,男,最爱大保健 ''' def chop_wood(name,age,sex): print('%s,%s岁,%s,上山去砍柴'%(name,age,sex)) def driver(name,age,sex): print('%s,%s岁,%s,开车去东北'%(name,age,sex)) def healthcare(name,age,sex): print('%s,%s岁,%s,最爱大保健'%(name,age,sex)) chop_wood('小明',12,'男') driver('小明',12,'男') healthcare('小明',12,'男') chop_wood('老李',22,'男') ================= class Dayaction: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def chop_wood(self): print('%s,%s岁,%s,上山去砍柴'%(self.name,self.age,self.sex)) def driver(self): print('%s,%s岁,%s,开车去东北'%(self.name,self.age,self.sex)) def healthcare(self): print('%s,%s岁,%s,最爱大保健'%(self.name,self.age,self.sex)) p1 = Dayaction('小明', 15, '男') p1.chop_wood() p1.driver() p1.healthcare()
3.3 类名称空间、对象名称空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性
- 静态属性就是直接在类中定义的变量
- 动态属性就是定义在类中的方法
其中类的数据属性是共享给所有对象的
>>>id(egg.role) 4341594072 >>>id(Person.role) 4341594072
而类的动态属性是绑定到所有对象的
>>>egg.attack <bound method Person.attack of <__main__.Person object at 0x101285860>> >>>Person.attack <function Person.attack at 0x10127abf8>
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...
最后都找不到就抛出异常
4、组合
组合:给一个类对象的属性 封装 另一个类的对象。
class Game_person: def __init__(self,nickname,sex,hp,ad): self.nickname = nickname self.sex = sex self.hp = hp self.ad = ad def attack(self,p): p.hp -= self.ad print('%s攻击了%s,%s还剩%s血量'%(self.nickname,p.nickname,p.nickname,p.hp)) def weapon_attack(self,武器): self.武器 = 武器 #斧子对象 class Weapon: def __init__(self,name,ad): self.name=name self.ad=ad def fight(self,p1,p2): p2.hp -= self.ad print('%s使用%s打了%s%s血,%s还剩%s滴血'\ %(p1.nickname,self.name,p2.nickname,self.ad,p2.nickname,p2.hp)) ts = Game_person('泰森','男',200,50) barry = Game_person('太白','男',100,10) fuzi = Weapon('斧子',60) # wea.fight(barry,ts) 这样写不好,主体应该是人 # ts.attack(barry) # barry.attack(ts) barry.weapon_attack(fuzi) # barry对象调用weapon_attack方法, # 方法执行的是将斧子对象wea封装到barry对象的属性中、 # barry.武器 相当于 wea barry.武器.fight(barry,ts)
5、面向对象的三大特征
5.1 继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的
类称为派生类或子类
python中类的继承分为:单继承和多继承
复制代码 class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
查看继承
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)
继承的重要性
==========================第一部分 例如 猫可以:爬树、吃、喝、拉、撒 狗可以:看门、吃、喝、拉、撒 如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下: #猫和狗有大量相同的内容 class 猫: def爬树(self): print '爬树' def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something class 狗: def 看门(self): print '看门' def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something ==========================第二部分 上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现: 动物:吃、喝、拉、撒 猫:爬树(猫继承动物的功能) 狗:看门(狗继承动物的功能) 伪代码如下: class 动物: def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 猫(动物): def爬树(self): print '爬树' # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 狗(动物): def 看门(self): print '看门' ==========================第三部分 #继承的代码实现 class Animal: def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) def shit(self): print ("%s 拉 " %self.name) def pee(self): print ("%s 撒 " %self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '猫' def爬树(self): print '爬树' class Dog(Animal): def __init__(self, name): self.name = name self.breed='狗' def 看门(self): print '看门' # ######### 执行 ######### c1 = Cat('小白家的小黑猫') c1.eat() c2 = Cat('小黑的小白猫') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat() 使用继承来重用代码比较好的例子 继承可以减少代码重用
那么问题又来了,多继承呢?
- 是否可以继承多个类
- 如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢?
1、Python的类可以继承多个类,Java和C#中则只能继承一个类
2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
- 当类是经典类时,多继承情况下,会按照深度优先方式查找
- 当类是新式类时,多继承情况下,会按照广度优先方式查找
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,
也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
class D: def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> D --> C # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar() 经典类多继承
class D(object): def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> C --> D # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar() 新式类多继承
经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,
如果D类中么有,则继续去C类中找,如果还是未找到,则报错
新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,
如果C类中么有,则继续去D类中找,如果还是未找到,则报错
注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了