Python - - 面向对象 - - 初始面向对象

目录

  • 面向过程vs面向对象
  • 初识面向对象
    • 类的相关知识
    • 对象的相关知识
    • 对象之间的交互
    • 类命名空间与对象、实例的命名空间
    • 类的组合用法
    • 初识面向对象小结
  • 面向对象的三大特性
    • 继承
    • 多态
    • 封装
  • 面向对象的更多说明
    • 面向对象的软件开发
    • 几个概念的说明
    • 面向对象常用术语

1,面向过程vs面向对象

1.1 面向过程

  • 面向过程:核心就是过程(流水线式思维),过程即解决问题的步骤,需要你自己去一步一步的执行完善
    • 优点是:将复杂的问题流程化,进而简单化,极大的降低了写程序的复杂度,只需要顺着执行的步骤,堆叠代码即可
    • 缺点是:扩展性差,一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身

1.2 面向对象

  • 面向对象:面向对象是将事物高度抽象化,必须先建立抽象模型,之后直接使用模型
    • 优点是:可扩展性高,解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中。
    • 缺点是:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。
  • 面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

1.3 名词

类:具有相同特征的一类事物

对象/实例:具体的某一个事物

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

2, 初识面向对象

  • python中一切皆为对象,类型的本质就是类
print(dict)        #类型dict就是类dic
d = dict(name = "abc")    #实例化
print(d)
print(d.pop("name"))       #向d发一条消息,执行d的方法pop

# 结果呈现
<class 'dict'>
{'name': 'abc'}
abc
  • 在python中,用变量表示特征,用函数表示技能,因而具有相同特征和技能的一类事物就是‘类’,对象是则是这一类事物中具体的一个。

2.1 类的相关知识

2.1.1 初始类

  • 声明函数
def functionName(args):
     '函数文档字符串'
      函数体 
  • 声明类
'''
class 类名:
    '类的文档字符串'
    类体
'''

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

2.1.2 类有两种作用:属性引用和实例化

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


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

# 结果呈现
person
<function Person.walk at 0x000000DCE5772BF8>
  • 实例化:类名加括号就是实例化,会自动触发__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对象,有自己具体的名字、攻击力和生命值。
  • 语法:对象名 = 类名(参数)
egg = Person('egon')  #类名()就等于在执行Person.__init__()
#执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。
  • 查看属性&调用方法
print(egg.name)     #查看属性直接 对象名.属性名
print(egg.walk())   #调用方法,对象名.方法名()
  • self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,但是正常人都不会这么做。

  • 类属性的补充

一:我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值

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

2.2 对象的相关知识

# 人狗大战
class Person:    # 定义一个人类 
    role = "person"    # 人的角色属性都是人
    def __init__(self, name, blood, aggr, sex):
        self.name = name    # 角色的名称
        self.blood = blood    # 角色的血量
        self.aggr = aggr        # 角色的攻击力
        self.sex = sex            # 角色的性别

    def attack(self, dog):        # 定义技能函数
        # 人可以攻击狗,这里的狗也是一个对象。
        # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降
        dog.blood -= self.aggr
        if dog.blood <= 0:
            print("%s 打了 %s, %s 被打死了,扑街~~~" % (self.name, dog.name, dog.name))
        else:
            print("%s 打了 %s, %s 掉了 %d 的血~~~" % (self.name, dog.name, dog.name, self.aggr))
  • 对象是关于类而实际存在的一个例子,即实例
  • 对象/实例只有一种作用:属性引用
ming = Person("小明", 100, 10, "None")
print(ming.name)
print(ming.blood)
print(ming.aggr)
print(ming.sex)

# 结果呈现
小明
100
10
None
  • 当然了,你也可以引用一个方法,因为方法也是一个属性,只不过是一个类似函数的属性,我们也管它叫动态属性。
  • 引用动态属性并不是执行这个方法,要想调用方法和调用函数是一样的,都需要在后面加上括号
print(ming.attack)
  • 面向对象小结——定义及调用的固定模式
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 shangshan():
    print("%s, %s 岁, %s, 上山去砍柴")

def drive():
    print("%s, %s 岁, %s, 开车去东北")

def favor():
    print("%s, %s 岁, %s, 最爱大保健")


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

    def shangshan(self):
        print("%s, %s 岁, %s, 上山去砍柴" % (self.name, self.age, self.sex))

    def drive(self):
        print("%s, %s 岁, %s, 开车去东北" % (self.name, self.age, self.sex))

    def favor(self):
        print("%s, %s 岁, %s, 最爱大保健" % (self.name, self.age, self.sex))

ming = Person("小明", "10", "男")
ming.shangshan()
ming.drive()
ming.favor()
zhang = Person("老张", "100", "男")
zhang.shangshan()
zhang.drive()
zhang.favor()

# 结果呈现
小明, 10 岁, 男, 上山去砍柴
小明, 10 岁, 男, 开车去东北
小明, 10 岁, 男, 最爱大保健
老张, 100 岁, 男, 上山去砍柴
老张, 100 岁, 男, 开车去东北
老张, 100 岁, 男, 最爱大保健
  • 非常明显的处理一类事物,这些事物都具有相似的属性和功能
  • 当有几个函数 需要反反复复传入相同的参数的时候,就可以 考虑面向对象
  • 这些参数都是对象的属性

2.3 对象之间的交互

# 创建一个狗类
class Dog:     # 定义一个狗类
    role = 'dog'  # 狗的角色属性都是狗
    def __init__(self, name, blood, aggr, kind):
        self.name = name    # 狗的名称
        self.blood = blood    # 狗的血量
        self.aggr = aggr        # 狗的伤害
        self.kind = kind        # 狗的类型

    def bite(self, person):
        # 狗可以咬人,这里的狗也是一个对象。
        # 狗咬人,那么人的生命值就会根据狗的攻击力而下降
        person.blood -= self.aggr
        if person.blood <= 0:
            print("%s 咬了 %s, %s 被咬死了,扑街~~~" % (self.name, person.name, person.name))
        else:
            print("%s 咬了 %s, %s 掉了 %d 的血~~~" % (self.name, person.name, person.name, self.aggr))
  • 实例化一只实实在在的二哈
ha2 = Dog('二愣子',10,1000,'哈士奇')
ming = Person("小明", 100, 10, "None")
print(ha2.blood)         #看看ha2的生命值
ming.attack(ha2)               #ming打了ha2一下
print(ha2.blood)         #ha2掉了10点血

# 结果呈现
10
小明 打了 二愣子, 二愣子 被打死了,扑街~~~
0
  • 完整代码
class Person:    # 定义一个人类 
    role = "person"    # 人的角色属性都是人
    def __init__(self, name, blood, aggr, sex):
        self.name = name    # 角色的名称
        self.blood = blood    # 角色的血量
        self.aggr = aggr        # 角色的攻击力
        self.sex = sex            # 角色的性别

    def attack(self, dog):        # 定义技能函数
        # 人可以攻击狗,这里的狗也是一个对象。
        # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降
        dog.blood -= self.aggr
        if dog.blood <= 0:
            print("%s 打了 %s, %s 被打死了,扑街~~~" % (self.name, dog.name, dog.name))
        else:
            print("%s 打了 %s, %s 掉了 %d 的血~~~" % (self.name, dog.name, dog.name, self.aggr))

class Dog:     # 定义一个狗类
    role = 'dog'  # 狗的角色属性都是狗
    def __init__(self, name, blood, aggr, kind):
        self.name = name    # 狗的名称
        self.blood = blood    # 狗的血量
        self.aggr = aggr        # 狗的伤害
        self.kind = kind        # 狗的类型

    def bite(self, person):
        # 狗可以咬人,这里的狗也是一个对象。
        # 狗咬人,那么人的生命值就会根据狗的攻击力而下降
        person.blood -= self.aggr
        if person.blood <= 0:
            print("%s 咬了 %s, %s 被咬死了,扑街~~~" % (self.name, person.name, person.name))
        else:
            print("%s 咬了 %s, %s 掉了 %d 的血~~~" % (self.name, person.name, person.name, self.aggr))

ha2 = Dog('二愣子',10,1000,'哈士奇')
ming = Person("小明", 100, 10, "None")
print(ha2.blood)         #看看ha2的生命值
ming.attack(ha2)               #ming打了ha2一下
print(ha2.blood)         #ha2掉了10点血

# 结果呈现
10
小明 打了 二愣子, 二愣子 被打死了,扑街~~~
0
  • circle 属性 半径, 两个方法:求周长和面积 # 2pir pir**2
from math import pi

class Circle:
    '''
    定义了一个圆形类;
    提供计算面积(area)和周长(perimeter)的方法
    '''
    def __init__(self,radius):
        self.radius = radius

    def area(self):
         return pi * self.radius * self.radius

    def perimeter(self):
        return 2 * pi *self.radius


circle =  Circle(10) #实例化一个圆
area1 = circle.area() #计算圆面积
per1 = circle.perimeter() #计算圆周长
print(area1,per1) #打印圆面积和周长

# 结果呈现
314.1592653589793 62.83185307179586

2.4 类命名空间与对象、实例的命名空间

  • 创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
  • 而类有两种属性:静态属性和动态属性
    • 静态属性就是直接在类中定义的变量
    • 动态属性就是定义在类中的方法
  • 其中类的数据属性是共享给所有对象的
>>>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,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常

2.5 面向对象的组合用法

  • 软件重用的重要方式除了继承之外还有另外一种方式,即:组合
  • 组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
  • 组合: 一个对象的属性值是另外一个类的对象
class Dog:
    def __init__(self, name, aggr , hp, kind):
        self.name = name
        self.aggr = aggr
        self.hp = hp
        self.kind = kind

    def bite(self, person):
        person.hp -= self.aggr


class Person:
    def __init__(self, name, aggr, hp, sex):
        self.name = name
        self.aggr = aggr
        self.hp = hp
        self.sex = sex
        self.money = 0

    def attack(self, dog):
        dog.hp -= self.aggr

    def get_weapon(self, weapon):
        if self.money >= weapon.price:
            self.money -= weapon.price
            self.weapon = weapon            # 给角色绑定武器
            self.aggr += weapon.aggr
        else:
            print("余额不足,请充值")


class Weapon:        # 定义武器
    def __init__(self, name, aggr, durability, price):
        self.name = name
        self.aggr = aggr
        self.durability = durability
        self.price = price

    def hand18(self, person):        # 降龙十八掌
        if self.durability > 0:
            person.hp -= self.aggr * 2
            self.durability -= 1

ming = Person("小明", 0.5, 100, "不详")
jin = Dog("金毛", 100, 500, "不详")
w = Weapon("打狗棒", 100 , 3, 998)

# ming 装备打狗棒
ming.money += 1000
ming.get_weapon(w)
print(ming.weapon)
print(ming.aggr)
ming.attack(jin)
print(jin.hp)
ming.weapon.hand18(jin)
print(jin.hp)

# 结果呈现
<__main__.Weapon object at 0x000000457A50EB00>
100.5
399.5
199.5
  • 圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长。
  • 这个时候,我们就首先实现一个圆形类,计算一个圆的周长和面积。然后在"环形类"中组合圆形的实例作为自己的属性来用
from math import pi

class Circle:
    '''
    定义了一个圆形类;
    提供计算面积(area)和周长(perimeter)的方法
    '''
    def __init__(self,radius):
        self.radius = radius

    def area(self):
         return pi * self.radius * self.radius

    def perimeter(self):
        return 2 * pi *self.radius


circle =  Circle(10) #实例化一个圆
area1 = circle.area() #计算圆面积
per1 = circle.perimeter() #计算圆周长
print(area1,per1) #打印圆面积和周长

class Ring:
    '''
    定义了一个圆环类
    提供圆环的面积和周长的方法
    '''
    def __init__(self,radius_outside,radius_inside):
        self.outsid_circle = Circle(radius_outside)
        self.inside_circle = Circle(radius_inside)

    def area(self):
        return self.outsid_circle.area() - self.inside_circle.area()

    def perimeter(self):
        return  self.outsid_circle.perimeter() + self.inside_circle.perimeter()


ring = Ring(10,5) #实例化一个环形
print(ring.perimeter()) #计算环形的周长
print(ring.area()) #计算环形的面积

# 结果呈现
314.1592653589793 62.83185307179586
94.24777960769379
235.61944901923448
  • 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程
class BirthDate:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

class Couse:
    def __init__(self,name,price,period):
        self.name=name
        self.price=price
        self.period=period

class Teacher:
    def __init__(self,name,gender,birth,course):
        self.name=name 
        self.gender=gender
        self.birth=birth
        self.course=course
    def teach(self): 
        print('teaching')

p1=Teacher('egon','male', 
            BirthDate('1995','1','27'), 
            Couse('python','28000','4 months')
           ) 

print(p1.birth.year,p1.birth.month,p1.birth.day) 

print(p1.course.name,p1.course.price,p1.course.period)

# 结果呈现
1995 1 27
python 28000 4 months
  • 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好

2.6 初识面向对象小结

# 定义一个人类

class Person:  # 定义一个人类
    role = 'person'  # 人的角色属性都是人

    def __init__(self, name, aggressivity, life_value, money):
        self.name = name  # 每一个角色都有自己的昵称;
        self.aggressivity = aggressivity  # 每一个角色都有自己的攻击力;
        self.life_value = life_value  # 每一个角色都有自己的生命值;
        self.money = money

    def attack(self,dog):
        # 人可以攻击狗,这里的狗也是一个对象。
        # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降
        dog.life_value -= self.aggressivity
 

# 定义一个狗类

class Dog:  # 定义一个狗类
    role = 'dog'  # 狗的角色属性都是狗

    def __init__(self, name, breed, aggressivity, life_value):
        self.name = name  # 每一只狗都有自己的昵称;
        self.breed = breed  # 每一只狗都有自己的品种;
        self.aggressivity = aggressivity  # 每一只狗都有自己的攻击力;
        self.life_value = life_value  # 每一只狗都有自己的生命值;

    def bite(self,people):
        # 狗可以咬人,这里的狗也是一个对象。
        # 狗咬人,那么人的生命值就会根据狗的攻击力而下降
        people.life_value -= self.aggressivity 

# 创建一个新的兵器类。

class Weapon:
    def __init__(self,name, price, aggrev, life_value):
        self.name = name
        self.price = price
        self.aggrev = aggrev
        self.life_value = life_value

    def update(self, obj):  #obj就是要带这个装备的人
        obj.money -= self.price  # 用这个武器的人花钱买所以对应的钱要减少
        obj.aggressivity += self.aggrev  # 带上这个装备可以让人增加攻击
        obj.life_value += self.life_value  # 带上这个装备可以让人增加生命值

    def prick(self, obj):  # 这是该装备的主动技能,扎死对方
        obj.life_value -= 500  # 假设攻击力是500

# 测试交互 

lance = Weapon('长矛',200,6,100)
egg = Person('egon',10,1000,600)  #创造了一个实实在在的人egg
ha2 = Dog('二愣子','哈士奇',10,1000)  #创造了一只实实在在的狗ha2

#egg独自力战"二愣子"深感吃力,决定穷毕生积蓄买一把武器
if egg.money > lance.price: #如果egg的钱比装备的价格多,可以买一把长矛
    lance.update(egg) #egg花钱买了一个长矛防身,且自身属性得到了提高
    egg.weapon = lance #egg装备上了长矛

print(egg.money,egg.life_value,egg.aggressivity)

print(ha2.life_value)
egg.attack(ha2)   #egg打了ha2一下
print(ha2.life_value)
egg.weapon.prick(ha2) #发动武器技能
print(ha2.life_value) #ha2不敌狡猾的人类用武器取胜,血槽空了一半
  • 按照这种思路一点一点的设计类和对象,最终你完全可以实现一个对战类游戏。
  • 角色的抽象 ,创建类,创建角色(实例化),操作这些实例

3,面向对象的三大特性

3.1 继承

3.1.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'>,)

3.1.2 继承与抽象(先抽象再继承)

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

  • 抽象分成两个层次:

    • 1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
    • 2.将人,猪,狗这三个类比较像的部分抽取成父类。
  • 抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

  • 继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
  • 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

3.1.3 继承与重用性

# 动物类(狗类与鸟类的相同点)
class Animal:
    def __init__(self, name):
        print("执行Animal.__init__")
        self.func()
        self.name = name
    def eat(self):      # 吃
        print("%s eating" % self.name)
    def drink(self):    # 喝
        print("%s drinking" % self.name)
    def func(self):
        print("Animal.func")

class Dog(Animal):
    def guard(self):    # 看家
        print("guarding")
    def func(self):
        print("Dog.func")

class Bird(Animal):
    def __init__(self, name):
        self.name = name
    def lay(self):  # 飞
        print("laying")

dog = Dog("啸天")
dog.drink()
bird = Bird("擎天")
bird.drink()
bird.lay()

# 结果呈现
执行Animal.__init__
Dog.func
啸天 drinking
擎天 drinking
laying
  • 在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时,们不可能从头开始写一个类B,这就用到了类的继承的概念。
  • 通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
class Animal:
    '''
    人和狗都是动物,所以创造一个Animal基类
    '''
    def __init__(self, name, aggr, hp):
        self.name = name    # 人和狗都有自己的昵称;
        self.aggr = aggr    # 人和狗都有自己的攻击力;
        self.hp = hp        # # 人和狗都有自己的生命值;

    def eat(self):
        print("%s is eating" % self.name)
        self.hp += 100

class Dog(Animal):
    pass

class Person(Animal):
    pass

jin = Dog("小金", 100, 500, "金毛")
ming = Person("小明", 100, 300, "不详")
jin.eat()
print(jin.hp)
ming.eat()
print(ming.hp)

# 结果呈现
小金 is eating
600
小明 is eating
400
  • 提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.

3.1.4 派生

  • 当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Animal:
    '''
    人和狗都是动物,所以创造一个Animal基类
    '''
    def __init__(self, name, aggr, hp):
        self.name = name    # 人和狗都有自己的昵称;
        self.aggr = aggr    # 人和狗都有自己的攻击力;
        self.hp = hp        # # 人和狗都有自己的生命值;

    def eat(self):
        print("%s is eating" % self.name)
        self.hp += 100

class Dog(Animal):
    def __init__(self, name, aggr, hp, kind):
        Animal.__init__(self, name, aggr, hp)
        self.kind = kind        # 父类中没有的属性 在子类中出现 叫做派生属性

    def eat(self):
        Animal.eat(self)
        self.tooth = 2
        print("%s 长两颗牙" % self.name)
    def bite(self, person):     # 父类中没有的方法 在子类中出现 叫做派生方法
        person.hp -= self.aggr

class Person(Animal):
    def __init__(self, name, aggr, hp, sex):
        Animal.__init__(self, name, aggr, hp)    # 如果既想实现新的功能也想使用父类原本的功能,还需要在子类中在调用父类
        self.sex = sex   # 父类中没有的属性 在子类中出现 叫做派生属性
        self.money = 0   # 父类中没有的属性 在子类中出现 叫做派生属性

    def attack(self, dog):      # 父类中没有的方法 在子类中出现 叫做派生方法
        dog.hp -= self.aggr


jin = Dog("小金", 100, 500, "金毛")
ming = Person("小明", 100, 300, "不详")
jin.eat()
print(jin.tooth)
print(jin.hp)
ming.eat()
print(ming.hp)

# 只要是子类的对象调用,子类中有的名字,优先调用子类的对象,子类中没有才去找父类,如果父类也没有就报错
# 结果呈现
小金 is eating
小金 长两颗牙
2
600
小明 is eating
400
  • 在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值.

  • 在python3中,子类执行父类的方法也可以直接用super方法.

class Animal:
    '''
    人和狗都是动物,所以创造一个Animal基类
    '''
    def __init__(self, name, aggr, hp):
        self.name = name    # 人和狗都有自己的昵称;
        self.aggr = aggr    # 人和狗都有自己的攻击力;
        self.hp = hp        # # 人和狗都有自己的生命值;

    def eat(self):
        print("%s is eating" % self.name)
        self.hp += 100

class Dog(Animal):
    def __init__(self, name, aggr, hp, kind):
        super().__init__(name, aggr, hp)    # super()  相当于 super(Dog,self) ,直接调用父类 
        # super(Dog, self).__init__(name, aggr, hp)    # super().方法名 不需要自己传self
        # Animal.__init__(self, name, aggr, hp)        # 父类名.方法名 需要自己传self参数
        self.kind = kind        # 派生类属性
    def eat(self):
        print("dog eating")
jin = Dog("小金", 100, 500, "金毛")
print(jin.name)
jin.eat()
super(Dog, jin).eat()

# 结果呈现
小金
dog eating
小金 is eating
  • 通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
  • 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师
>>> class Teacher:
...     def __init__(self,name,gender):
...         self.name=name
...         self.gender=gender
...     def teach(self):
...         print('teaching')
... 
>>> 
>>> class Professor(Teacher):
...     pass
... 
>>> p1=Professor('egon','male')
>>> p1.teach()
teaching

3.1.5 抽象类与接口类

3.1.5.1 接口类

  • 继承有两种用途:
    • 继承基类的方法,并且做出自己的改变或者扩展(代码重用)
    • 声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Alipay:
    '''
    支付宝支付
    '''
    def pay(self,money):
        print('支付宝支付了%s元'%money)

class Applepay:
    '''
    apple pay支付
    '''
    def pay(self,money):
        print('apple pay支付了%s元'%money)


def pay(payment,money):
    '''
    支付函数,总体负责支付
    对应支付的对象和要支付的金额
    '''
    payment.pay(money)


p = Alipay()
pay(p,200)
  • 开发中容易出现的问题
class Alipay:
    '''
    支付宝支付
    '''
    def pay(self,money):
        print('支付宝支付了%s元'%money)

class Applepay:
    '''
    apple pay支付
    '''
    def pay(self,money):
        print('apple pay支付了%s元'%money)

class Wechatpay:
    def fuqian(self,money):
        '''
        实现了pay的功能,但是名字不一样
        '''
        print('微信支付了%s元'%money)

def pay(payment,money):
    '''
    支付函数,总体负责支付
    对应支付的对象和要支付的金额
    '''
    payment.pay(money)


p = Wechatpay()
pay(p,200)   #执行会报错
  • 接口初成:手动报异常:NotImplementedError来解决开发中遇到的问题
class Payment:
    def pay(self):
        raise NotImplementedError

class Wechatpay(Payment):
    def fuqian(self,money):
        print('微信支付了%s元'%money)


p = Wechatpay()  #这里不报错
pay(p,200)      #这里报错了
  • 借用abc模块来实现接口
from abc import ABCMeta,abstractmethod

class Payment(metaclass=ABCMeta):
    @abstractmethod
    def pay(self,money):
        pass


class Wechatpay(Payment):
    def fuqian(self,money):
        print('微信支付了%s元'%money)

p = Wechatpay() #不调就报错了
  • 实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
  • 继承的第二种含义非常重要。它又叫“接口继承”。
  • 接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
  • 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
  • 依赖倒置原则:
    • 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
  • 在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块:
  • http://pypi.python.org/pypi/zope.interface
  • twisted的twisted\internet\interface.py里使用zope.interface
  • 文档https://zopeinterface.readthedocs.io/en/latest/
  • 设计模式:https://github.com/faif/python-patterns
  • 为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。

然后让子类去实现接口中的函数。

这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。

归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。

再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样

3.1.5.2 抽象类

  • 什么是抽象类

    • 与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
  • 为什么要有抽象类

    • 如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
  • 比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。

  • 从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

  • 从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案

  • 在python中实现抽象类


#一切皆文件
import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定义抽象方法,无需实现功能
    def read(self):
        '子类必须定义读功能'
        pass

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def write(self):
        '子类必须定义写功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #报错,子类没有定义抽象方法

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的读取方法')

class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的读取方法')

class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的读取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

3.1.5.3 抽象类与接口类

  • 抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
  • 抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
  • 在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
  • 1.多继承问题
    • 在继承抽象类的过程中,我们应该尽量避免多继承;
    • 而在继承接口的时候,我们反而鼓励你来多继承接口
    • 接口隔离原则:
      • 使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
  • 2.方法的实现
    • 在抽象类中,我们可以对一些抽象方法做出基础实现;
    • 而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现

3.1.5.4 抽象类还是接口类区别:面向对象的开发规范

  • Java里的所有类的继承都是单继承,所有抽象类完美的解决了单继承需求中的规范问题
  • 但对于多继承的需求,由于Java本身语法的不支持,所以创建了接口Interface这个概念来解决多继承的问题
  • python中没有接口类:Java里有接口Interface这个概念 不能实例化
    • python中自带多继承,所以直接用class来实现接口类
  • python中支持抽象类:一般情况下 单继承 不能实例化
    • 且可以实现python代码

3.1.6 多继承 钻石继承

  • 继承顺序

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中才分新式类与经典类
  • 继承原理
  • 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.如果对下一个类存在两个合法的选择,选择第一个父类

3.1.7 继承小结

  • 继承的作用
    • 减少代码的重用
    • 提高代码可读性
    • 规范编程模式
  • 几个名词
    • 抽象:抽象即抽取类似或者说比较像的部分。是一个从具题到抽象的过程。
    • 继承:子类继承了父类的方法和属性
    • 派生:子类在父类方法和属性的基础上产生了新的方法和属性
  • 抽象类与接口类
    -1.多继承问题
    • 在继承抽象类的过程中,我们应该尽量避免多继承;
    • 而在继承接口的时候,我们反而鼓励你来多继承接口
  • 2.方法的实现
    • 在抽象类中,我们可以对一些抽象方法做出基础实现;
    • 而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
  • 单继承与多继承
  • 单继承
    • 先抽象在继承,几个类之间的相同代码抽象出来,成为父类
    • 子类自己没有的名字,就可以使用父类的方法和属性
    • 如果子类自己有,一定是先用自己的
    • 在类中使用self的时候,一定要看清楚self指向谁
  • 多继承
    • 新式类和经典类
      • 多继承寻找名字的顺序: 新式类广度优先,经典类深度优先
      • 新式类中 有一个类名.mro方法,查看广度优先的继承顺序
      • python3 中 有一个super方法,根据广度优先的继承顺序查找上一个类

3.2 多态

  • 多态指的是一类事物有多种形态
  • 动物有多种形态:人,狗,猪
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')
  • 文件有多种形态:文本文件,可执行文件
import abc
class File(metaclass=abc.ABCMeta): #同一类事物:文件
    @abc.abstractmethod
    def click(self):
        pass

class Text(File): #文件的形态之一:文本文件
    def click(self):
        print('open file')

class ExeFile(File): #文件的形态之二:可执行文件
    def click(self):
        print('execute file')
  • 多态性
  • 什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)
  • 多态性是指在不考虑实例类型的情况下使用实例
在面向对象方法中一般是这样表述多态性:
向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。
也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
peo=People()
dog=Dog()
pig=Pig()

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

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()
  • 鸭子类型
    • Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
    • python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
    • 也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
      • 例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
      • 例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系
      • list tuple 这种相似,是自己写代码的时候约束的,而不是通过父类约束的
  • 优点:松耦合 每个相似的类之间都没有影响
  • 缺点:太随意,只能靠自觉
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
    def read(self):
        pass

    def write(self):
        pass

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

3.3 封装

  • 封装

    • 隐藏对象的属性和实现细节,仅对外提供公共访问方式。
  • 好处

      1. 将变化隔离;
      1. 便于使用;
      1. 提高复用性;
      1. 提高安全性;
  • 封装原则

      1. 将不需要对外提供的内容都隐藏起来;
      1. 把属性都隐藏,提供公共方法对其访问。

3.3.1 私有变量和私有方法

  • 在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
    • 在python中只要 __名字,就把这个名字私有化了
    • 私有化了之后,就不能从类的外部直接调用了
    • 静态属性 方法 对象属性 都可以私有化
    • 这种私有化只是从代码级别做了变形,并没有真正的约束
    • 变形机制 _类名__名字 在类外用这个调用,在类的内部直接 __名字 调用
class Person:
    __key = 123     # 私有的静态属性
    def __init__(self, name, passwd):
        self.name = name
        self.__passwd = passwd      # 私有属性

    def __get_pwd(self):          # 私有方法
        print(self.__dict__)
        return self.__passwd    # 只要在类的内部使用私有属性,就会自动的带上 类名

    def login(self):        # 正常的方法调用私有方法
        print(self.__get_pwd())
        self.__get_pwd()

alex = Person("alex", "alex3714")
# print(alex.passwd)
print(alex.__dict__)
print(alex._Person__passwd)  # _类名__属性名
print(alex.get_pwd())class Person:
    __key = 123     # 私有的静态属性
    def __init__(self, name, passwd):
        self.name = name
        self.__passwd = passwd      # 私有属性

    def __get_pwd(self):          # 私有方法
        print(self.__dict__)
        return self.__passwd    # 只要在类的内部使用私有属性,就会自动的带上 类名

    def login(self):        # 正常的方法调用私有方法
        print(self.__get_pwd())
        self.__get_pwd()

alex = Person("alex", "alex3714")
# print(alex.passwd)
print(alex.__dict__)
print(alex._Person__passwd)  # _类名__属性名
print(alex.get_pwd())

3.3.1.1 私有变量

#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class A:
    __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.

#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
  • 这种自动变形的特点:
    • 1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
    • 2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
    • 3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
  • 这种变形需要注意的问题是:
    • 1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
    • 2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形

3.3.1.2 私有方法

  • 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况
>>> class A:
...     def fa(self):
...         print('from A')
...     def test(self):
...         self.fa()
... 
>>> class B(A):
...     def fa(self):
...         print('from B')
... 
>>> b=B()
>>> b.test()
from B
 

#把fa定义成私有的,即__fa
>>> class A:
...     def __fa(self): #在定义时就变形为_A__fa
...         print('from A')
...     def test(self):
...         self.__fa() #只会与自己所在的类为准,即调用_A__fa
... 
>>> class B(A):
...     def __fa(self):
...         print('from B')
... 
>>> b=B()
>>> b.test()
from A
  • 会用到私有的概念的场景
    • 1,隐藏起一个属性,不想让类的外部调用
    • 2,我想保护这个属性,不想让属性随意被改变
    • 3,我想保护这个属性,不被子类继承
# get  set  方法
class Room:     # 房子
    def __init__(self, name, length, width):
        self.__name = name
        self.__length = length  # 长
        self.__width = width    # 宽
    def get_name(self):
        return self.__name

    def set_name(self, newName):
        if type(newName) is str and newName.isdigit() == False:
            self.__name = newName
        else:
            print("不合法的姓名")

    def area(self):     # 面积
        return self.__length * self.__width

jin = Room("金", 2, 1)
print(jin.area())
jin.set_name("2")
print(jin.get_name())
# 假设父类的私有属性 能被 子类调用么
class Foo:
    __key = "123"   # _Foo.__key

class Son(Foo):
    print(Foo.__key)    # _Son.__key

3.3.2 封装与扩展性

  • 封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
        return self.__width * self.__length


#使用者
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area


#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
        return self.__width * self.__length * self.__high


#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1.tell_area()

3.3.2.1 property属性

  • property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
  • 内置装饰器函数 只在面向对象中使用
  • 圆的面积和周长
from math import pi
class Circle:       # 圆
    def __init__(self, r):
        self.r = r
    @property                  # 伪装成属性
    def perimeter(self):       # 周长
        return 2*pi*self.r
    @property
    def area(self):             # 面积
        return self.r**2*pi
c1 = Circle(5)
print(c1.area)      # 圆的面积 可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c1.perimeter) # 圆的周长 可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值

# 结果呈现
78.53981633974483
31.41592653589793

#注意:此时的特性area和perimeter不能被赋值
c1.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
  • 例 BMI指数
例: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 Person:
    def __init__(self, name, high, weight):
        self.name = name
        self.high = high
        self.weight = weight
    @property
    def bmi(self):
        return self.weight / self.high ** 2

jin = Person("金", 1.6, 90)
print(jin.bmi)

# 结果呈现
35.15624999999999
  • 为什么用property:将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
  • 除此之外,看下
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
  • python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo:
    def __init__(self,val):
        self.__NAME=val #将所有的数据属性都隐藏起来

    @property
    def name(self):
        return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)

    @name.setter
    def name(self,value):
        if not isinstance(value,str):  #在设定值之前进行类型检查
            raise TypeError('%s must be str' %value)
        self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME

    @name.deleter
    def name(self):
        raise TypeError('Can not delete')

f=Foo('egon')
print(f.name)
# f.name=10 #抛出异常'TypeError: 10 must be str'
del f.name #抛出异常'TypeError: Can not delete'
  • 一个静态属性property本质就是实现了get(查),set(改),delete(删)三种方法
class Foo:
    @property
    def AAA(self):
        print('get的时候运行我啊')

    @AAA.setter
    def AAA(self,value):
        print('set的时候运行我啊')

    @AAA.deleter
    def AAA(self):
        print('delete的时候运行我啊')

#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
class Foo:
    def get_AAA(self):
        print('get的时候运行我啊')

    def set_AAA(self,value):
        print('set的时候运行我啊')

    def delete_AAA(self):
        print('delete的时候运行我啊')
    AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应

f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
class Goods:

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price


obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
print(obj.price)
del obj.price     # 删除商品原价

3.3.2.2 classmethod

  • 类的操作行为,默认参数 cls 代表一个类
  • 当这个方法的操作只涉及静态属性的时候,就应该使用classmethod来装饰这个方法
class Goods:
    __discount = 0.8
    def __init__(self, name, price):
        self.name = name
        self.__price = price

    @property
    def price(self):
        return self.__price * Goods.__discount
    @classmethod        # 把一个方法变成类中的方法,这个方法就直接可以被类调用,不需要依托任何对象
    def change_discount(cls, new_discount):  # 修改 折扣
        cls.__discount = new_discount

apple = Goods("苹果",5)
print(apple.price)
Goods.change_discount(0.5)  # Goods.change_discount(Goods)
print(apple.price)

# 结果呈现
4.0
2.5

3.3.2.3 staticmethod

  • 静态方法 没有默认的参数 就先函数一样
  • 在完全面向对象的程序中,如果一个函数,既和对象没有关系,也和类没有关系,那么就用staticmethod将这个函数变成一个静态方法
class Login:
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def login(self):pass

    @staticmethod
    def get_name_pwd():     # 静态方法
        usr = input("用户名:")
        pwd = input("密码:")
        Login(usr, pwd)

Login.get_name_pwd()

# 结果呈现
用户名:小明
密码:123

面向对象的更多说明

面向对象的软件开发

很多人在学完了python的class机制之后,遇到一个生产中的问题,还是会懵逼,这其实太正常了,因为任何程序的开发都是先设计后编程,python的class机制只不过是一种编程方式,如果你硬要拿着class去和你的问题死磕,变得更加懵逼都是分分钟的事,在以前,软件的开发相对简单,从任务的分析到编写程序,再到程序的调试,可以由一个人或一个小组去完成。但是随着软件规模的迅速增大,软件任意面临的问题十分复杂,需要考虑的因素太多,在一个软件中所产生的错误和隐藏的错误、未知的错误可能达到惊人的程度,这也不是在设计阶段就完全解决的。

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

    • 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提出的思路,用面向对象语言编写出程序既可。

    在一个大型软件开发过程中,OOP只是很小的一个部分。

    对于全栈开发的你来说,这五个阶段都有了,对于简单的问题,不必严格按照这个5个阶段进行,往往由程序设计者按照面向对象的方法进行程序设计,包括类的设计和程序的设计

几个概念的说明

  • 1.面向对象的程序设计看起来高大上,所以我在编程时就应该保证通篇class,这样写出的程序一定是好的程序(面向对象只适合那些可扩展性要求比较高的场景)
  • 2.很多人喜欢说面向对象三大特性(这是从哪传出来的,封装,多态,继承?漏洞太多太多,好吧暂且称为三大特性),那么我在基于面向对象编程时,我一定要让我定义的类中完整的包含这三种特性,这样写肯定是好的程序
    • 好家伙,我说降龙十八掌有十八掌,那么你每次跟人干仗都要从第一掌打到第18掌这才显得你会了是么:面对敌人,你打到第三掌对方就已经倒下了,你说,不行,你给老子起来,老子还没有show完...
  • 3.类有类属性,实例有实例属性,所以我们在定义class时一定要定义出那么几个类属性,想不到怎么办,那就使劲的想,定义的越多越牛逼
  • 这就犯了一个严重的错误,程序越早面向对象,死的越早,为啥面向对象,因为我们要将数据与功能结合到一起,程序整体的结构都没有出来,或者说需要考虑的问题你都没有搞清楚个八九不离十,你就开始面向对象了,这就导致了,你在那里干想,自以为想通了,定义了一堆属性,结果后来又都用不到,或者想不通到底应该定义啥,那就一直想吧,想着想着就疯了。

面向对象常用术语

  • 抽象/实现

    • 抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
    • 对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
  • 封装/接口

    • 封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
    • 注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
    • 真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
      -(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
  • 合成

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

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

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

    • 多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
    • 多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
    • 冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
  • 自省/反射

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

posted @ 2018-10-08 20:36  小Q渺晓  阅读(175)  评论(0编辑  收藏  举报