Python--面向对象初识

2019年11月08日 阅读数:53
这篇文章主要向大家介绍Python--面向对象初识,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

Python基础-初识面向对象

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象做为程序的基本单元,一个对象包含了数据和操做数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数经过切割成小块函数来下降系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每一个对象均可以接收其余对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,全部数据类型均可以视为对象,固然也能够自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。python

面向过程 VS 面向对象

面向过程的程序设计的核心是(流水线式思惟),过程即解决问题的步骤,面向过程的设计就比如精心设计好的一条流水线 ,考虑周全何时处理什么东西。git

  • 优势:极大的下降了写程序的复杂度,只须要顺着要执行的步骤,堆叠代码便可。
  • 缺点:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
  • 应用场景:一旦完成基本不多改变的场景,著名的例子有Linux内核,git,以及Apache HTTP Server 等。

面向对象的程序设计的核心是对象(上帝式思惟),要理解对象为什么物,必须把本身当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也能够创造出来。面向对象的程序设计比如如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题须要四我的:唐僧,沙和尚,猪八戒,孙悟空,每一个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并很差玩,因而如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。而后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。程序员

  • 优势:解决了程序的扩展性。对某一个对象单独修改,会马上反映到整个体系中,如对游戏中一我的物参数的特征和技能修改都很容易。
  • 缺点:可控性差,没法向面向过程的程序设计流水线式的能够很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即使是上帝也没法预测最终结果。因而咱们常常看到一个游戏人某一参数的修改极有可能致使阴霸的技能出现,一刀砍死3我的,这个游戏就失去平衡。
  • 应用场景:需求常常变化的软件,通常需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

在python 中面向对象的程序设计并非所有。编程

面向对象编程可使程序的维护和扩展变得更简单,而且能够大大提升程序开发效率 ,另外,基于面向对象的程序可使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。安全

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

  • 类:具备相同特征的一类事物(人、狗、老虎)
  • 对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)
  • 实例化:类——>对象的过程

示例说明:

示例说明面向过程和面向对象在程序流程上的不一样之处。app

假设咱们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序能够用一个dict表示:ide

std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }

 而处理学生成绩能够经过函数实现,好比打印学生的成绩:函数

def print_score(std):
    print('%s: %s' % (std['name'], std['score']))

若是采用面向对象的程序设计思想,咱们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有namescore这两个属性(Property)。若是要打印一个学生的成绩,首先必须建立出这个学生对应的对象,而后,给对象发一个print_score消息,让对象本身把本身的数据打印出来。微信支付

class Student(object):

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

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

给对象发消息实际上就是调用对象对应的关联函数,咱们称之为对象的方法(Method)。面向对象的程序写出来就像这样:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

类相关的知识

申明

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

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

#建立一个类
class Data:
    pass

注意:类名一般是大写开头的单词

属性

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


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

 实例化:类名加括号就是实例化,会自动触发__init__函数的运行,能够用它来为每一个实例定制本身的特征

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


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

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

语法:对象名 = 类名(参数)

p1 = Person()

slef

self:在实例化时自动将对象/实例自己传给__init__的第一个参数,你也能够给他起个别的名字,可是长的帅的人都不会这么作。
由于你瞎改别人就不认识

类属性的补充

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

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

对象相关的知识

  • 实例化对象
    格式:对象名 = 类名(参数列表)
    注意:没有参数,小括号也不能省略

访问对象的属性和方法

  • 访问属性
    格式:对象名.属性名
    赋值:对象名.属性名 = 新值
  • 访问方法
    格式:对象名.方法名(参数列表)
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岁,男,最爱大保健
'''


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

    def print_hooby(self, hobby):
        print ("%s, %s岁, %s, %s "%(self.name, self.age, self.sex, hobby))

p1 = Person("小明", 10, "")
p1.print_hooby("上山去砍柴")
p1.print_hooby("开车去东北")
p1.print_hooby("最爱大保健")

p2 = Person("老李", 90, "")
p2.print_hooby("上山去砍柴")
p2.print_hooby("开车去东北")
p2.print_hooby("最爱大保健")
练习题
# 计算一个类实例过多少个对象

class Count:
    count = 0

    def __init__(self):
        Count.count = self.count + 1

a = Count()
b = Count()
c = Count()
d = Count()
print(Count.count)
from math import pi

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

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

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

circle = Circle(3)  # 实例化一个圆
area1 = circle.area()   # 计算圆面积
per1 = circle.perimeter()   # 计算圆周长
print(area1, per1)  # 打印圆面积和周长
一个简单的例子理解面向对象

类名称空间与对象的名称空间

建立一个类就会建立一个类的名称空间,用来存储类中定义的全部名字,这些名字称为类的属性

类有两种属性:静态属性和动态属性

  • 静态属性就是直接在类中定义的变量
  • 动态属性就是定义在类中的方法

其中类的数据属性是共享给全部对象的

class Person:
    name = "小白"
    def fun1(self):
        print(self.name)

p1 = Person()
p2 = Person()

print(id(p1.name)) 
2436828402064
print(id(Person.name))  
2436828402064

而类的动态属性是绑定到全部对象的

class Person:
    name = "小白"
    def fun1(self):
        print(self.name)

p1 = Person()
p2 = Person()

print(p1.fun1)
<bound method Person.fun1 of <__main__.Person object at 0x000002375E4A8C18>>
print(Person.fun1)
<function Person.fun1 at 0x000002375E4AB8C8>

建立一个对象/实例就会建立一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性

查询顺序:

  • 对象.属性:先从对象空间找,若是找不到,再从类空间找,再找不到,再从父类找...
  • 类名.属性:先从本类空间找,若是找不到,再从父类找...

对象与对象之间是互相独立的.

面向对象的组合用法

组合指的是,给一个类的对象封装一个属性,这个属性是另外一个类的对象

什么有什么的关系使用组合。好比:学生有书的关系,可使用组合

圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长。
这个时候,咱们就首先实现一个圆形类,计算一个圆的周长和面积。而后在"环形类"中组合圆形的实例做为本身的属性来用

from math import pi

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

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

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

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

class Ring:
    def __init__(self, radius_outside, radius_inside):
        """
        :param radius_outside: 外圆的半径
        :param radius_inside: 内圆的半径
        """
        self.outside_redius = Circle(radius_outside)
        self.inside_redius = Circle(radius_inside)

    def rin_area(self):
        " 计算圆环的面积,用外圆的面积 - 内圆的面积"
        return self.outside_redius.area() - self.inside_redius.area()

    def rin_perimeter(self):
        " 计算圆环的周长,用外圆的周长 + 内圆的周长"
        return self.outside_redius.perimeter() + self.inside_redius.perimeter()


ring = Ring(9, 3)   # 实例化一个圆环
rinArea = ring.rin_area()   # 计算圆环的面积
rinPer = ring.rin_perimeter()   # 计算圆环的周长
print(rinArea, rinPer)  # 打印圆环的面积和周长
"""
模拟英雄联盟写一个游戏人物的类.
  要求:
  (1)建立一个 Game_role的类.
  (2) 构造方法中给对象封装name,ad(攻击力),hp(血量).三个属性.
  (3) 建立一个attack方法,此方法是实例化两个对象,互相攻击的功能:
      例: 实例化一个对象 盖伦,ad为10, hp为100
      实例化另个一个对象 剑豪 ad为20, hp为80
      盖伦经过attack方法攻击剑豪,此方法要完成 '谁攻击谁,谁掉了多少血,  还剩多少血'的提示功能.
"""

class Gamerole:
    def __init__(self, name, ad, hp):
        self.name = name
        self.ad = ad
        self.hp = hp

    def attack(self, p1):
        hp = p1.hp - self.ad
        print("%s攻击了%s, %s掉了%s血, 还剩%s血" % (self.name, p1.name, p1.name, self.ad, hp))


gailun = Gamerole("盖伦", 10, 100)
jianhao = Gamerole("剑豪", 20, 80)
gailun.attack(jianhao)



# 添加武器进行攻击:斧子,刀,枪,棍,棒...,
# 版本一:
# 代码不合理: 人物利用武器攻击别人,你的动做发起者是人,而不是武器.

class Gamerole:
    def __init__(self, name, ad, hp):
        self.name = name
        self.ad = ad
        self.hp = hp

    def attack(self, p1):
        hp = p1.hp - self.ad
        print("%s攻击了%s, %s掉了%s血, 还剩%s血" % (self.name, p1.name, p1.name, self.ad, hp))


class Arms:
    def __init__(self, name, ad):
        self.name = name
        self.ad = ad

    def fight(self, p1, p2):
        hp = p2.hp - self.ad
        print("%s使用%s攻击了%s, %s掉了%s血, 还剩下%s血" % (p1.name, self.name, p2.name, p2.name, self.ad, hp))

gailun = Gamerole("盖伦", 10, 100)
jianhao = Gamerole("剑豪", 20, 80)
dabaodao = Arms("大宝刀", 20)
dabaodao.fight(gailun, jianhao)


# 版本二:经过组合来实现,让代码合理

class Gamerole:
    def __init__(self, name, ad, hp):
        self.name = name
        self.ad = ad
        self.hp = hp

    def armament_weapon(self, aex): # 调用时传入武器对象
        self.aex = aex

class Arms:
    def __init__(self, name, ad):
        self.name = name
        self.ad = ad

    def fight(self, p1, p2):
        hp = p2.hp - self.ad
        print("%s使用%s攻击了%s, %s掉了%s血, 还剩下%s血" % (p1.name, self.name, p2.name, p2.name, self.ad, hp))


gailun = Gamerole("盖伦", 10, 100)
jianhao = Gamerole("剑豪", 20, 80)
dabaodao = Arms("大宝刀", 20)

gailun.armament_weapon(dabaodao)    # 给盖伦装备了大宝刀这个武器
gailun.aex.fight(gailun, jianhao)
组合小练习

面向对象三大特征

面向对象有三大特性:继承、封装、和多态。

继承

什么是什么的关系使用继承,节省代码。好比:人是动物,狗也是动物,那么人和狗均可以继承动物这个类

继承: 单继承,多继承.

单继承

继承是一种建立新类的方式,在Python中,新建的类能够继承一个或者多个父类,父类又称为基类或超类,新建的类称为派生类或子类

子类能够自动拥有父类中除了私有属性外的其余全部内容。说⽩了, ⼉⼦能够随便用爹的东西。可是朋友们, 必定要认清楚⼀个事情。必须先有爹, 后有⼉⼦。 顺序不能乱,在Python中实现继承很是简单。在声明类的时候, 在类名后⾯面添加一个小括号,把要继承的类传进去就能够完成继承关系。

那么什么状况可使用继承呢?

单纯的从代码层⾯上来看,当两个类具备相同的功能或者特征的时候,能够采用继承的形式。提取一个父类,这个父类中编写两个类中相同的部分。而后两个类分别去继承这个类就能够了。这样写的好处是咱们能够避免写不少重复的功能和代码。

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()

综合示例:

动物都有吃、喝、拉、撒共同属性,各动物有各动物的方法. 定义一个动物类(Animal),定义一个鸟类(Bird),定义一个马类(Horse)

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

    def eat(self):
        print("%s 在吃东西" % self.name)

    def drink(self):
        print("%s 在喝东西" % self.name)

    def pull(self):
        print("%s 在拉粑粑" % self.name)

    def sow(self):
        print("%s 在嘘嘘" % self.name)


class Bird(Animal):
    def flight(self):
        print("%s 在自由的飞翔" % self.name)


class Horse(Animal):
    def running(self):
        print("%s 在飞驰的奔跑" % self.name)


bird1 = Bird("麻雀", 1.2, "")   # 实例化一只鸟
bird1.flight()  # 调用自身独有的方法
bird1.eat()     # 调用父类公共的方法
horse1 = Horse("草原马", 3, "")   # 实例化一匹马
horse1.running()    # 调用自身独有的方法
horse1.pull()       # 调用父类公共的方法

如今要给鸟类添加一个翅膀的属性,给马类添加一个尾巴的属性

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

    def eat(self):
        print("%s 在吃东西" % self.name)

    def drink(self):
        print("%s 在喝东西" % self.name)

    def pull(self):
        print("%s 在拉粑粑" % self.name)

    def sow(self):
        print("%s 在嘘嘘" % self.name)


class Bird(Animal):
    def __init__(self, name, sex, age, wing):
        # 方法一:经过父类名.父类方法名(参数)
        # Animal.__init__(self, name, sex, age)
        # 方法二:经过super().父类方法名(参数(自动传入self))
        super().__init__(name, sex, age)    # 还能够这样写super(Bird,self).__init(name, sex, age)
        self.wing = wing

    def flight(self):
        print("%s 在自由的飞翔" % self.name)


class Horse(Animal):
    def __init__(self, name, sex, age, tail):
        # Animal.__init__(self, name, sex, age)
        super().__init__(name, sex, age)
        self.tail = tail

    def running(self):
        print("%s 在飞驰的奔跑" % self.name)


bird2 = Bird("鹦鹉", 6, "", "红翅膀")   # 实例化一只鸟,加入翅膀
print(bird2.__dict__)       # 查看是否将翅膀属性成功添加
# {'name': '鹦鹉', 'sex': 6, 'age': '母', 'wing': '红翅膀'}

horse2 = Horse("汗血马", 3, "", "长尾巴")    # 实例化一匹马,加入尾巴
print(horse2.__dict__)      # 查看是否将尾巴属性成功添加
# {'name': '汗血马', 'sex': 3, 'age': '公', 'tail': '长尾巴'}

总结:

只执行父类的方法:子类中不要定义与父类同名的方法

只执行子类的方法:在子类建立这个方法

问题:既要执行子类的方法,又要执行父类的方法?

有两种解决方法:

一、在子类中执行父类的方法

  • 父类名.父类方法名(参数)

二、经过super()

  • super().父类方法名(参数(自传self))

多继承

类: 经典类, 新式类

  • 新式类: 凡是继承object类都是新式类.   python3x 全部的类都是新式类,由于python3x中的类都默认继承object.
    class A(object):
        pass
  • 经典类: 不继承object类都是经典类.  python2x:(既有新式类,又有经典类) 全部的类默认都不继承object类,全部的类默认都是经典类.你可让其继承object.
    class A:
        pass

单继承: 新式类,经典类查询顺序同样
多继承:

  • 新式类: 遵循广度优先.  广度优先 : 一条路走到倒数第二级,判断,若是其余路能走到终点,则返回走另外一条路.若是不能,则走到终点.
  • 经典类: 遵循深度优先.  深度优先 : 一条路走到底.
  • 深度优先,广度优先:只能是继承两个类的状况

 

经典类多继承:

class A:
    def func(self):
        print(" IN A")

class B(A):
    def func(self):
        print(" IN B")

class C(A):
    def func(self):
        print(" IN C")

class D(B):
    def func(self):
        print(" IN D")

class E(C):
    def func(self):
        print(" IN E")

class F(D, E):
    def func(self):
        print(" IN F")

func1 = F()
func1.func()
# 执行func方法时:
# 首先去F类找,若是F没有去D类找,若是D类没有去B类找,若是B类没有去A类找,若是A类没有去E类找,若是E类没有去C类找,若是C类尚未则报错
# 查找顺序为F——>D——>B——>A——>E——>C
# 在上述查找func方法的过程当中,一旦找到,则寻找过程当即中断,便不会再继续找了

新式类多继承:

class A(object):
    def func(self):
        print(" IN A")

class B(A):
    def func(self):
        print(" IN B")

class C(A):
    def func(self):
        print(" IN C")

class D(B):
    def func(self):
        print(" IN D")

class E(C):
    def func(self):
        print(" IN E")

class F(D, E):
    def func(self):
        print(" IN F")

func1 = F()
func1.func()

print(F.mro()) # 查询类的继承顺序
# 打印结果:[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
# 执行func方法时:
# 首先去F类找,若是F没有去D类找,若是D类没有去B类找,若是B类没有去E类找,若是E类没有去C类找,若是C类没有去A类找,若是A类尚未则报错
# 查找顺序为F——>D——>B——>E——>C——>A
# 在上述查找func方法的过程当中,一旦找到,则寻找过程当即中断,便不会再继续找了

继承总结

面向对象为何要有继承?继承的好处是什么?

  • 优化代码、节省代码
  • 提升代码的复用性
  • 提升代码的维护性
  • 让类与类之间发生关系

抽象类与接口类

接口类

为何要使用接口类?

接口提取了一群类共同的函数,能够把接口当作一个函数的集合。

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

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

归一化的好处在于:

  • 归一化让使用者无需关心对象的类是什么,只须要知道这些对象都具有了某些功能就能够了,这极大的下降了使用者的使用难度。
  • 归一化使得高层的外部使用者能够不加区分的处理多有接口兼容的对象集合。
    好比:咱们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由两者分别产生一只老鼠和一只松鼠送到你面前,即使是你分别不到底哪只是什么鼠你确定知道他俩都会跑,都会吃,都能呼吸。
    
    再好比:咱们有一个汽车接口,里面定义了汽车全部的功能,而后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,你们只须要学会了怎么开汽车,那么不管是本田,仍是奥迪,仍是大众咱们都会开了,开的时候根本无需关心我开的是哪一类车,操做手法(函数调用)都同样
    好比

接口类示例

需求说明:开发一个支付功能、支持微信支付、支付宝支付、Apple支付等...

版本一:采用归一化设计

# 版本一:采用归一化设计
class Alypay(object):
    """
    支付宝支付
    """
    def pay(self, money):
        print("支付宝支付了 %s 元" % money)


class Wechatpay(object):
    """
    微信支付
    """
    def pay(self, money):
        print("微信支付了 %s 元" % money)


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


def apy(obj, money):
    obj.pay(money)


a1 = Alypay()
w1 = Wechatpay()
p1 = Applepay()
apy(a1, 200)
apy(w1, 100)
apy(p1, 50)
按照上面这种方法是可行的, 可是有个问题, 若是第二个程序员去更改你的代码,若是类中不是定义的pay函数去支付, 那么就会报错,列以下面将apple支付里面的函数(pay)变为(applepay).

版本二:

class Alypay(object):
    """
    支付宝支付
    """
    def pay(self, money):
        print("支付宝支付了 %s 元" % money)


class Wechatpay(object):
    """
    微信支付
    """
    def pay(self, money):
        print("微信支付了 %s 元" % money)


class Applepay(object):
    """
    apple支付
    """
    def applepay(self, money):
        print("apple支付了 %s 元" % money)


def apy(obj, money):
    obj.pay(money)


a2 = Alypay()
w2 = Wechatpay()
p2 = Applepay()
apy(a2, 200)
apy(w2, 100)
apy(p2, 50)     # 这里执行就会报错. 打破了归一化设计原则

版本三:经过abc模块来实现接口,若是类中定义的非模块指定的函数名,咱们让其报错。

from abc import ABCMeta, abstractclassmethod


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


class Alypay(Payment):
    """
    支付宝支付
    """
    def pay(self, money):
        print("支付宝支付了 %s 元" % money)


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


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

def apy(obj, money):
    obj.pay(money)

a1 = Alypay()
w1 = Wechatpay()
p1 = Applepay()     # 执行报错:  TypeError: Can't instantiate abstract class Applepay with abstract methods pay
apy(a1, 200)
apy(w1, 100)
apy(p1, 50)

最终版本:

from abc import ABCMeta, abstractclassmethod


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


class Alypay(Payment):
    """
    支付宝支付
    """
    def pay(self, money):
        print("支付宝支付了 %s 元" % money)


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


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

def apy(obj, money):
    obj.pay(money)

a1 = Alypay()
w1 = Wechatpay()
p1 = Applepay()
apy(a1, 200)
apy(w1, 100)
apy(p1, 50)

抽象类

  • 什么是抽象类?

  抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化; Python抽象类是借助模块来实现

  • 为啥要有抽象类?

  若是说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。

  好比咱们又香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。你永远没法吃到一个叫作水果的东西。

  从设计角度看,若是类是从现实对象抽取而来的,那么抽象类就是基于类抽象而来的。

  从实现角度看,抽象类与普通类的不一样之处在于:抽象类中有抽象的方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口类有点相似,但实际上是不一样的。

在Python中实现抽象类

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


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

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

# class Txt(All_file):
#     pass
#
# t1 = Txt()    # 报错,子类没有定义抽象方法
# 报错内容  TypeError: Can't instantiate abstract class Txt with abstract methods read, write


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("进程数据的写方法")


wenbenfile = Txt()

satafile = Sata()

processfile = Process()

# 这样你们都被归一化了,也就是一切皆文件的思想
wenbenfile.read()
satafile.write()
processfile.read()

print(wenbenfile.all_type)
print(satafile.all_type)
print(processfile.all_type)

抽象类与接口类的总结

抽象类的本质仍是类,指的是一组类的类似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的类似性。

抽象类是一个介于类和接口直接的一个概念,同时具有类和接口的部分特性,能够用来实现归一化设计

在python中,并无接口类这种东西,即使不经过专门的模块来定义接口,咱们也应该有一些基本的概念。

一、多继承问题

在继承抽象类的过程当中,应该尽可能避免多继承;

而在继承接口的时候,反而鼓励多继承接口

接口隔离原则:
使用多个专门的接口,而不使用单一的总接口。即客户端不该该依赖那些不须要的接口。

二、方法的实现

在抽象类中,咱们能够对一些抽象方法作出基础实现;

而在接口类中,任何方法都只是一种规范,具体的功能须要子类实现

多态

多态

多态指的是一类事物的多种形态。一种类型的多种形态  多个子类去继承父类,那么每个子类都是这个父类的一种形态

动物有多种形态:人、狗、猪

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:利用标准库中定义的各类‘与文件相似’的对象,尽管这些对象的工做方式像文件,但他们没有继承内置文件对象的方法

class TxtFile(object):
    def read(self):
        pass
    
    def write(self):
        pass


class DiskFile(object):
    def read(self):
        pass
    
    def write(self):
        pass
例子

例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系

class TxtFile(object):
    def read(self):
        pass

    def write(self):
        pass


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

    def write(self):
        pass


# 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)
例子

封装

封装

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

好处

  • 将变化隔离;
  • 便于使用;
  • 提升复用性;
  • 提升安全性;

封装原则

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

私有变量和私有方法

在Python中使用双下划线开头的方式将属性隐藏起来(设置为私有的)

私有变量

# 其实这仅仅只是一种变形操做
# 类中全部双下划线开头的名称如__x 都会自动造成:_类名__x的新式:


class A(object):
    name = "小白"
    __age = 18  # 类的数据属性就应该是共享的,可是语法上是能够把类的数据属性设置成私有的如__age,会变形为_A__age

    def __init__(self):
        self.__x = 10   # 变形为self._A__X

    def __foo(self):    # 变形为_A__foo
        print(self.__age)
        print("From A")

    def fun1(self):
        self.__foo()    # 只有在类内部才能够经过__foo的形式访问到.


obj1 = A()
print(obj1.name)
# print(obj1.__age)   # 执行报错;由于实例化对象不能访问私有变量
# print(A.__age)  # 执行报错;由于类名不能访问私有变量
# 对于私有变量,类的外部不能访问.

obj1.fun1() # 对于私有静态变量,类的内部能够访问.

print(A._A__age)    # A._A__age是能够访问到的,即这种操做并非严格意义上的限制外部访问,仅仅只是一种语法意义上的变形

对于私有变量来讲,只能在本类中内部访问,类的外部,派生类均不可访问.

这种自动变形的特色:

1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果

2.这种变形其实正是针对外部的变形,在外部是没法经过__x这个名字访问到的。

3.在子类定义的__x不会覆盖在父类定义的__x,由于子类中变造成了:_子类名__x,而父类中变造成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是没法覆盖的。

这种变形须要注意的问题是:

1.这种机制也并无真正意义上限制咱们从外部直接访问属性,知道了类名和属性名就能够拼出名字:_类名__属性,而后就能够访问了,如a._A__N

2.变形的过程只在类的内部生效,在定义后的赋值操做,不会变形

私有方法

在继承中,父类若是不想让子类覆盖本身的方法,能够将方法定义为私有的

class A(object):
    def func1(self):
        print(" In A ")

    def func2(self):
        self.func1()


class B(A):
    def func1(self):
        print(" In B ")


c1 = B()
c1.func2()
# 打印结果: In B



class A(object):
    def __func1(self):
        print(" In A ")

    def func2(self):
        self.__func1()


class B(A):
    def __func1(self):
        print(" In B ")

c2 = B()
c2.func2()
# 打印结果: In A

封装与扩展性

封装在于明确区份内外,使得类实现者能够修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合做基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

# 类的设计者
class Room(object):
    def __init__(self, name, owner, length, width, high):
        self.name = name
        self.owner = owner
        self.__length = length
        self.__width = width
        self.__high = high

    def tell_area(self):
        return self.__length * self.__width     # 对外提供的接口,隐藏了内部的实现细节,此时咱们想求的是面积


# 使用者
r1 = Room("客厅", "小白", 20, 20 ,20)
area = r1.tell_area()   # 使用者调用接口tell_area
print(area)


# 类的设计者,轻松的扩展了功能,而类的使用者彻底不须要改变本身的代码
class Room(object):
    def __init__(self, name, owner, length, width, high):
        self.name = name
        self.owner = owner
        self.__length = length
        self.__width = width
        self.__high = high

    def tell_area(self):    # 对外提供的接口,隐藏内部实现,此时咱们想求的是体积,内部逻辑变了,只需求修该下列一行就能够很简答的实现,并且外部调用感知不到,仍然使用该方法,可是功能已经变了
        return self.__length * self.__width * self.__high


# 使用者
r1 = Room("客厅", "小白", 20, 20 ,20)
area = r1.tell_area()   # 对于仍然在使用tell_area接口的人来讲,根本无需改动本身的代码,就能够用上新功能
print(area)

属性

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 Bmi(object):
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height

    @property
    def calculation_bmi(self):
        return "%s 的bmi 值%s" % (self.name, self.weight / self.height ** 2)


p1 = Bmi("小白", 54, 1.65)
# print(p1.calculation_bmi())
print(p1.calculation_bmi)

示例二:

import math


class Circle(object):
    def __init__(self, radius):
        """
        :param 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.area)   # 能够向访问数据属性同样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter)  # 同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''



#注意:此时的特性area和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
计算园的周长和面积

为何要使用property

将一个类的函数定义成特性之后,对象再去使用的时候obj.name,根本没法察觉本身的name是执行了一个函数而后计算出来的,这种特性的使用方式遵循了统一访问的原则

列如

ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为何你们 不说“女儿”,就像“parent”原本是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开

属性的改值:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.__age = age if type(age) is int else print("您输入的年龄类型有误,请输入int类型")

    @property   # 将一个方法假装成一个属性
    def age(self):
        return self.__age   # obj.age访问的是self.__age(这也是真实值的存放位置)

    @age.setter
    def age(self, a1):
        """判断,修改的年龄必须是数字"""
        self.__age = a1 if type(a1) is int else print("您输入的年龄类型有误,请输入int类型")

    @age.deleter
    def age(self):
        del self.__age


p1 = Person("小白", 20)
print(p1.age)
p1.age = 22 # 属性的改值, 一运行,就会自动执行类中的@age.setter下的函数
print(p1.age)
del p1.age

一个静态属性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
View Code
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
View Code

怎么用?

class Goods(object):

    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


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

property 总结

property 装饰器函数,内置函数,帮助你将类中的方法假装成属性,特性

  调用方法的时候不须要主动加括号

  让程序的逻辑性更合理

@方法名.setter  装饰器,修改被property装饰的属性的时候会调用被这个装饰器装饰的方法,除了self以外还有一个参数,被修改的值

@方法名.deleter 装饰器,当要删除被property装饰的属性的时候会调用被这个装饰器装饰的方法

类方法 classmethod

classmethod 类方法的装饰器 内置函数
使用类名调用,默认传类名做为第一个参数
不用对象命名空间中的内容,而用到了类命名空间中的变量(静态属性),或者类方法或静态方法

class A(object):

    def func(self): # 普通方法
        print(self)

    @classmethod
    def func1(cls): # 类方法
        print(cls)

a1 = A()
a1.func()

# 类方法: 经过类名调用的方法,类方法中第一个参数约定俗称cls,python自动将类名(类空间)传给cls.
A.func(a1)

A.func1()
a1.func1()  # 对象调用类方法,cls 获得的是类自己.

类方法的应用场景:

  • 类中 有些方法是不须要对象参与.
  • 对类中的静态变量进行改变,要用类方法.
  • 继承中,父类获得子类的类空间.

静态方法 staticmethod

class A:

    @staticmethod
    def login(username, password):
        if username == '小白' and password == 123:
            print('登陆成功')
        else:
            print('登陆失败...')


A.login('小白',1234)