Python分享-5-面向对象

Python分享第五节

最近受邀给团队中的小伙伴们分享Python知识,本篇文章是第五节课的基本内容。

一、课前准备

  1. 复习Python的基础语法与基础数据类型

二、课堂主题

第五课主要学习程序设计中面向对象的基本知识

三、本节目标

1、掌握Python中对象的创建与使用

2、掌握对象的方法和属性

3、掌握面向对象的三大特征:封装继承、多态

四、知识要点

4.1、匿名函数

匿名函数: 顾名思义就是函数没有名字,在Python中,使用lambda关键字定义的函数就是匿名函数。

优点:简化代码,增加运行效率。

特点: 匿名函数只适合做一下简单的操作,返回值不需要加上return。

下面举个例子:

普通函数:

def func(a,b,c): 
    return a+b+c

print(func(1,2,3))

匿名函数:

result = (lambda a,b,c : a+b+c)(1,2,3) 
print(result)

4.2、面向对象编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。

OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把 函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。

面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息并处理,计算机程序的执行就是一系列消息在各个对象之间传递。

在自然界中,万事万物皆为对象。在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。

假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示:

std1 = { 'name': 'Curry', 'score': 98 }

std2 = { 'name': 'James', '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()

面向对象的设计思想是从自然界中来的,因为在自然界中,(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具 体的Student,比如,Bart SimpsonLisa Simpson是两个具体的Student

所以,面向对象的设计思想是抽象出Class,根据Class创建Instance

面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

4.3、类和对象

面向对象编程的2个非常重要的概念:类和对象

对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念——

就相当于制造飞机时的图纸,用它来进行创建的飞机就相当于对象

4.3.1、类

物以类聚,人以群分。具有相似内部状态和运动规律的实体的集合(或统称为抽象)。具有相同属性行为事物的统称类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以对应多个对象

4.3.2、对象

某个具体事物的存在在现实世界中可以是看得见摸得着的,可以是直接使用的

4.3.3、类和对象之间的关系

类就是创建对象的模板

4.3.4、定义类和创建对象

定义一个类,格式如下:

class 类名:

    方法列表


class Plane(object):

    def fly(self): 
        print("飞机可以飞行")

说明:

  • objectPython里所有类的最顶级父类
  • 类名的命名规则按照”大驼峰命名法”
  • info是一个实例方法,第一个参数一般是self,表示实例对象本身
  • Python中,可以根据已经定义的类去创建出一个或多个对象

创建对象的格式为:

对象名1 = 类名()
对象名2 = 类名()
对象名3 = 类名()

class Plane(object): 
    """fly 是一个实例方法,类对象可以调用实例方法,实例方法的第一个参数一定是self"""

    def fly(self):
        """当对象调用实例方法时,
        Python会自动将对象本身的引用做为参数,传递到实例方法的第一个参数self里"""

        print(self)
        print("飞机可以飞行")
        print("self各不同,对象是出处。")

# Plane这个类 实例化了一个对象

plane = Plane()

# 对象调用实例方法fly(),执行fly()里的代码
# .表示选择属性或者方法
plane.fly()

print(plane) 
# 打印对象,则默认打印对象在内存的地址,结果等同于fly里的print(self)

4.4、对象的属性和方法

4.4.1、添加和获取对象的属性

class Hero(object):
    """定义了一个英雄类,可以移动和攻击""" 
    def move(self):
    """实例方法"""
        print("正在前往战场...")
# 实例化了一个英雄对象
hero = Hero()

# 给对象添加属性,以及对应的属性值
hero.name = "妲己" # 姓名
hero.hp = 2600 # 生 命 值
# 通过.成员选择运算符,获取对象的属性值
print("英雄 %s 的生命值 :%d" % (hero.name, hero.hp))
# 通过.成员选择运算符,获取对象的实例方法
hero.move()

4.4.2、通过self获取对象属性

class Hero(object):
    """定义了一个英雄类,可以移动和攻击""" 
    def move(self):
        """实例方法"""
        print("正在前往战场...")

    def info(self):
        """在类的实例方法中,通过self获取该对象的属性"""
        print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
# 实例化了一个英雄对象
hero = Hero()
# 给对象添加属性,以及对应的属性值
hero.name = "妲己" # 姓名
hero.hp = 2600 # 生 命 值
# 通过.成员选择运算符,获取对象的属性值
hero.info()    # 只需要调用实例方法info(),即可获取英雄的属性
hero.move()

4.4.3、__init__魔法方法

__init__()方法

魔法方法Python 类里提供的,以两个下划线开始,两个下划线结束的方法,叫做魔法方法。

__init__()就是一个魔法方法, 通常用来做属性初始化赋值操作 。

如果类中没有写 __init__() 方法,Python会自动创建,但是不执行任何操作。 如果为了能够在完成自己想要的功能,可以自己定义__init__()方法。

class Hero(object):
    def __init__(self):
        """在类实例化对象的时候,会被自动调用""" 
        self.name = "hero" # 姓 名
        self.hp = 2600 # 生命值

    """定义了一个英雄类,可以移动和攻击""" 
    def move(self):
        """实例方法"""
        print("正在前往战场...")

    def info(self):
        """在类的实例方法中,通过self获取该对象的属性"""
        print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
# 实例化了一个英雄对象
hero = Hero()

# 通过.成员选择运算符,获取对象的属性值
hero.info()
# 只需要调用实例方法info(),即可获取英雄的属性
hero.move()

__init__()方法的注意点

  • __init__()方法,在创建一个对象时默认被调用,不需要手动调用
  • __init__(self)中的self参数,不需要开发者传递,Python解释器会自动把当前的对象引用传递过去
带参数的__init__()方法
class Hero(object):
    def __init__(self, name, hp):
        """在类实例化对象的时候,会被自动调用""" 
        self.name = name # 姓 名
        self.hp = hp # 生命值

    """定义了一个英雄类,可以移动和攻击""" 
    def move(self):
        """实例方法"""
        print("正在前往战场...")

    def info(self):
        """在类的实例方法中,通过self获取该对象的属性"""
        print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
# 实例化了一个英雄对象
hero1 = Hero("妲己", 2600)
hero2 = Hero("武则天", 3000)
# 通过.成员选择运算符,获取对象的属性值
hero1.info()
# 只需要调用实例方法info(),即可获取英雄的属性
hero2.move()

print(hero1)
print(hero2)

# 不同对象的属性值的单独保存
print(id(hero1.name)), print(id(hero2.name))
# 同一个类的不同对象,实例方法共享
print(id(hero1.move())), print(id(hero2.move()))

总结:

  • 通过一个类可以创建多个对象,就好比通过一个模具创建多个实体一样
  • __init__()中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么__init__()中出了self作为第1个形参外还需要2个形参,例如__init__(self, x, y)
  • 在类内部获取属性实例方法,通过self获取
  • 在类外部获取属性实例方法,通过对象名获取
  • 如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址
  • 通过一个类可以创建多个对象,就好比通过一个模具创建多个实体一样
  • 但是实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法

4.5、继承

在程序中,继承描述的是多个类之间的所属关系。

继承就是当你写一个类时,虽然这个类满足了某些功能但是你又想拓展它的功能,此时你就可以使用继承机制再写一个该类的子类,来完成你需要的功能。

一旦子类继承了父类就拥有了父类的方法与域(除私有方法和私有域),在子类中也可以重写父类的方法,覆盖父类中同名的方法,定义与父类相同的域实现子类需要的功能。

继承的优点是提高代码效率避免代码重写

如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。那么类A就是基类,也叫做父类,类B就是派生类,也叫做子类

# 父 类
class A(object):
    def __init__(self): 
        self.num = 10

    def print_num(self): 
        print(self.num + 10)
# 子 类
class B(A): 
    pass

b = B()
print(b.num) 
b.print_num()

4.5.1、单继承

单继承:子类只继承一个父类。

# 定义一个Person类
class Person(object): 
    def __init__(self):
        # 属性
        self.name = "女娲"
    # 实例方法
    def make_person(self):
        print(" <%s> 造了一个人..." % self.name)


# 定义Teacher类,继承了 Person,则Teacher是子类,Person是父类。
class Chinese(Person):
# 子类可以继承父类所有的属性和方法,哪怕子类没有自己的属性和方法,也可以使用父类的属性和方法。
    pass

panda = Chinese()
print(panda)
# 创建子类实例对象
print(panda.name) 
# 子类对象可以直接使用父类的属性
panda.make_person()
# 子类对象可以直接使用父类的方法

总结:

虽然子类没有定义__init__()方法初始化属性,也没有定义实例方法,但是父类有。所以只要创建子类的对象,就默认执行了那个继承过来的__init__()方法

如果子类需要继承某个父类,需要在定义类时在小括号()中填上父类的类名。父类的属性、方法,会被继承给子类

4.5.2、多继承

多继承:子类继承多个父类。

class Human(object):
    def __init__(self, sex):
        self.sex = sex

    def p(self):
        print("这是Human的方法")


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

    def p(self):
        print("这是Person的方法")

    def person(self):
        print("这是person特有的方法")

class Student(Human, Person):
    def __init__(self, name, sex, grade):
    #要想调用特定父类的构造器可以使用父类名.__init__方式。
       Human.__init__(self,sex)
       Person.__init__(self,name)
       self.grade = grade

# ------创建对象 -------------
stu = Student("tom", "male", 88)
print(stu.name,stu.sex,stu.grade)
stu.p()  
# 提问:这里调用的p()方法是谁的p()方法呢?


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


#定义函数
class Son(Human, Teacher):
    def __init__(self, sex, name, age, fan):
        Human.__init__(self, sex)
        Teacher.__init__(self, name, age)
        self.fan = fan

son1 = Son("jerry", "female", 18, "打球")
son1.person()  # 可以调用父类的父类的方法。
son1.p()  # 子类调用众多父类中同名的方法,按继承的顺序查找

在多继承中, 如果不同的父类中存在 同名的方法子类对象在调用方法时,会调用哪一个父类中的方法呢?

Python中,针对提供了一个内置属性 __mro__ 可以查看方法搜索顺序。 MRO是method resolution order,主要用于在多继承时判断 方法、属性的调用路径

print(Student.__mro__)  #Student是多继承后的类名
  • 在搜索方法时,是按照 __mro__ 的输出结果从左至右的顺序查找的
  • 如果在当前类中找到方法,就直接执行,不再搜索
  • 如果没有找到,就查找下一个类 中是否有对应的方法,如果找到,就直接执行,不再搜索
  • 如果找到最后一个类,还没有找到方法,程序报错

4.5.3、重写父类方法

当子类继承父类时,父类的方法满足不了子类的需要,此时可以对父类的方法进行重写

重写的特点:

  • 继承关系
  • 方法名相同
class Person(object): 
    def run(self):
        print("人可以奔跑")

class Student(Person):
    def __init__(self, name, age): 
        self.name = name
        self.age = age
# 因为父类的方法满足不了子类的需要,对其进行重写
    def run(self):
        print("%s跑的比一般人快" % self.name)
stu = Student("王五", 10)
# 调用方法的时候先从本类去找,如果本来没有再去父类去找,会遵循mro的特点
stu.run()

4.5.4、属性方法

类属性和实例属性

类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。

对于公有的类属性,在类外可以通过类对象和实例对象访问

在定义变量名时, 尽量避免定义以下划线开头的变量

  1. _xxx “单下划线 “ 开始的成员变量叫做保护变量,意思是只有类对象(即类实例)和子类对象自己能访问到这些变量,需通过类提供的接口进行访问;不能用’from module import *’导入
  2. __xxx 类中的私有变量/方法名 (Python的函数也是对象,所以成员方法称为成员变量也行得通。),” 双下划线 “ 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
  3. __xxx__ 系统定义名字,前后均有一个“双下划线” ,代表python里特殊方法专用的标识,如 __init__() 代表类的构造函数。
class People(object):

    name = 'Tom' # 公有的类属性
    __age = 12 # 私有的类属性

p = People() 
print(p.name) # 正 确
print(People.name) # 正 确
print(p.age) 
# 错误,不能在类外通过实例对象访问私有的类属性
print(People.age) 
# 错误,不能在类外通过类对象访问私有的类属性实例属性(对象属性)

实例属性(对象属性)

class People(object): 
    address = '江苏' # 类属性
    def __init__(self):
        self.name = 'xiaowang' # 实例属性
        self.age = 20 # 实例属性

p = People()

p.age = 12 # 实例属性
print(p.address) #正确
print(p.name) # 正确
print(p.age) # 正确
print(People.address) # 正确
print(People.name)  # 错误

通过实例(对象)去修改类属性

#通过实例(对象)去修改类属性
class People(object):
    country = 'china' #类属性

print(People.country) 
p = People() 
print(p.country) 
p.country = 'japan'
print(p.country) # 实例属性会屏蔽掉同名的类属性
print(People.country)
del p.country # 删除实例属性
print(p.country)

总结

如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。
如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。

类方法和静态方法

(1) 类方法

类方法是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象, 一般以cls作为第一个参数,能够通过实例对象和类对象去访问。

class People(object): 
    country = 'china'
#类方法,用classmethod来进行修饰
    @classmethod
    def get_country(cls): 
        return cls.country

p = People()
print(p.get_country())  
#可以用过实例对象引用
print(People.get_country())  
#可以通过类对象引用

类方法还有一个用途就是可以对类属性进行修改:

class People(object): 
    country = 'china'

    #类方法,用classmethod来进行修饰@classmethod
    @classmethod
    def get_country(cls): 
        return cls.country

    @classmethod
    def set_country(cls,country): 
        cls.country = country



p = People()
print(p.get_country())  #可以用过实例对象访问
print(People.get_country()) #可以通过类访问

p.set_country('japan')

print(p.get_country()) 
print(People.get_country())

#结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变

(2) 静态方法

需要通过修饰器@staticmethod来进行修饰,静态方法不需要多定义参数,可以通过对象和类来访问。

class People(object): 
    country = 'china'

    @staticmethod #静态方法
    def get_country():
        return People.country

p = People()
# 通过对象访问静态方法
p.get_country()
# 通过类访问静态方法
print(People.get_country())

总结

从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的 必定是类对象的属性和方法; 实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类实例对象来引用

4.6、多态

多态:让具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容(功能)的函数。
特点

  • 只关心对象的实例方法是否同名,不关心对象所属的类型;
  • 对象所属的类之间,继承关系可有可无;
  • 多态的好处可以增加代码的外部调用灵活度,让代码更加通用,兼容性比较强;
  • 多态是调用方法的技巧,不会影响到类的内部设计。

应用场景

1、对象所属的类之间没有继承关系

调用同一个函数fly(), 传入不同的参数(对象),可以达成不同的功能

class Duck(object):                                  # 鸭子类
    def fly(self):
        print("鸭子沿着地面飞起来了")

class Swan(object):                                  # 天鹅类
    def fly(self):
        print("天鹅在空中翱翔")

class Plane(object):                                 # 飞机类
    def fly(self):
        print("飞机隆隆地起飞了")

def fly(obj):                                        # 实现飞的功能函数
    obj.fly()

duck = Duck()
fly(duck)

swan = Swan()
fly(swan)

plane = Plane()
fly(plane)

===运行结果:===================================================================================
鸭子沿着地面飞起来了
天鹅在空中翱翔
飞机隆隆地起飞了

2、对象所属的类之间有继承关系(应用更广)

class gradapa(object):
    def __init__(self,money):
        self.money = money
    def p(self):
        print("this is gradapa")

class father(gradapa):
    def __init__(self,money,job):
        super().__init__(money)
        self.job = job
    def p(self):
        print("this is father,我重写了父类的方法")

class mother(gradapa): 
    def __init__(self, money, job):
        super().__init__(money)
        self.job = job

    def p(self):
         print("this is mother,我重写了父类的方法")
         return 100

#定义一个函数,函数调用类中的p()方法
def fc(obj): 
    obj.p()
gradapa1 = gradapa(3000) 
father1 = father(2000,"工人")
mother1 = mother(1000,"老师")

fc(gradapa1)            #这里的多态性体现是向同一个函数,传递不同参数后,可以实现不同功能.
fc(father1)
print(fc(mother1))
===运行结果:===================================================================================
this is gradapa
this is father,我重写了父类的方法
this is mother,我重写了父类的方法
100

五、总结

  • 本节课的和大家普及了面向对象这个编程中很重要的思想
  • 需要掌握对象的创建,属性和方法的调用
  • 需要掌握继承的使用

文章作者: Yuhao Wang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yuhao Wang !
  目录