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)
View Code

由此我们可以看出,未报错之前能看到的最大数字就是998.当然了,997是python为了我们程序的内存优化所设定的一个默认值,

我们当然还可以通过一些手段去修改它: 

import sys
sys.setrecursionlimit(100000)
count = 0
def func1():
    global count
    count += 1
    print(count)
    func1()
func1()
View Code

我们可以通过这种方式来修改递归的最大深度,刚刚我们将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))
View Code

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
View Code

上面这个方法就实现了从一个列表中找到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)
View Code

升级版二分查找

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))
View Code

3、面向对象                                                                                                

实际工作中,python 都是面向对象,写代码,或者 面向对象+函数写代码。

面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,

面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。

优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。

缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。

应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以

及Apache HTTP Server等。

 

面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向

对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

了解一些名词:类、对象、实例、实例化

类:具有相同特征的一类事物(人、狗、老虎)

对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)

实例化:类——>对象的过程

3.1 什么是类                          

声明

def functionName(args):
     '函数文档字符串'
      函数体 


'''
class 类名:
    '类的文档字符串'
    类体
'''

#我们创建一个类
class Data:
    pass

声明函数vs声明类
View Code

属性

class Person:   #定义一个人类
    role = 'person'  #人的角色属性都是人
    def walk(self):  #人都可以走路,也就是有一个走路方法
        print("person is walking...")


print(Person.role)  #查看人的role属性
print(Person.walk)  #引用人的走路方法,注意,这里不是在调用
View Code

实例化:类名加括号就是实例化,会自动触发__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)  #引用人的走路方法,注意,这里不是在调用
View Code

实例化的过程就是类——>对象的过程

原本我们只有一个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__)
View Code

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)
View Code

对象的相关知识

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   #查看对象的属性,直接用 对象名.属性名 即可
对象名.方法名()     #调用类中的方法,直接用 对象名.方法名() 即可
View Code
'''
练习一:在终端输出如下信息
 
小明,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
View Code

而类的动态属性是绑定到所有对象的

>>>egg.attack
<bound method Person.attack of <__main__.Person object at 0x101285860>>
>>>Person.attack
<function Person.attack at 0x10127abf8> 
View Code

在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)
View Code

5、面向对象的三大特征                                                                                 

5.1 继承                                                     

继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的

类称为派生类或子类

python中类的继承分为:单继承和多继承

复制代码
class ParentClass1: #定义父类
    pass

class ParentClass2: #定义父类
    pass

class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
    pass
View Code

查看继承

>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
View Code

提示:如果没有指定基类,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()

使用继承来重用代码比较好的例子

继承可以减少代码重用
View Code

那么问题又来了,多继承呢?

  • 是否可以继承多个类
  • 如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢?

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类中找,如果还是未找到,则报错

注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了

posted @ 2018-06-13 14:41  独孤。。。  阅读(267)  评论(0编辑  收藏  举报