Python OOP面向对象

一、什么是面向对象的程序设计

1、面向过程

程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。

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

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

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

2、面向对象

程序设计:核心是对象二字

对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。

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

缺点:

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

2. 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。只有对象之间交互才能准确地知道最终的结果。

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

面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。

 

二、类与对象

如果说对象是特征与技能的结合体,类则是一系列对象相似的特征与技能的结合

现实世界中

一定先有的一个个具体存在的对象,然后随着人类文明的发展由人类站在不同的角度总结出的类

注意:
1. 类是抽象的概念,而对象才是具体存在的事物
2,站在不同的角度,可以总结出不同的类

'''
在现实世界中站在人这个角度,先有对象才有类
    对象1:mogu
        特征:
            五官=眼、口、耳、鼻、喉
            姓名=mogu
        技能:
            学习、吃饭、睡觉
    对象2:小明
        特征:
            五官=眼、口、耳、鼻、喉
            姓名=小明
        技能:
            学习、吃饭、睡觉
    对象1:小李
        特征:
            五官=眼、口、耳、鼻、喉
            姓名=小李
        技能:
            学习、吃饭、睡觉    
            
现实中的人类:
    相似的特征:五官=眼、口、耳、鼻、喉
    相似的技能:学习、吃饭、睡觉    
'''
现实世界:抽象举例

程序中

一定是要先定义类,后调用类来产生对象

这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类

不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象

'''
#在程序中,务必保证:先定义(类),后使用(产生对象)
PS:
  1. 在程序中特征用变量标识,技能用函数标识
  2. 因而类中最常见的无非是:变量和函数的定义
'''
class People:
    five_sense_organs='eyes, mouth, ears, nose, throat'
    mood='joys,sorrows'
    def learn(self):
        print('%s is learning'%self)

    def eat(self):
        print('%s is eating'%self)

    def sleep(self):
        print('%s is sleeping'%self)
# 
print(People.five_sense_organs)
print(People.__dict__)
'''
#注意:
  1、类中可以有任意python代码,这些代码在类定义阶段便会执行
  2、因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过People.__dict__查看
  3、对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的.语法
  4、.点是访问属性的语法,类中定义的名字,都是类的属性

#程序中类的用法
. 专门用来访问属性,本质操作的就是__dict__
People.five_sense_organs #等于经典类的操作People.__dict__['five_sense_organs']
People.mood='Joys,Sorrows' #等于经典类的操作People.__dict__['mood']='Joys,Sorrows'
People.money='一亿' #等于经典类的操作People.__dict__['money']='一亿'
del People.money #等于经典类的操作People.__dict__.pop('money')

'''
#程序中的对象
#调用类,或称为实例化,得到对象
mogu=People()
xiaoming=People()
#如此,mmogu、xiaoming都一样了,而这三者除了相似的属性之外还各种不同的属性,这就用到了__init__
#注意:该方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值

class People:
    five_sense_organs='eyes, mouth, ears, nose, throat'
    mood='joys,sorrows'
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

    def learn(self):
        print('%s is learning'%self)

    def eat(self):
        print('%s is eating'%self)

    def sleep(self):
        print('%s is sleeping'%self)

mogu=People('mogu',18,'male')
xiaoming=People('xiaoming',28,'female')

print(mogu.name)            #mogu
mogu.hobby='read,music'     
print(mogu.__dict__)        #{'name': 'mogu', 'age': 18, 'sex': 'male', 'hobby': 'read,music'}
# print(mogu.hobby)
del mogu.hobby
print(mogu.__dict__)        #{'name': 'mogu', 'age': 18, 'sex': 'male'}

详细说明类的__init__方法

#方式一、为对象初始化自己独有的特征
class People:
    country='China'
    x=1
    def run(self):
        print('----->', self)

# 实例化出三个空对象
obj1=People()
obj2=People()
obj3=People()

# 为对象定制自己独有的特征
obj1.name='mogu'
obj1.age=18
obj1.sex='male'

obj2.name='xiaoqing'
obj2.age=28
obj2.sex='female'

obj3.name='xiaoming'
obj3.age=28
obj3.sex='female'

# print(obj1.__dict__)
# print(obj2.__dict__)
# print(obj3.__dict__)
# print(People.__dict__)


#方式二、为对象初始化自己独有的特征
class People:
    country='China'
    x=1
    def run(self):
        print('----->', self)

# 实例化出三个空对象
obj1=People()
obj2=People()
obj3=People()

# 为对象定制自己独有的特征
def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male'
    obj.name = x
    obj.age = y
    obj.sex = z

chu_shi_hua(obj1,'mogu',18,'male')
chu_shi_hua(obj2,'xiaoqing',28,'female')
chu_shi_hua(obj3,'xiaoming',28,'female')

#方式三、为对象初始化自己独有的特征
class People:
    country='China'
    x=1

    def chu_shi_hua(obj, x, y, z): 
        obj.name = x
        obj.age = y
        obj.sex = z

    def run(self):
        print('----->', self)


obj1=People()
# print(People.chu_shi_hua)
People.chu_shi_hua(obj1,'mogu',18,'male')

obj2=People()
People.chu_shi_hua(obj2,'xiaoqing',28,'female')

obj3=People()
People.chu_shi_hua(obj3,'xiaoming',28,'female')

# 方式四、为对象初始化自己独有的特征
class People:
    country='China'
    x=1

    def __init__(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male'
        obj.name = x
        obj.age = y
        obj.sex = z

    def run(self):
        print('----->', self)

obj1=People('mogu',18,'male') #People.__init__(obj1,'mogu',18,'male')
obj2=People('xiaoqing',28,'female') #People.__init__(obj2,'xiaoqing',28,'female')
obj3=People('xiaoming',28,'female') #People.__init__(obj3,'xiaoming',28,'female')


# __init__方法
# 强调:
#   1、该方法内可以有任意的python代码
#   2、一定不能有返回值
__init__

PS:

1. 站的角度不同,定义出的类是截然不同的,详见面向对象实战之需求分析

2. 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类...... 

3. 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类的特殊属性

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

三、属性查找以及类与类型

一、类的属性

类有两种属性:数据属性和函数属性

1. 类的数据属性是所有对象共享的

2. 类的函数属性是绑定给对象用的

#类的数据属性是所有对象共享的,id都一样
class People:
    money=1
    def learn(self):
        print('is learning')

obj1=People()
obj2=People()
obj3=People()

print(id(People.money))

print(id(obj1.money))
print(id(obj2.money))
print(id(obj3.money))

'''
1643995360
1643995360
1643995360
1643995360
'''


# 类的函数属性是绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样
# ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准
print(People.learn)
print(obj1.learn)
print(obj2.learn)
print(obj3.learn)
'''
<function People.learn at 0x0000000002201D90>
<bound method People.learn of <__main__.People object at 0x0000000002203048>>
<bound method People.learn of <__main__.People object at 0x0000000002203080>>
<bound method People.learn of <__main__.People object at 0x00000000022036D8>>
'''
示例

在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常 

#练习:编写一个学生类,产生一堆学生对象,要求有一个计数器(属性),统计总共实例了多少个对象
class Student:
    count=0
    def __init__(self,name):
        self.name=name
        Student.count+=1
obj1=Student('a')
obj2=Student('z')
obj3=Student('q')
obj4=Student('w')
obj5=Student('r')
obj6=Student('s')
obj7=Student('x')
print(Student.count)
小练习

二、绑定到对象的方法特殊之处

类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数。

类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法

强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)

注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self

三、类与类型

类即类型

  提示:python的class术语与c++有一定区别,与 Modula-3更像。

  python中一切皆为对象,且python3中类与类型是一个概念,类型就是类

#类型dict就是类dict
>>> list
<class 'list'>

#实例化的到3个对象l1,l2,l3
>>> l1=list()
>>> l2=list()
>>> l3=list()

#三个对象都有绑定方法append,是相同的功能,但内存地址不同
>>> l1.append
<built-in method append of list object at 0x10b482b48>
>>> l2.append
<built-in method append of list object at 0x10b482b88>
>>> l3.append
<built-in method append of list object at 0x10b482bc8>

#操作绑定方法l1.append(3),就是在往l1添加3,绝对不会将3添加到l2或l3
>>> l1.append(3)
>>> l1
[3]
>>> l2
[]
>>> l3
[]
#调用类list.append(l3,111)等同于l3.append(111)
>>> list.append(l3,111) #l3.append(111)
>>> l3
[111] 
类与类型(重要)

四、继承与派生

一、什么是继承

继承是一种新建类的方式,新建的类称之为子类,被继承的类称之为基类、父类、超类
继承描述的是一种“遗传”的关系:子类可以重用父类的属性

在python中的继承注意两点
  1. 在python中支持一个子类同时继承多个父类

  2. python中类分为两种:

    新式类:但凡继承object的类,以及该类的子类。。。都是新式类,在python3中一个类如果没有继承类,默认继承object类

        即python3中所有的类都是新式类

    经典类: 没有继承object的类,以及该类的子类。。。都是经典类,在python2中才区分新式类与经典类

二、继承与抽象

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

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

抽象分成两个层次: 

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

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

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

 

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

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

 

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

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

    def drink(self):
        print('%s is drinking'%self.name)

    def la(self):
        print('%s is laing'%self.name)

    def sa(self):
        print('%s is saing'%self.name)

class People(Animal):
    pass
class Pig(Animal):
    pass
class Dog(Animal):
    pass

aobama=People('奥巴码')
meixi=People('梅戏')
maidou=Pig('麦兜')
zjq=Pig('猪坚强')
shinubi=Dog('史努比')
shipaike=Dog('史派克')

aobama.drink() #奥巴码 is drinking
maidou.drink() #麦兜 is drinking
shinubi.drink()#史努比 is drinking
View Code

三、派生

子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名

那么调用新增的属性时,就以自己为准了。

class People:
    country='China'
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def learn(self): #父类的学习方法
        print('%s is learn from People' % self.name)

class Teacher(People):
    def __init__(self,name,age,sex,level):
        People.__init__(self,name,age,sex)#重用父类的方法
        self.level=level

    def teach(self):
        print('%s is teach'%self.name)

class Student(People):
    def __init__(self,name,age,sex,score):
        People.__init__(self,name,age,sex)#重用父类的方法
        self.score=score

    def learn(self): #派生学生自己的学习方法
        print('%s is learn from Student'%self.name)

egon=Teacher('egon',28,'male',99)
mogu=Student('mogu',24,'male',0)
示例派生

 四、在继承背景下,属性查找的优先级

一、单继承背景:

  先找对象自身空间 -----> 对象的类空间 ------> 对象类的父类 -----> 一级一级往上查找

二、多继承背景:

  多继承有一个菱形问题

1、非菱形结构

则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

 从左往右查找 :A--->B--->E--->C--->F--->D--->G (查找顺序)

2、菱形结构

属性的查找方式有两种,分别是:深度优先和广度优先

    新式类:但凡继承object的类,以及该类的子类。。。都是新式类
在python3中一个类如果没有继承类,默认继承object类,即python3中所有的类都是新式类

经典类 : 没有继承object的类,以及该类的子类。。。都是经典类
在python2中才区分新式类与经典类

   当类是经典类时:将按照广度优先的顺序查找(A--->B--->E--->G--->C--->F--->D)

   当类是新式类时:将按照深度优先的顺序查找(A--->B--->E--->C--->F--->D--->G)

class A(object):
    def test(self):
        print('from A')

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性

#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类

继承顺序
View Code

3、继承原理

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

print(People.mro())

[<class '__main__.People'>, <class '__main__.Animal'>, <class 'object'>]

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

五、继承总结

继承的两种用途

一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合

二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能

五、子类派生的新方法重用父类功能的方式

子类派生出的新方法中重用父类功能

方式一:

# 指名道姓地访问某一个类的函数
# 注意:
# 1. 该方式与继承是没有关系的
# 2. 访问是某一个类的函数,没有自动传值的效果

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


class Teacher(People):
    def __init__(self,name,age,sex,level):
        People.__init__(self,name,age,sex)#重用父类的方法
        self.level=level
        
    def teach(self):
        print('%s is teach'%self.name)

class Student(People):
    def __init__(self,name,age,sex,score):
        People.__init__(self,name,age,sex)#重用父类的方法
        self.score=score
        
    def learn(self):
        print('%s is learn'%self.name)

egon=Teacher('egon',28,'male',99) 
mogu=Student('mogu',24,'male',0) 
子类派生新方法重用父类功能方式一

 方式二:

# 在子类派生出的新方法中重用父类功能的方式二:只能在子类中用
# 在python2:super(自己的类名,对象自己)
# 在python3:super()
# 调用super()会得到一个特殊的对象,该特殊的对象是专门用来引用父类中的属性的,!!!完全参照mro列表!!!

class People:
    country='China'
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def learn(self):
        print('%s is learn from People' % self.name)

class Teacher(People):
    def __init__(self,name,age,sex,level):
        super(People,self).__init__(name,age,sex)#重用父类的方法二
        self.level=level

    def teach(self):
        print('%s is teach'%self.name)

class Student(People):
    def __init__(self,name,age,sex,score):
        super().__init__(name,age,sex)#重用父类的方法二(简写)
        self.score=score

    def learn(self):
        print('%s is learn from Student'%self.name)

egon=Teacher('egon',28,'male',99)
mogu=Student('mogu',24,'male',0)

# 注意:
# 1. 该方式与继承严格依赖于继承的mro列表
# 2. 访问是绑定方法,有自动传值的效果
子类重用父类的方式二
#例子
class A:
    def test(self):
        print('A.test()')
        super().test()

class B:
    def test(self):
        print('from B')

class C(A,B):
    pass

obj=C()
print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
obj.test()
'''
A.test()
from B
'''
super()例子

强调:二者使用哪一种都可以,但最好不要混合使用 

六、组合(减少代码冗余)

软件重用的重要方式除了继承之外还有另外一种方式组合

组合:一个类中以另外一个类的对象作为数据属性,称为类的组合

组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同

1.继承的方式

通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人

2.组合的方式

用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...

class People:
    country = 'China'

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

    def choose_course(self, course_name):
        '''选择课程方法'''
        self.course.append(course_name)

    def check_course(self):
        '''查看课程信息方法'''
        for course_obj in self.course:  # 查看老师课程信息
            print(course_obj.name, course_obj.price)  # 英语 9999 #此处还可自定义显示格式


class Teacher(People):
    '''老师类'''
    pass


class Student(People):
    '''学生类'''
    pass


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


egon = Teacher('egon', 28, 'male')  # 实例化老师
mogu = Student('mogu', 24, 'male')  # 实例化学生
english = Course('英语', 9999, 12)  # 实例化课程

egon.choose_course(english)  # 老师选择了课程 
mogu.choose_course(english)  # 蘑菇选择了课程

egon.check_course()
mogu.check_course()
#ps :   Teacher类与Student类 无任何继承关系 却使用了课程对象的信息
#        此处即为组合实现
组合示例

 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好

七、多态与多态性

多态:指的是一种事物的多种形态

多态性:可以在不用考虑对象具体类型的情况下而直接使用对象

优点:归一化,简单化对象的使用

import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')

多态性

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,我们可以定义一个统一的接口来使用,即归一化思想
def func(obj):
    obj.talk()

多态性有什么好处

1.增加了程序的灵活性

  以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

2.增加了程序额可扩展性

  通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用  

import abc

class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def speak(self):
        pass


class Cat(Animal):
    def speak(self):
        print('喵喵叫')

cat1=Cat()
#cat1.speak()

def func(obj):
    obj.speak()
func(cat1)#甚至连调用方式也无需改变,就能调用猫的speak功能
'''
这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的speak方法,即func(cat1)
'''

鸭子类型

python崇尚鸭子类型

即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’

python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象

也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法

#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
    def read(self):
        pass

    def write(self):
        pass

class DiskFile:
    def read(self):
        pass
    def write(self):
        pass
View Code

例2:其实大家一直在享受着多态性带来的好处,比如Python的序列类型有多种形态:字符串,列表,元组,多态性体现如下

#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))

#我们可以在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()

len(s)
len(l)
len(t)
View Code

八、封装

1、什么是封装

封 指的是隐藏,该隐藏是为了明确地区分内外,即该隐藏是对外不对内(在类外部无法直接访问隐藏的属性,而在类内部是可以访问)

就是将数据属性或者函数属性存放到一个名称空间

class People:
    __country='China' #_People__country='China'
    __n=111           #_People__n=111

    def __init__(self,name):
        self.__name=name #self._People__name=name

    def run(self):
        print('%s is running' %self.__name) #self._People__name

# print(People.__country)#访问不到会报错

obj=People('egon')
# print(obj.__name)#访问不到会报错
print(obj.run)
obj.run()

print(People.__dict__)
print(People._People__country) #这样就能访问到
print(obj.__dict__)
print(obj._People__name)#这样就能访问到

# 总结这种隐藏需要注意的问题:
# 1. 这种隐藏只是一种语法上的变形,并没有真的限制访问
# 2. 这种变形只在类定义阶段检测语法时变形一次,类定义阶段之后新增的__开头的属性不会发生变形

# 3. 在继承中,父类如果不想让子类覆盖自己的方法,可以在该方法前加__开头

class Foo:
    def __f1(self): #_Foo__f1
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.__f1() #self._Foo__f1()

class Bar(Foo):
    def __f1(self): #_Bar__f1
        print('Bar.f1')


obj=Bar()
obj.f2()
View Code

2、为什么要用封装

# 封装的真实意图:把数据属性或函数属性装起来就是为了以后使用的,封起来即藏起来是为不让外部直接使用
# 1.封数据属性:把数据属性藏起来,是为了不让外部直接操作隐藏的属性,而通过类内开辟的接口来间接地操作属性,
#             我们可以在接口之上附加任意的控制逻辑来严格控制使用者对属性的操作
class People:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def tell_info(self):
        print('<name:%s age:%s>' % (self.__name, self.__age))

    def set_info(self, name, age):
        if type(name) is not str:
            print('名字必须是str类型')
            return
        if type(age) is not int:
            print('年龄必须是int类型')
            return

        self.__name = name
        self.__age = age


peo = People('xiaoming', 28)

# obj.tell_info()
# obj.set_info('xiaozhang',19)
# obj.set_info(123,19)
peo.set_info('xiaoli',19)
peo.tell_info()
严格控制使用者对属性的操作
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、特性(property)

什么是特性property

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

例一:BMI指数

(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)

成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
  体质指数(BMI)=体重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86
class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height

    @property
    def bmi(self):
        return self.weight / (self.height ** 2)


obj=People('xiaoming',55,1.72)
# print(obj.bmi())

print(obj.bmi)
View Code

例二:圆的周长和面积 

import math
class Circle:
    def __init__(self,radius): #圆的半径radius
        self.radius=radius

    @property
    def area(self):
        return math.pi * self.radius**2 #计算面积

    @property
    def perimeter(self):
        return 2*math.pi*self.radius #计算周长

c=Circle(10)
print(c.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
#注意:此时的特性arear和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
View Code

为什么要用property

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则(将函数属性伪装为数据属性)

九、绑定方法与非绑定方法

# 类中定义的函数有两大类(3小种)用途,一类是绑定方法,另外一类是非绑定方法

# 1. 绑定方法:
# 特点:绑定给谁就应该由谁来调用,谁来调用就会将谁当作第一个参数自动传入
# 1.1 绑定给对象的:类中定义的函数默认就是绑定对象的
# 1.2 绑定给类的:在类中定义的函数上加一个装饰器classmethod


# 2. 非绑定方法
# 特点: 既不与类绑定也不与对象绑定,意味着对象或者类都可以调用,但无论谁来调用都是一个普通函数,根本没有自动传值一说

一、绑定方法

绑定给对象的方法(略)前面全是绑定给对象的,主要学绑定给类的

绑定给类的方法(classmethod)

  classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法

IP='127.0.0.1'
PORT=3306
settings
import settings

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

     def tell_info(self):
        print('%s : %s' %(self.ip,self.port))

    @classmethod
    def from_conf(cls):
        return cls(settings.IP, settings.PORT)

obj1=Mysql.from_conf()

obj1.tell_info()
类方法使用示例

二、非绑定方法

在类内部用staticmethod装饰的函数即非绑定方法,就是普通函数

statimethod不与类或对象绑定,谁都可以调用,没有自动传值效果

class Mysql:
    def __init__(self):
        self.id=self.create_id()

    @staticmethod
    def create_id():#不需要传任何参数
        import uuid
        return uuid.uuid4()
    
    
obj=Mysql()
print(obj.id)
非绑定方法

三、二者的区别

# HOST='127.0.0.1'
# PORT=3306


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

    @staticmethod
    def from_conf():
        return MySQL(settings.HOST,settings.PORT)

    # @classmethod #哪个类来调用,就将哪个类当做第一个参数传入
    # def from_conf(cls):
    #     return cls(settings.HOST,settings.PORT)

    def __str__(self):
        return '就不告诉你'

class Mariadb(MySQL):
    def __str__(self):
        return '<%s:%s>' %(self.host,self.port)


m=Mariadb.from_conf()
print(m) #我们的意图是想触发Mariadb.__str__,但是结果触发了MySQL.__str__的执行,打印就不告诉你:
View Code

十、反射

1、什么是反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

 python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

四个可以实现自省的函数

下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

# 反射:指的是通过字符串来操作属性
class Foo:
    def __init__(self,name):
        self.name=name
obj=Foo('xiaoming')

hasattr

hasattr() #判断object中有没有一个name字符串对应的方法或属性
# print(hasattr(obj,'name')) #'name' in obj.__dict__

getattr

getattr()#从对象中获取命名属性
# print(getattr(obj,'name')) #obj.__dict__['name']
# print(getattr(obj,'age')) #obj.__dict__['age']
# print(getattr(obj,'age',None)) #obj.__dict__['age']

setattr

setattr()#将给定对象上的命名属性设置为指定的值。
# setattr(obj,'age',18) #obj.age=18
# setattr(obj,'name','XIAOMING') #obj.name='XIAOMING'
# print(obj.__dict__)

delattr

delattr()#从给定对象中删除命名属性。
# delattr(obj,'name')# del obj.name
# print(obj.__dict__)
class Ftp:
    def get(self):
        print('get')

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

    def login(self):
        print('login')

    def run(self):
        while True:
            choice=input('>>>: ').strip()
            if hasattr(self,choice):
                method=getattr(self,choice)
                method()
            else:
                print('命令不存在')


obj=Ftp()
obj.run()
FTP应用反射

十一、内置方法

#1. __doc__  表示类的描述信息
class Foo:
    '''
    描述信息啦
    '''
    def func(self):
        pass
print(Foo.__doc__)
__doc__ 表示类的描述信息
#2. __module__ 和 __class__
#2.1 __module__  表示当前操作的对象在哪个模块
#2.2 __class__   表示当前操作的对象的类是什么 (__class__.__name__ 即可拿到类名)

#settings 文件内定义一个类
class Foo:
    def __init__(self,name):
        self.name=name

#src 文件
import settings

obj=settings.Foo('mogu')
print(obj.__module__)  #settings
print(obj.__class__.__name__) #Foo
__module__ 和 __class__
#3. __str__  如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值。
class Foo:
    def __str__(self):
        return '我是__str__啦啦'

obj=Foo()
print(obj)
__str__ 打印对象时输出
#4.  __del__:在对象被删除前自动触发, 在该方法内应该执行与该对象有关的系统资源的回收操作
class Foo:
    def __init__(self,filename,encoding='utf-8'):
        self.f=open(filename,'r',encoding=encoding)

    def __del__(self):
        # print('run.....')
        self.f.close()

obj=Foo('a.txt')
del obj #obj.__del__()
__del__资源的回收
#5. __call__ 对象后面加括号,触发执行
class Foo:
    def __init__(self):
        pass
    def __call__(self, *args, **kwargs):
        print('__call__')

obj = Foo()  # 执行 __init__
obj()  # 执行 __call__
__call__ 对象后面加括号,触发执行
。。。
__dict__  类或对象中的所有成员
#7. __getitem__ 和 __setitem__ 和 __delitem__
#用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo:
    def __getitem__(self, item):
        print('__getitem__',item)
    def __setitem__(self, key, value):
        print('__setitem__',key,value)
    def __delitem__(self, key):
        print('__delitem__',key)
obj=Foo()
result=obj['mogu'] #自动触发 __getitem__ mogu
obj['k']='xiaoming' #自动触发 __setitem__ k xiaoming
del obj['k'] #自动触发 __delitem__ k
__getitem__ 和 __setitem__ 和 __delitem__
#8. __iter__ 用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 __iter__
class Foo(object):

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

    def __iter__(self):
        return iter(self.list)

obj = Foo([11,22,33,44])

for i in obj:
    print(i)

#等同于(详细去看迭代器)
obj = iter([11, 22, 33, 44])
for i in obj:
    print(i)
__iter__ 用于迭代器
#9. __new__ 和 __metaclass__
# 详见元类
__new__ 和 __metaclass__

十二、元类metaclass

链接转至:http://www.cnblogs.com/linhaifeng/articles/8029564.html

十三、python中OOP的常用术语

1、抽象/实现

抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。

对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。 

2、封装/接口

封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。

注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”

真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明

(注意:对外透明的意思是外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)

3、合成

合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。

4、派生/继承/继承结构

派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。

5、泛化/特化

基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。

6、多态与多态性

多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气

多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。

冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样

7、自省/反射

自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__

十四、面向对象的软件开发

软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程

    面向对象的软件工程包括下面几个部分:

1.面向对象分析(object oriented analysis ,OOA)

    软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。

    建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。

 

2 面向对象设计(object oriented design,OOD)

    根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。

    首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。

    在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述

 

3 面向对象编程(object oriented programming,OOP)

    根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python

 

4 面向对象测试(object oriented test,OOT)

    在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。

    面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。

 

5 面向对象维护(object oriendted soft maintenance,OOSM)

    正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。

    由于使用了面向对象的方法开发程序,使用程序的维护比较容易。

    因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。

    在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。

    现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。

 

posted @ 2018-08-30 15:02  xiaomogugu  阅读(400)  评论(0编辑  收藏  举报