Python 面向对象1——面向对象编程介绍

  前言:

1、对象可以比作人【(会某些技能,具有某些属性(特征)】。

2、每个对象都有不同的属性(特征),需要用__init__去定义这些属性(特征)。

3、类可以比作一群人(他们有相似的技能或者相似的特征)。

4、先定义类,然后调用类(实例化)产生对象。

5、"类" 具有数据属性(所有对象共享)函数属性(主要是给对象使用的,并且是绑定到对象的)。

 

创建类的2中方式:

# 方式一
class Foo(object):
    CITY = "bj"

    def func(self,x):
        return x+1

# 方式二
Coo=type("Coo",(object,),{"CITY":"bj","func":lambda self,x:x+1})

 

  一、前言:面向过程编程 ---针对扩展性要求不高的程序,可使用面向过程编程

  1、什么是面向过程编程?

     核心是过程二字,过程指的是解决问题的步骤,即先干什么,在干什么,在干什么。。。。

     基于面向过程的思想,编写程序就好比在设计一条流水线,是一种机械式思维方式。

     优点:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)

     缺点:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。

应用场景:一旦完成基本很少改变的场景,著名的例子有Linux内核,git,以及Apache HTTP Server等。

面向过程编程小例子:把要做的事情拆分为一件件小的事情,然后完成。

def login():
    uname=input('pls input uname>>:').strip()
    pwd=input('pls input pwd>>:').strip()
    return uname,pwd
 
def auth(uname,pwd):
    if uname == 'szq' and pwd == '123':
        return True,uname
    else:
        return False,uname
 
def index():
    if res[0]:
        print('欢迎%s登录' %res[1])
    else:
        print('%s登录失败' % res[1])
 
uname,pwd=login()
res=auth(uname,pwd)
index()

 

  二、面向对象编程 ---用在扩展性强的程序上(与用户交互上)

  1、什么是面向对象编程?

     核心是 "对象" 二字,对象指的是:特征(指变量)与技能(指函数)的结合体。

     基于面向对象的思想,编写程序就好比在创造一个世界,程序员就是这个世界的上帝,因此一切存在的事物皆对象,任何不存在的对象也都可以造出来。

     优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。

     缺点:

    1. 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。

    2. 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。

应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方

 

三、

    类:一系列对象相似的特征与技能的结合体

    在程序中:一定要先定义类后调用类 (产生对象)

例子:

对象1:学生
    特征 
        school='Oldboy'
        name='李泰迪'
        sex='male'
        age=18
    技能 
        选课

对象2:学生
    特征
        school='Oldboy'
        name='张随便'
        sex='male'
        age=38
    技能
        选课

对象3:老师
    特征 
        name='Egon'
        sex='male'
        age=18
        level=10
    技能 
        打分
        点名

总结:

    学生之间相似的特征:school='Oldboy'
    学生之间相似的技能:选课

    3.1、类的定义  -->类在定义阶段就会执行代码,会产生类的名称空间

class OldboyStudent:    #类并不是一个真正存在的东西,它是一个抽象的概念(这里表示school='Oldboy')
    # 公共的特征: 变量
    school='Oldboy'

    # 技能: 函数
    def choose_course(self):
        print('is choose_course')

print(OldboyStudent.__dict__)        #打印类定义的名称空间
print(OldboyStudent.__dict__['school'])        #取名称空间内的"school"对应的值
# >>:Oldboy
print(OldboyStudent.school)          #本质就是取"OldboyStudent.__dict__['school']"的值(OldboyStudent名称空间内"school"的值)
# >>:Oldboy

OldboyStudent.choose_course(123)     #调用类的容器"choose_course",由于choose_course本身是一个函数,那么就按照函数的写法来调用
# >>:is choose_course

    3.2、类的用法:增,删,改,查

'''类的用法:增删改查'''
OldboyStudent.country='China'       #增加一条数据
OldboyStudent.school='Oldgirl'      #改数据,把'Oldboy'修改为'Oldgirl'
print(OldboyStudent.__dict__)       #查看数据
del OldboyStudent.school            #在类(一个容器)里面把school给删掉
print(OldboyStudent.__dict__)

  总结类的用途:2种
    1、通过调用类(实例化),可以产生对象。
    2、类本身就是一个容器,里面有很多名称空间定义好的名称,可以直接取。

 

    3.3、python为类内置的特殊属性

类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)

 

四、对象

    4.1、实例化对象 [object=class()]   -->对象独有的特征一定会放在对象自己的名称空间内。对象共有的特征可以放在类的名称空间中。

class OldboyStudent:
    country='China'      #这个相关于对象的公共特征(即每个对象都有这个特征)
    def __init__(self,name,sex,age):   #"__init__"是一个内置方法(在满足某种条件下自动触发),用来定义对象有哪些属性(其中self表示对象,对象会自动当做第一个值传入)
        self.name=name   #特征1
        self.sex=sex     #特征2
        self.age=age     #特征3

    def learn(self):   #定义对象的技能
        print('学习') 

    def eat(self):     #定义对象的技能
        print('吃饭')

    def sleep(self):   #定义对象的技能
        print('睡觉')

# 在实例化时,自动触发__init__(s1,'张三','male','18')[将对象以及对象要传入的值一起传给__init__]
s1=OldboyStudent('张三','male','18')      # 调用类(实例化)产生对象
s2=OldboyStudent('李四','female','28')    # 调用类(实例化)产生对象
s3=OldboyStudent('王二','male','38')      # 调用类(实例化)产生对象

print(s1.__dict__)   #打印对象s1的名称空间,拿到名称空间的值。
    # {'name': '张三', 'sex': 'male', 'age': '18'}
print(s2.__dict__)
    # {'name': '李四', 'sex': 'female', 'age': '28'}
print(s3.__dict__)
    # {'name': '王二', 'sex': 'male', 'age': '38'}

总结对象的用途:
    1、对象的属性查找是:先从对象本身的名称空间找,找不到则取类的名称空间中查找,两者都找不到则报错。

 

4.2、修改对象名称空间的值,与修改类名称空间的值

#一、先定义类
class OldboyStudent:
    school='Oldboy'

    def __init__(self, x, y, z):
        self.name = x
        self.sex = y
        self.age = z

    def choose_course(self):
        print('is choose_course')

#二、后调用类
stu1=OldboyStudent('李泰迪','male',18)
stu2=OldboyStudent('张随便','famale',38)

# 修改对象下的名称空间的值只针对对象本身的名称空间生效,而不会影响到其他对象。
stu1.school='US'
print(stu1.school)
>>:US
print(stu2.school)
>>:Oldboy

# 修改类下的名称空间的值,那么引用该类的对象的名称空间的值都会被修改。
OldboyStudent.school='US'
print(stu1.school)
>>:US
print(stu2.school)
>>:US

 

4.3、对象绑定方法的使用

  绑定方法的特殊点:
     1、绑定给谁就由谁来调用
     2、谁来调用,就会将谁当做第一个参数自动传入

class OldboyStudent:

    school='Oldboy'
    def __init__(self, x, y, z):
        self.name = x
        self.sex = y
        self.age = z

    def choose_course(self):
        print('%s is choose_course' %self.name)  # 新增self.name

    def eat(self):
        print('%s is eating' %self.name)         # 新增self.name

    def sleep(self):
        print('%s is sleeping' %self.name)       # 新增self.name

stu1=OldboyStudent('李泰迪','male',18)
stu2=OldboyStudent('张随便','famale',38)

OldboyStudent.choose_course(stu1)
OldboyStudent.choose_course(stu2)
# 李泰迪 is choose_course
# 张随便 is choose_course

# 强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)
stu1.choose_course()                # 等同于OldboyStudent.choose_course(stu1)
stu2.choose_course()                # 等同于OldboyStudent.choose_course(stu2)
# 李泰迪 is choose_course
# 张随便 is choose_course

 

4.4、从代码级别看面向对象

#1、在没有学习类这个概念时,数据与功能是分离的
def exc1(host,port,db,charset):
    conn=connect(host,port,db,charset)
    conn.execute(sql)
    return xxx


def exc2(host,port,db,charset,proc_name)
    conn=connect(host,port,db,charset)
    conn.call_proc(sql)
    return xxx

#每次调用都需要重复传入一堆参数
exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;')
exc2('127.0.0.1',3306,'db1','utf8','存储过程的名字')



#2、我们能想到的解决方法是,把这些变量都定义成全局变量
HOST=‘127.0.0.1’
PORT=3306
DB=‘db1’
CHARSET=‘utf8’

def exc1(host,port,db,charset):
    conn=connect(host,port,db,charset)
    conn.execute(sql)
    return xxx


def exc2(host,port,db,charset,proc_name)
    conn=connect(host,port,db,charset)
    conn.call_proc(sql)
    return xxx

exc1(HOST,PORT,DB,CHARSET,'select * from tb1;')
exc2(HOST,PORT,DB,CHARSET,'存储过程的名字')



#3、但是2的解决方法也是有问题的,按照2的思路,我们将会定义一大堆全局变量,
    这些全局变量并没有做任何区分,即能够被所有功能使用,
    然而事实上只有HOST,PORT,DB,CHARSET是给exc1和exc2这两个功能用的。
    言外之意:我们必须找出一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了。
class MySQLHandler:
    def __init__(self,host,port,db,charset='utf8'):
        self.host=host
        self.port=port
        self.db=db
        self.charset=charset
    def exc1(self,sql):
        conn=connect(self.host,self.port,self.db,self.charset)
        res=conn.execute(sql)
        return res


    def exc2(self,sql):
        conn=connect(self.host,self.port,self.db,self.charset)
        res=conn.call_proc(sql)
        return res


obj=MySQLHandler('127.0.0.1',3306,'db1')
obj.exc1('select * from tb1;')
obj.exc2('存储过程的名字')


#改进
class MySQLHandler:
    def __init__(self,host,port,db,charset='utf8'):
        self.host=host
        self.port=port
        self.db=db
        self.charset=charset
        self.conn=connect(self.host,self.port,self.db,self.charset)
    def exc1(self,sql):
        return self.conn.execute(sql)

    def exc2(self,sql):
        return self.conn.call_proc(sql)


obj=MySQLHandler('127.0.0.1',3306,'db1')
obj.exc1('select * from tb1;')
obj.exc2('存储过程的名字')

数据与专门操作该数据的功能组合到一起

 

4.5、总结对象的好处

1、可扩展性高的例子

     1):定义类并产生三个对象:s1 ,s2 ,s3

class OldboyStudent:

    def __init__(self,name,sex,age):   #定义对象有哪些特征
        self.name=name   #特征1
        self.sex=sex     #特征2
        self.age=age     #特征3

s1=OldboyStudent('张三','male','18')      # 调用类(实例化)产生对象
s2=OldboyStudent('李四','female','28')    # 调用类(实例化)产生对象
s3=OldboyStudent('王二','male','38')      # 调用类(实例化)产生对象

    2):新增一个类属性(特征),那么实例化后所有对象都会收到这个属性。


class OldboyStudent:
    country='China'

    def choose_course(self):
        print('%s 测试绑定方法' %self.name)

    def __init__(self,name,sex,age):   #定义对象有哪些属性
        self.name=name   #属性1
        self.sex=sex     #属性2
        self.age=age     #属性3

    def learn(self):   #定义对象的特征
        print('学习')

    def tell_info(self):
        info="""
        国籍:%s
        姓名:%s
        年龄:%s
        性别:%s
        """%(self.country,self.name,self.age,self.sex)
        print(info)

s1=OldboyStudent('张三','male','18')      # 调用类(实例化)产生对象
s2=OldboyStudent('李四','female','28')    # 调用类(实例化)产生对象
s3=OldboyStudent('王二','male','38')      # 调用类(实例化)产生对象

print(s1.country)
# China

s1.choose_course()
# 张三 测试绑定方法

s1.tell_info()
# 国籍: China
# 姓名: 张三
# 年龄: 18
# 性别: male

 

五、面向对象的三大特性:继承,派生,多态,封装

 5.1、第一大特性:继承与派生

 1、什么是继承?

   1) 继承是一种新建类的方式,新建的类称为子类或者派生类,被继承的类称为父类或者基类或者超类。

   2) 子类会遗传父类的一系列属性,从而解决代码重用问题。

   3) Python支持多继承

   4)  在Python3中,如果没有显示继承任何类(使用print(Sub1.__bases__)查看继承了哪些类),那么默认继承object类。

   5)  在Python2中,如果没有显示继承任何类(使用print(Sub1.__bases__)查看继承了哪些类),也不会继承object类。

 

2、继承与抽象

  继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承

  抽象即抽取类似或者说比较像的部分。

  抽象分成两个层次: 

    1.将奥巴马和梅西这俩对象比较像的部分抽取成类; 

    2.将人,猪,狗这三个类比较像的部分抽取成父类。

  抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

 

  继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

  抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

 

2、为什么要用继承?

  在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时。

  我们不可能从头开始写一个类B,这就用到了类的继承的概念。

  通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。例子如下:

==========================第一部分
例如

  猫可以:喵喵叫、吃、喝、拉、撒

  狗可以:汪汪叫、吃、喝、拉、撒

如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下:
 

#猫和狗有大量相同的内容
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 cry(self):
        print('喵喵叫')

class Dog(Animal):

    def __init__(self, name):
        self.name = name
        self.breed='狗'

    def cry(self):
        print('汪汪叫')


# ######### 执行 #########

c1 = Cat('小白家的小黑猫')
c1.eat()

c2 = Cat('小黑的小白猫')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()

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

 

    提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大减少了编程的工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大。例子如下:

class Hero:
    def __init__(self,nickname,aggressivity,life_value):
        self.nickname=nickname
        self.aggressivity=aggressivity
        self.life_value=life_value

    def move_forward(self):
        print('%s move forward' %self.nickname)

    def move_backward(self):
        print('%s move backward' %self.nickname)

    def move_left(self):
        print('%s move forward' %self.nickname)

    def move_right(self):
        print('%s move forward' %self.nickname)

    def attack(self,enemy):
        enemy.life_value-=self.aggressivity
class Garen(Hero):
    pass

class Riven(Hero):
    pass

g1=Garen('草丛伦',100,300)
r1=Riven('锐雯雯',57,200)

print(g1.life_value)
r1.attack(g1)
print(g1.life_value)

'''
运行结果
'''

  注意:像g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。

 

  1) 减少代码冗余例子 --- 这里使用到了对象的继承派生

  例子:定义学生类和老师类,但是学生类和老师类大部分代码相同,这里就可以使用类的继承来减少代码冗余。

#修改前:
import pickle

class OldboyStudent:
    school='oldboy'

    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

    def choose_course(self,course):
        self.course=course
        print('%s is choose course:%s' %(self.name,self.course))

    def save(self):
        with open('%s.stu' %self.name,'wb') as f:
            pickle.dump(self,f)

class OldboyTeacher:
    school = 'oldboy'

    def __init__(self,name,age,sex,level):
        self.name = name
        self.age = age
        self.sex = sex
        self.level = level

    def score(self,stu):
        print('%s is score %s' %(self.name,stu.name))

    def save(self):
        with open('%s.stu' %self.name,'wb') as f:
            pickle.dump(self,f)

stu1=OldboyStudent('alex',20,'male')
stu1.save()

tea1=OldboyTeacher('egon',18,'male','10')
tea1.save()

#####################################分割线####################################

#修改后:  使用继承与派生的方式减少代码冗余。
import pickle

class OldboyPeople:
    school='oldboy'

    def save(self):
        with open('%s.stu' %self.name,'wb') as f:
            pickle.dump(self,f)

    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

class OldboyStudent(OldboyPeople):
    def choose_course(self,course):
        self.course=course
        print('%s is choose course:%s' %(self.name,self.course))

# 在子类中派生出新方法中重用父类的功能:
# 方式一:指名道姓的访问父类的函数
class OldboyTeacher(OldboyPeople):                
    def __init__(self,name,age,sex,level):        # 在子类派生出的新方法中重用父类的功能。[定义了与父类名字相同的"__init__"]
        OldboyPeople.__init__(self,name,age,sex)  # 指名道姓的访问父类的函数"__init__",把子类[def __init__(self,name,age,sex,level)]里面的self,name,age,sex 通过[ OldboyPeople.__init__(self,name,age,sex)]传值给父类OldboyPeople。
        self.level = level                        # 在子类中新增一个功能level 

    def score(self,stu):
        print('%s is score %s' %(self.name,stu.name))

stu1=OldboyStudent('alex',20,'male')
print(stu1.name)
print(stu1.school)

# alex
# oldboy

tea1=OldboyTeacher('egon',18,'male','10')
print(tea1.name)
print(tea1.level)

# egon
# 10

 

3、如何用继承      继承的优点:减少类与类之间的代码冗余

子类与父类的继承关系例子:

class Parent1:   # 父类
    pass

class Parent2:   # 父类
    pass

class Sub1(Parent1):         # 子类
    pass

class Sub2(Parent1,Parent2): # 子类
    pass

print(Sub1.__bases__)        # 查看子类Sub1继承的父类有哪些
# (<class '__main__.Parent1'>,)

print(Sub2.__bases__)        # 查看子类Sub2继承的父类有哪些
# (<class '__main__.Parent1'>, <class '__main__.Parent2'>)

 

4、在Python中类分为两种

     新式类:==>Python3

                但凡继承object的类,以及该类的子类都是新式类。

                在Python3中所有的类都是新式类(Python3中所有类的父类都是object)。

     经典类:==>Python2

                没有继承object类,以该类的子类都是经典类。

                只有在Python2中才有存在经典类,因为在python2中没有显示继承任何类,也不会继承object类。

 

 5、继承实现的原理 -- 子类重用父类共的两种方式

  5.1.1、属性的查找关系,对象先查找自己的名称空间,如果没有则去类的名称空间查找

  例子1:单继承下,属性的查找关系

class Foo:
    def f1(self):
        print('Foo f1')

    def f2(self):       # self = obj
        print('Foo f2')
        self.f1()       # self.f1() == obj.f1() ,obj这个对象本身是没有f1()这个函数的,它需要去类中查找,即Bar(Foo)中函数f1()

class Bar(Foo):
    def B1(self):
        print('Bar B1')

obj=Bar()
obj.f2()

# Foo f2
# Bar f1

 

  5.1.2、多继承下的继承顺序

   Python中子类可以同时继承多个父类,如A(B,C,D)

   如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

   如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先广度优先

 

5.1..3、继承原理 (python是如何实现继承的)

     python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如

>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

    为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
    而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
   1.子类会先于父类被检查
   2.多个父类会根据它们在列表中的顺序被检查
   3.如果对下一个类存在两个合法的选择,选择第一个父类

 

5.1.4、子类中调用父类的方法 -- 多继承要谨慎使用,多继承相当于程序的强耦合。

方法一:指名道姓,即父类名.父类方法()

class Vehicle:  # 定义交通工具类
    Country = 'China'

    def __init__(self, name, speed, load, power):
        self.name = name
        self.speed = speed
        self.load = load
        self.power = power

    def run(self):
        print('开动啦...')

class Subway(Vehicle):  # 地铁
    def __init__(self, name, speed, load, power, line):
        Vehicle.__init__(self, name, speed, load, power)  # 派生方式1--指名道姓的使用"Vehicle"类定义的属性
        self.line = line

    def run(self):
        print('地铁%s号线欢迎您' % self.line)
        super(Subway, self).run()

line13 = Subway('中国地铁', '180m/s', '1000人/箱', '电', 13)
line13.run()

 

方法二:super()    super(自己的类名,self).父类中的方法名()

class Vehicle:  # 定义交通工具类
    Country = 'China'

    def __init__(self, name, speed, load, power):
        self.name = name
        self.speed = speed
        self.load = load
        self.power = power

    def run(self):
        print('开动啦...')

class Subway(Vehicle):  # 地铁
    def __init__(self, name, speed, load, power, line):
        # 在python3中: super() 等同于 super(Subway,self)
        super(Subway,self).__init__(name, speed, load, power)      # 派生方式2-super(),可以把"super()"理解为父类,那么就可以通过“super”直接调用父类里面的功能了。
        self.line = line

    def run(self):
        print('地铁%s号线欢迎您' % self.line)
        super(Subway, self).run()

line13 = Subway('中国地铁', '180m/s', '1000人/箱', '电', 13)
line13.run()

  调用super会得到一个特殊的对象,该对象是专门用来引用父类中的方法的 (该对象会严格按照当前类的MRO列表从当前类的父类中依次查找属性)

  证明:调用super()时,是如何按照MRO列表依次查找属性的

class A:
    def f1(self):
        print("A")
        super().f2()   # super() 会基于当前所在的查找位置继续往后查找。 
                       # 从下面的MRO列表中可以看出:super() 会基于<class '__main__.B'>开始查找。
class B:
    def f2(self):
        print("B")

class C(A,B):
    def f2(self):
        print("C")

obj=C()
print(C.mro())
# MRO列表: [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

obj.f1()
# A
# B

强调:指名道姓或者super()二者使用哪一种都可以,但最好不要混合使用

 

5.1.5、类之间的组合使用

1、什么是组合?

   一个对象属性的值是来自于另外一个类的对象,这就叫做类的组合使用。

2、为什么要用组合?

    组合是用来减少类与类之间的代码冗余(这一点和继承是相同的,只不过继承是:多个子类属于一个父类,属于一个从属关系,这种的话都可以使用继承的方式来减少代码冗余。但是对于没有从属关系的类,想要实现代码冗余:这个时候就需要用到组合。)

组合使用例子:OldboyStudent类下的"tell_course_info"对象(函数)里面的for循环取到的值来自于Course类下面的"tel_info"对象(函数)

# 人类
class OldboyPeople:
    school='Oldboy'
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age


# 学生类
class OldboyStudent(OldboyPeople):
    def __init__(self,name, sex, age):
        OldboyPeople.__init__(self,name, sex, age)
        self.course=[]   # 课程为空

    def choose_course(self,course):
        print('%s is choose_course %s' %(self.name,course))

    def tell_course_info(self):     # 循环取出self.course里面的值,并打印信息
        for course in self.course:  # 在stu1对象里面,循环取出将python对象和linux对象
            course.tel_info()       # course拿到的就是[python对象和linux对象],然后调用python对象和linux对象下面的tel_info方法

# 课程类
class Course:
    def __init__(self,name,price,period):
        self.name=name
        self.price=price
        self.period=period

    def tel_info(self):
        print("""
        名称: %s,
        价格: %s,
        周期: %s,
        """ % (self.name,self.price,self.period))
python=Course('Python','8000','5mons')  # 调用课程类拿到一个对象(python)
linux=Course('Linux','10000','3mons')   # 调用课程类拿到一个对象(linux)

# stu1对象的course属性的值,来自于Course类下面的python对象和linux对象
stu1=OldboyStudent('李三',18,'male')    # 调用学生类拿到一个对象(stu1)

stu1.course.append(python)   # 将python[课程类实例化的对象]这个对象放入到stu1对象内。
stu1.course.append(linux)    # linux[课程类实例化的对象]这个对象放入到stu1对象内。
stu1.tell_course_info()      # 调用学生类里面的方法,把学生所选的课程信息全部打印出来。

  名称: Python,
  价格: 8000,
  周期: 5mons,
        
  名称: Linux,
  价格: 10000,
  周期: 3mons,

 

 5.2、第二大特性:多态与多态性

1、什么是多态?
    同一种事务的多种形态。

2、为何要用多态
    多态的特性:可以在不用考虑对象具体类型的情况下,直接调用对象的一些方法。

3、如何用多态

     解释多态的例子:

import abc

# "Animal"类在使用了"metaclass=abc.ABCMeta"之后无法被实例化(抽象基类不能被实例化)
class Animal(metaclass=abc.ABCMeta):  # 父类存在的意义就是用来定义规范
    @abc.abstractmethod               # 在使用"abc.abstractmethod"方法之后:
    def talk(self):                   # 所有继承了"Animal"的子类,里面必须定义一个相同的talk函数,否则报错。
        pass

    @abc.abstractmethod
    def eat(self):
        print("eat")

class People(Animal):
    def talk(self):
        print("hello")

    def eat(self):
        print("eat")

class Dog(Animal):
    def talk(self):
        print("旺旺")

    def eat(self):
        print("eat")

class Pig(Animal):
    def talk(self):
        print("henhen")

    def eat(self):
        print("eat")


peo1=People()
dog1=Dog()
pig1=Pig()

peo1.talk()
dog1.talk()
pig1.talk()

peo1.eat()
dog1.eat()
pig1.eat()

peo1.eat()
dog1.eat()
pig1.eat()

3.1、Python推崇的是鸭子类型(走起路来像鸭子,叫声像鸭子,那么就是鸭子)

class Animal(metaclass=abc.ABCMeta):
    def talk(self):
        pass

# 这里不用继承Animal类了,自己在每一个类里面都定义一个叫talk的方法:
class People:
    def talk(self):
        print("hello")

class Dog:
    def talk(self):
        print("旺旺")

class Pig:
    def talk(self):
        print("henhen")


peo1=People()
dog1=Dog()
pig1=Pig()

peo1.talk()
dog1.talk()
pig1.talk()

 

5.3、第三大特性:封装

1、什么是封装?
    "封"的意思就是藏起来,在内部可以看到,但是对外部是隐藏的。
    "装"的意思是往一个容器中放入一系列属性

2、为何要用封装 --- 将数据属性隐藏起来,从而让类的使用者无法直接操作该数据属性,只能间接使用[ 通过调用类内部提供的接口的方式来使用, 而接口则由类的开发人员设计。]

   2.1、封装数据属性:

     将数据属性隐藏起来,从而让类的使用者无法直接操作该数据属性。
     需要类的设计者在类的内部开辟接口,让类的使用者用接口来间接地操作数据。
     类的设计者可以在接口上附加任意逻辑,从而严格控制类的使用者对类属性的操作。

    将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。

class People():
    def __init__(self,name,age):
        self.__name=name
        self.__age=age

    def tell_info(self):
        print("<%s:%s>" %(self.__name,self.__age))

peo1=People("sudaa",18)
# print(peo1.__name)  #在外部,无法通过调用对象的方式来查看封装后的属性值

peo1.tell_info()   # 只能通过类内部的方法[在类内部可以调用和查看__开头的属性],查看属性的值
<sudaa:18>

# 数据隐藏之后无法调用
print(peo1.name)
print(peo1.__name)

 

   2.2:封装方法:目的是隔离复杂度

封装方法举例: 

  1. 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。

  2. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!

  3. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了

提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。

# 取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
# 对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
# 隔离了复杂度,同时也提升了安全性
class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用户认证')
    def __input(self):
        print('输入取款金额')
    def __print_bill(self):
        print('打印账单')
    def __take_money(self):
        print('取款')

    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()

 

3、如何用封装
    但凡是双下划线开头" __test "(不能是双下划线结尾)的属性,都会被隐藏起来,类内部可以直接使用而类外部无法直接使用封装是对外不对内的(外部非要访问的话,通过res._Foo__f1()的方式照样可以获取到值)
    这种隐藏的特点: 
        1、只是一种语法上的变形,会将__开头的属性变形为:_自己的类名__属性名
        2、该变形只在类定义阶段发生一次,在类定义阶段之后新增的__开头的属性并不会发生变形
        3、这种隐藏是对外不对内的
--见例子1
        4、在继承中,父类如果不想让子类覆盖自己的同名方法,可以将方法(函数)定义为私有的(即给函数加上__开头)。--见例子3

 例子1:封装的例子,封装之后,类里面定义的函数,在类的外面是无法直接调用的。

class Foo():
    __n=1

    def __init__(self,name):
        self.name=name

    def __f1(self):
        print('f1')

res=Foo('szq')

# res.__f1()    # 使用此方法无法访问对象下的__f1方法

# 问题?如何才能访问到对象下的__f1方法:
# 1.首先查看类的名称空间
# print(Foo.__dict__)  # 通过查看Foo类的名称空间可以看出'_Foo__n'与_Foo__f1'
# {'__module__': '__main__', '_Foo__n': 1, '__init__': <function Foo.__init__ at 0x0000023ECC1EAA60>, '_Foo__f1': <function Foo.__f1 at 0x0000023ECC1EAAE8>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}

# 2.根据名称空间来访问对象下的__f1方法
# res._Foo__f1()   # 那么就需要使用这种方式来获取类内部的一些属性和值
# f1

例子2:封装之后,外部如何调用? --通过在类的内部新建接口的形式!

class Foo():
    __n=1

    def __init__(self,name):
        self.name=name

    def __f1(self):
        print('f1')

    def f2(self):     # 类的内部新增一个接口函数f2,通过f2函数来调用"__f1"函数
        self.__f1()

res=Foo('szq')
res.f2()
f1

例子3:在继承中,父类如果不想让子类覆盖自己的同名方法,如何实现?

class Foo:
    def __f1(self):
        print("Foo.f1")

    def f2(self):
        print("Foo.f2")
        self.__f1()

class Bar(Foo):
    def __f1(self):
        print("Bar.f1")

obj=Bar()
obj.f2()

# 没加__时
# Foo.f2
# Bar.f1

# 加__之后
# Foo.f2
# Foo.f1

 

 4、封装之property ,类里面的装饰器如何使用?

 通过使用property装饰器:可以将对象的"技能"(函数)伪装成一个"属性"(变量),并且可以对这个属性(变量)做"增,删,改"的操作。

 例子1:将一个对象的技能(函数)伪装成一个属性(变量)

伪装之前:想要访问People类下面的tell_name时,只能通过peo1.tell_name()的方式

class People():
    def __init__(self,name):
        self.__name=name

    def tell_name(self):
        return self.__name

peo1=People('sudada')
print(peo1.tell_name())

sudada


伪装之后:想要访问People类下面的name时,就可以直接通过peo1.name的方式去访问,和访问属性(变量)的方式是一样的。
class People():
    def __init__(self,name):
        self.__name=name

    @property
    def name(self):
        return self.__name

peo1=People('sudada')
print(peo1.name)

sudada

  例子2:将一个对象的技能(函数)伪装成一个属性(变量),并且做 "增,删,改" 的操作

class People():
    def __init__(self,name):
        self.__name=name

    @property              # 将技能(函数)伪装为一个属性(变量),可以进行查看
    def name(self):
        return self.__name

    @name.setter           # 由于name已经被伪装为一个属性(变量),那么就得有修改这个属性(变量)值的方法。就是"@name.setter"
    def name(self,val):
        if type(val) is not str:
            raise TypeError('名字的值必须为str类型')
        self.__name=val

    @name.deleter          # 删除伪装后的属性(变量)的方法"name.deleter"
    def name(self):
        print('无法删除!')

peo1=People('sudada')
peo1.name='szq'            # 在使用"@name.setter"装饰器之后,就可以通过peo1.name='szq'的方式修改name的值了。
print(peo1.name)
# szq

del peo1.name    # 删除操作,触发"name.deleter",并拿到返回值
# 无法删除!

 

5、绑定方法与非绑定方法

1、绑定方法  --  classmethod
    特点:绑定给谁,就应该有谁来调用,谁来调用就会将谁当做第一个参数传入。
    
    1.绑定到对象的方法:
        在类中定义的函数,在没有被任何装饰器装饰的情况下,默认都是绑定给对象的。
    
    2.绑定到类的方法:
        在类中定义的函数,在被装饰器classmethod装饰的情况下,该方法是绑定类的,由类来调用

  绑定到类的例子:最常用的应用场景--提供额外的实例化方式 

import settings   # 导入配置文件
'''
settings配置文件内容如下:
HOST='127.0.0.1'
PORT=3306
'''


class MySQL():
    def __init__(self,host,port):
        self.host=host
        self.port=port

    @classmethod          # 提供一种额外的实例化方式,从配置文件中读取配置的方式来完成传参的一种方式
    def from_conf(cls):   # cls是自动补全的,和self一样。这里的cls补全的是类(MySQL)。[哪个类去调用就传谁入哪个了类]
        return cls(settings.HOST,settings.PORT)     
        # cls(settings.HOST,settings.PORT) == MySQL(settings.HOST,settings.PORT)就是一次实例化,拿到的是一个对象。

# 方式一:使用类的绑定方法拿到的对象,MySQL.from_conf()获取到的是一个对象,这个对象是通过"cls(settings.HOST,settings.PORT)"(实例化)得到的。
conn1=MySQL.from_conf()
print(conn1.host,conn1.port)
# 127.0.0.1 3306

# 方式二:实例化类,获取到对象的方式
conn=MySQL('1.1.1.1',3306)
print(conn.host,conn.port)
# 1.1.1.1 3306

  2、与非绑定方法(得到的就是一个普通的函数,谁都可以调用)  --  staticmethod
    特点:即不与类绑定也不与对象绑定,没有任何自动传值的效果,因为函数体根本也不需要。

import hashlib
import time

class Mysql:
    def __init__(self,host,port):
        self.host=host
        self.port=port

    @staticmethod
    def create_id():        # 这里不需要传入self,也就是不需要绑定任何类或者对象。
        m=hashlib.md5()
        m.update(str(time.clock()).encode("utf-8"))
        return m.hexdigest()

obj=Mysql("1.1.1.1",3306)
print(obj.create_id())        # 对象可以调用create_id函数
print(Mysql.create_id())      # 类也可以调用create_id函数

 

六、内置方法之反射

1、什么是反射?

   指的是通过字符串来操作类或者对象的属性。

例子:反射涉及到的四个内置函数

# 先定义类
class People():
    country='China'
    def __init__(self,name):
        self.name=name
    def foo(self):
        print("from foo")
peo=People("sudada")

涉及到的四个内置函数

一、hasattr   # 查看一个类是否存在某个属性
# 方法一:通过查看"People"类的名称空间的方式查看"country"这个属性是否存在
print("country" in People.__dict__)
# 方法二:本质是通过方式一得来的
print(hasattr(People,'country'))


二、getattr  # 获取一个类某个属性的值
# 方法一:通过查看"People"类的名称空间的方式获取"country"这个属性的值
print(People.__dict__['country'])
# 方法二:
print(getattr(People,'country',None))   # 返回变量值
print(getattr(People,"foo"))            # 返回函数的内存地址
# People类存在country这个属性的话,则返回属性对应的值(变量就返回值,函数则返回函数的内存地址),否则返回None


三、setattr  # 设置一个类某个属性的值
setattr(People,'x',111)
print(People.x)
# 111


四、delattr  # 删除一个类某个属性
delattr(People,'country')
print(People.__dict__)

  2、反射的应用场景: -- 通过输入命令的方式获取类里面某个属性的值

class FTP():
    def get(self):
        print('get...')

    def put(self):
        print('put...')

    def run(self):
        while True:
            cmd=input('>>: ').strip()   # 用户输入命令
            if hasattr(self,cmd):       # 判断输入的"命令"是否存在于FTP这个类的属性里面
                res=getattr(self,cmd)   # 如果输入的"命令"存在的话,那么就通过getattr的方法拿到FTP这个类对应的属性,并拿到返回结果
                res()                   # 把拿到的结果加括号运行,从而得到属性的值
            else:
                print('命令不存在')

obj=FTP()
obj.run()

 

七、内置方法

  内置方法一: __str__  当对象被打印时(print(对象名)),显示__str__函数的返回值。

class People():
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

    def __str__(self):      # 在实例化对象后,只要使用"print(对象)",就会触发这个函数,并且会执行这个函数。
        return '["%s",%s,"%s"]' %(self.name,self.age,self.sex)

peo1=People('sudaa',18,'male')
print(peo1)
# ["sudaa",18,"male"]

 

  内置方法二: __del__   当对象被删除之前(当前程序代码结束运行之前),会自动触发

   哪些地方会用到__del__:当一个Python变量关联了系统变量时,涉及到系统回收资源的情况下,会用到__del__。

class Foo():
    def __init__(self,x):
        self.x=x

    def __del__(self):   # 当一个对象的代码执行完毕之后,就会触发这个规则(执行这个函数)
        print('del...')

obj=Foo(111)
print('====>')   # 先执行这个代码,然后执行__del__代码
# ====>
# del...


# 伪代码:当MySQL服务器调用完毕后,关闭系统资源
class MySQL():
    def __init__(self,host,port):
        self.host=host
        self.port=port
        self.conn=connect(host,port)

    def __del__(self):
        self.conn.close

obj=MySQL('1.1.1.',3306)

 

八、内置函数补充  --  详见第8接15讲

 

九、面向对象相关补充:metaclass(一)

  9.1、创建类的2中方式

# 方式一
class Foo(object):
    CITY = "bj"

    def func(self,x):
        return x+1

# 方式二
Coo=type("Coo",(object,),{"CITY":"bj","func":lambda self,x:x+1})

  9.2、类由自定义的type创建,通过metaclass可以指定当前的类由哪一个type创建。 

class MyType(type):
    def __init__(self,*args,**kwargs):
        print("创建类之前")
        super().__init__(*args,**kwargs)
        print("创建类之后")


class Foo(object,metaclass=MyType):  # 当前类,由type类创建(每个类默认metaclass=type)
    CITY = "bj"

    def func(self,x):
        return x+1

# 右键运行后的效果如下,虽然没有实例化对象,但是metaclass=MyType时,会创建"MyType"这个类
# 创建类之前
# 创建类之后

# 执行过程:
1.MyType的__init__方法
2.MyType的__call__方法
3.Foo的__new__方法
4.Foo的__init__方法

  9.3、总结metaclass

     1.默认类都是由type实例化创建。

     2.某个类指定mrtaclass=MyType,那么当前类的所有派生类(子类)都由MyType创建。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章