Python基础6 面向对象

本节内容

 

面向对象编程介绍

为什么要面向对象开发?

面向对象的特性:封装,继承,多态

类,方法

 

引子

假设现在我们需要开发一款简单的游戏,譬如叫做人兽战争。我们需要简单的2个角色,一个人,一个怪兽,而且人和兽有不同的技能,不如人可以借助道具打怪兽,怪兽可以攻击人,咬人。

 

这时候我们可以写下如下的简单代码来描述这2个角色模型:

def person(name,age,sex,equipment):
    data = {
        "name": name,
        "age": age,
        "sex": sex,
        "equipment": equipment
            }
    return data


def monster(name, monster_type):
    data = {
        "name": name,
        "monster": monster_type
    }
    return data

上述2个方法其实相当于造就了2个模型,游戏的开始,我们需要生成一个实际的人跟怪兽的对象。

m1 = monster("凯多", "兽人")
p1 = person("路飞", 20, "F", "海贼")
p2 = person("索隆", 21, "F", "剑豪")

角色对象生成了,我们需要考虑下一个问题,人跟兽有不同的功能,人打兽,兽咬人,等。这时候我们就需要再写不同的函数,想执行那个技能就去调用那个函数。

def bark(m):
    print("monster [%s]: wow~ wow~" % m["name"])


def walk(p):
    print("human [%s] is running" % p["name"])


m1 = monster("凯多", "兽人")
p1 = person("路飞", 20, "F", "海贼")
p2 = person("索隆", 21, "F", "剑豪")

bark(m1)
walk(p1)

好了,我们已经基本实现了,所有最简单的功能了(这里就不一一执行简单的代码了)。

但是,在调用的时候,一不小心调用错了方法。

p2 = person("索隆", 21, "F", "剑豪")
bark(p2)

竟然把人的对象,传给怪兽的方法。事实上,其实这个没有错,但是从设计逻辑上来说确实不应该这样。如何实现这个限制呢?

def person(name, age, sex, equipment):
    def walk(p):
        print("human [%s] is running" % p["name"])
    data = {
        "name": name,
        "age": age,
        "sex": sex,
        "equipment": equipment,
        "walk": walk
            }
    return data


def monster(name, monster_type):
    def bark(m):
        print("monster [%s]: wow~ wow~" % m["name"])
    data = {
        "name": name,
        "monster": monster_type,
        "bark": bark
    }
    return data

m1 = monster("凯多", "兽人")
p1 = person("路飞", 20, "F", "海贼")
p2 = person("索隆", 21, "F", "剑豪")

m1["bark"](p1)

如上,实现了这个限制,人只能用人技能。

但是我们只是组织了2个完全不同的角色之间功能的混用。但是有可能相同的角色,有些属性不一样呢?比如,海贼跟海军,因为都是人,我们需要建一个person()的模板。海军跟海贼可以相互打击,但是海军不可以杀死被绑架的人质,海贼可以。这些又怎么实现呢。也许你会说简单,只需要在杀人质的方法里面加上一个判断就可以。没错,这判断按解决了这个问题。但是你会发现海贼跟海军还有许多其他的区别,但是同事又有很多共性,如果每个都单独判断会累死。

也许你又会说,那既然这样,直接写2个角色,反正区别这么多。但是他们有很多共性啊,比如吃饭,走路。这样写就要很多相同点的一起重复书写了。

大概就是这么个意思。话题到这,我们引入了面向对象。

 

 

面向过程 VS 面向对象

编程范式

首先编程是用特定语法+数据结构+算法组成的代码告诉计算器如何执行任务的过程。一个程序是为了得到一个结果而编写的一组命令的集合。正所谓条条大路通罗马,实现一个任务的结果可以有多种多样不同的方式。对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。不同的编程范式本质上代表对各种各种类型的任务采取的不同的解决问题的思路,大多数语言只支持一种编程范式,当然也有语言可以同时支持多种编程范式。两种最重要的编程范式分别是面向过程编程和面向对象编程。

 

面向过程编程(Procedural Programming)

面向过程编程依赖 - 你猜到了- procedures,一个procedure包含一组要被进行计算的步骤, 面向过程又被称为top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。

举个例子,数据库备份,分三步,连接,备份,测试备份可用性。

代码如下:

def db_conn():

    print("connecting db...")
def db_backup(dbname):
    print("导出数据库...",dbname)
    print("将备份文件打包,移至相应目录...")

def db_backup_test():
    print("将备份文件导入测试库,看导入是否成功")

def main():
    db_conn()
    db_backup('my_db')
    db_backup_test()

if __name__ == '__main__':
    main()

上述代码的问题也显而易见,如果你对程序进行修改,对你修改的部分有依赖或者调用功能的各个部分也要跟着一起修改。随着程序的越来越大,维护的难度不言而喻。

所以我们一般认为,如果只是用代码写一些简单的脚本,去做一些一次性的任务,用面向过程的方式是极好的,但是如果你要处理的任务是复杂的,切需要不断维护和迭代的,那面向对象就是最方便的了。

 

 

面向对象编程

OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

面向对象的几个核心特性如下

Class 类
一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法

Object 对象
一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同

Encapsulation 封装
在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法

Inheritance 继承
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承

Polymorphism 多态
多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。
对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。
多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定。

 

 

面向对象编程(Object-Oriented Programming )介绍

OOP的三大特性:继承,封装,多态。

对于编程语言初学者来讲,OOP不是一个很容易理解的编程方式。大家素日按都知道三大特性,大家也都知道如何定义类,方法等面向对象的常用语法。但是一旦到真正操刀写程序的时候,很多时候还是喜欢用函数式编程来完成代码的书写。特别是初学者,很容易陷入一个窘境就是“我知道面向对象,我也会写类,但我依然没发现在使用了面向对象后,对我们的程序开发效率或其它方面带来什么好处,因为我使用函数编程就可以减少重复代码并做到程序可扩展了,为啥子还用面向对象?”。个人觉得,原因应该还是因为没有充分了解面向对象能带来的好处。

无论用什么形式编程,我们都要明确记住以下原则:

一、写重复代码是很不好的低级行为

二、代码是需要经常变更的

开发正规的程序跟写个运行一次久扔掉的小脚本是有很大的不同的。你的代码总是要进行不断修改的,不是修复bug就是需求的变更,增加。为了日后程序的修改更可扩展性,你写的代码一定要遵循易读易改的原则。

其实OOP编程的主要作用也是使你的代码修改和扩展变的更容易,那么小白要问了,既然函数都能实现这个需求了,还要OOP干毛线用呢? 呵呵,说这话就像,古时候,人们打仗杀人都用刀,后来出来了枪,它的主要功能跟刀一样,也是杀人,然后小白就问,既然刀能杀人了,那还要枪干毛线,哈哈,显而易见,因为枪能更好更快更容易的杀人。函数编程与OOP的主要区别就是OOP可以使程序更加容易扩展和易更改。

 

我们再写一个简单的警匪游戏的例子来引入吧。

比如先定义一个恐怖分子,一个警察:

#role 1
name = 'Alex'
role = 'terrorist'
weapon = 'AK47'
life_value = 100
 
#rolw 2
name2 = 'Jack'
role2 = 'police'
weapon2 = 'B22'
life_value2 = 100

这是一想,游戏一开始,任何一方把对面杀了就结束了,实在无趣,我们多加一些人进去。

#role 1
name = 'Alex'
role = 'terrorist'
weapon = 'AK47'
life_value = 100
money = 10000
 
#rolw 2
name2 = 'Jack'
role2 = 'police'
weapon2 = 'B22'
life_value2 = 100
money2 = 10000
 
#role 3
name3 = 'Rain'
role3 = 'terrorist'
weapon3 = 'C33'
life_value3 = 100
money3 = 10000
 
#rolw 4
name4 = 'Eric'
role4 = 'police'
weapon4 = 'B51'
life_value4 = 100
money4 = 10000

其实写到这边,小编已经写不下去了,硬撑一下,大家一定想到这样太散了,我们把这些家人字典中

roles = {
    1:{'name':'Alex',
       'role':'terrorist',
       'weapon':'AK47',
       'life_value': 100,
       'money': 15000,
       },
    2:{'name':'Jack',
       'role':'police',
       'weapon':'B22',
       'life_value': 100,
        'money': 15000,
       },
    3:{'name':'Rain',
       'role':'terrorist',
       'weapon':'C33',
       'life_value': 100,
       'money': 15000,
       },
    4:{'name':'Eirc',
       'role':'police',
       'weapon':'B51',
       'life_value': 100,
       'money': 15000,
       },
}

角色都出来了,再想想人物要是更多。。。实在是写不下去了。姑且吐槽到这边。开始面向对象的编程。

OOP:

class Role(object):
    def __init__(self, name, age, weapon, life_v=100, money=1500):
        self.name = name
        self.age = age
        self.weapon = weapon
        self.life_v = life_v
        self.money = money

    def shot(self):
        print("shot...")

    def get_shot(self):
        print("get shot...")

    def buy_gun(self, gun_name):
        print("bought [%s]..." % gun_name)

r1 = Role("dandy", "police" "M4A1")
r2 = Role("smith", "terrorist", "AK47")
# 生成了r1,r2两个角色

先不钻牛角尖,相比较靠函数拼拼凑凑出来的写法,可以看出上述的代码改进了两点:

1.代码量少了很多很多

2.角色和它具有的功能可以一目了然

 

 

上面运用到了类,下面我们就来解析一下类的基本定义与语法:

class human(object):
    print("who am I?")

H = human()  # 这是对类的实例化
# 此时的H 就是human的实例化对象
# 实例化其实就是以human 类为模板,在内存里开辟一段内存空间,存上数据,赋值一个变量名

上面的代码其实太简单了,连最简单的传参都没有,继续:

class human(object):

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

    def sayname(self):
        print("Hi, nice to meet you. My name is %s!" % self.name)

H = human("dandy", "man")
H.sayname()

可能这个时候很懵。。。__init__是什么?self优势干嘛用?

首先先把最后的2句(实例化 & 调用方法)注释掉。

没实例直接打印human类输出如下:

# H = human("dandy", "man")
# H.sayname()

print(human)

output:
<class '__main__.human'>

这个输出代表什么?它的意思是,即使不实例化,这个human类本身也是存在于内存中的,那实例化后会怎么样呢?我引用一张图讲解下,例化时,会产生什么化学反应呢?

根据上图不难看出,其实self就是实例本身!实例化的过程中,python会自动把这个实例本身通过self参数传进去。

明白了类的基本定义,接下来分析下这块代码:

class Role(object): #定义一个类, class是定义类的语法,Role是类名,(object)是新式类的写法,必须这样写,以后再讲为什么

    def __init__(self,name,role,weapon,life_v=100,money=15000): #初始化函数,在生成一个角色时要初始化的一些属性就填写在这里

        self.name = name #__init__中的第一个参数self,和这里的self都 是什么意思? 看下面解释

        self.role = role

        self.weapon = weapon
     self.life_v = life_v
     self.money = money

解释这个__init__,我们引入一个类的概念叫做,初始化方法也叫构造方法。在类被调用时,这个方法(虽然它是函数形式,但在类中就不叫函数了,叫方法)会自动执行,进行一些初始化的动作,所以我们这里写__init__(self,name,role,weapon,life_v=100,money=15000)就是要在创建一个角色时给它设置这些属性,那第一个self干嘛用?

 

初始化一个角色,就需要调用这个类一次:

r1 = Role("dandy", "police" "M4A1")#生成一个角色 , 会自动把参数传给Role下面的__init__(...)方法 
r2 = Role("smith", "terrorist", "AK47")#生成一个角色

我们可以发现,上面创建角色的时候,其实并没有给__init__传值,程序也没有报错,是因为类在调用自己的__init__(....)自己帮你给self赋值了

1 r1 = Role("dandy", "police" "M4A1") #此时self 相当于 r1 , Role(r1,'dandy','police','M4A1’)  
2 r2 = Role("smith", "terrorist", "AK47")#此时self 相当于 r2, Role(r2,'smith','terrorist','AK47’)

其实在执行r1 = Role("dandy", "police" "M4A1")时,python的解释器干了2件事:

1. 在内存中开辟一块空间指向r1这个变量名

2. 调用Role这个类并执行其中的__init__(…)方法,相当于Role.__init__(r1,'dandy','police',M4A1’),这么做是为什么呢? 是为了把'dandy','police',’M4A1’这3个值跟刚开辟的r1关联起来 因为关联起来后,你就可以直接r1.name, r1.weapon 这样来调用啦。所以,为实现这种关联,在调用__init__方法时,就必须把r1这个变量也传进去,否则__init__不知道要把那3个参数跟谁关联呀。

 

所以这个__init__(…)方法里的,self.name = name , self.role = role 等等的意思就是要把这几个值 存到r1的内存空间里。

现在来解释为什么后面的几个方法还需要self参数呢?不是在初始化角色的时候,就已经把角色的属性跟r1绑定好了?

def buy_gun(self, gun_name):
     print("bought [%s]..." % gun_name)

如果要调用类里面的方法,需要这样写:

r1 = Role("dandy", "police" "M4A1")
r1.buy_gun("AKW”) #python 会自动帮你转成 Role.buy_gun(r1,”B21")

执行结果:

bought [AKW]...

依然没给self传值 ,但Python还是会自动的帮你把r1 赋值给self这个参数, 为什么呢? 因为,你在buy_gun(..)方法中可能要访问r1的一些其它属性呀, 比如这里就访问 了r1的名字,怎么访问呢?你得告诉这个方法呀,于是就把r1传给了这个self参数,然后在buy_gun里调用 self.name 就相当于调用r1.name 啦,如果还想知道r1的生命值 有多少,直接写成self.life_v就可以了。 说白了就是在调用类中的一个方法时,你得告诉人家你是谁。

所以,根据以上,我们可以得出结论:

1. 上面的r1 = Role("dandy", "police" "M4A1")的过程,叫做类的实例化,就是把一个虚拟的抽象的类,通过这句代码变成一个具体的对象了这个对象叫做实例。

2. 刚刚定义的这个类体现了面向对象的第一个基本特征,封装其实就是使用构造方法将内容封装到某个具体对象中,然后通过对象直接或者self间接获取被封装的内容

 

面向对象的特性:

封装

封装,顾名思义,就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

封装你是面向对象的特征之一,是对象和类概念的主要特征。

继承

面向对象编程(OOP)语言的一个主要功能就是继承。继承是指这样的一种能力:它可以使用所继承父类的所以功能,并无需重新编写原来的类的情况下对这些功能进行扩展。

通过继承创建的类,我们称之为子类或派生类。 被继承的类称为基类,父类或超类。

继承的过程,就是从一般到特殊的过程。要实现继承,可以通过继承和组合来实现。

在某些OOP语言中,一个子类可以继承多个基类,但是一般情况下,一个子类只能有一个基类,要实现多重继承,用多级继承实现更好。

继承概念的实现方式主要有2类:实现继承、接口继承。

实现继承是指使用基类的属性和方法而无需编写额外的代码;

接口继承是指仅使用属性和方法的名称,但是子类必须提供实现的能力(子类重构父类方法);

在考虑使用继承时,有一点需要主要的是,2个类之间应该是属于的关系。例如,Boss是一个人,Manager也是一个人,他们都可以继承Person类,但是Head类确不能继承Person类,因为Head并不是一个人。

抽象类仅定义将由子类创建的一般属性和方法。

OO开发范式大致为:划分对象,抽象类,将类组织成为层次化结构(继承和合成),用类与实例进行设计和实现几个阶段。

继承示例代码

 1 class SchoolMember(object):
 2     member = 0 
 3 
 4     def __init__(self, name, age):
 5         self.name = name
 6         self.age = age
 7 
 8     def tell(self):
 9         pass
10 
11     def enroll(self):
12         SchoolMember.member +=1
13         print("\033[31;1mnew member [%s] is enrolled, now there are [%s] members.\033[0m" % (self.name, SchoolMember.member))
14 
15     def __del__(self):
16         """析构方法"""
17         print("\033[32;1mmember [%s] is deleted !\033[0m" % self.name)
18 
19 
20 class Teacher(SchoolMember):
21     def __init__(self, name, age, course, salary):
22         super(Teacher, self).__init__(name, age)
23         self.course = course
24         self.salary = salary
25         self.enroll()
26 
27     def teaching(self):
28         msg = "Hi, my name is [%s] and i will teach your [%s] course." % (self.name,self.course)
29         print(msg)
30 
31     def tell(self):
32         '''自我介绍方法'''
33         msg = '''Hi, my name is [%s], a [%s] teacher !''' % (self.name, self.course)
34         print(msg)
35 
36 
37 class Student(SchoolMember):
38     def __init__(self, name, age, grade, id):
39         super(Student, self).__init__(name, age)
40         self.grade = grade
41         self.id = id
42         self.enroll()
43 
44     def tell(self):
45         msg = "Hi , I am [%s],I am studying [%s]." % (self.name, self.grade)
46         print(msg)
47 
48 if __name__ == "__main__":
49     t1 = Teacher("Dandy", 22, "Python", 20000)
50     t2 = Teacher("Rocky", 29, "C#", 19000)
51 
52     s1 = Student("Sam", 23, "Python", 1001)
53     s2 = Student("Tom", 24, "Python", 1002)
54 
55     t1.teaching()
56     t2.teaching()
57     t1.tell()
View Code

 

多态

 多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单说,就是允许将子类类型的指针赋值给父类类型的指针。

那么,多态的作用是什么?我们知道封装可以隐藏代码实现的细节,使代码模块化;集成可以扩展已存在的代码模块;他们的目的都是为了--代码重用。而多态呢,是为了实现另一个目的--接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用其中任何一个类的实例的某一属性时的正确调用。

Pyhon 很多语法都是支持多态的,比如 len(),sorted(), 你给len传字符串就返回字符串的长度,传列表就返回列表长度。

多态代码示例

 1 class Animal(object):
 2     def __init__(self, name):
 3         self.name = name
 4         
 5     def talk(self):
 6         raise NotImplementedError("Subclass must implement abstract method")
 7 
 8 
 9 class Cat(Animal):
10     def talk(self):
11         print('%s: 喵喵喵!' % self.name)
12 
13 
14 class Dog(Animal):
15     def talk(self):
16         print('%s: 汪!汪!汪!' % self.name)
17 
18 
19 def func(obj):  # 一个接口,多种形态
20     obj.talk()
21 
22 
23 c1 = Cat('小晴')
24 d1 = Dog('李磊')
25 func(c1)
26 func(d1)
View Code

 

posted @ 2017-08-28 23:00  dandyzhang  阅读(273)  评论(0编辑  收藏  举报