九、面向对象

面向过程与面向对象的对比

面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了程序的复杂度
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。对象是特征和技能的结合,其中特征和技能分别对应对象的数据属性和方法属性。
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

id、type和value的概念

在python当中一切皆对象,每产生一个对象会对应三个属性:id、类型type和数值
id可以理解为在内存当中的位置(其实不是,id实际指向的是对象的地址) is是身份运算符,其中id对应的就是身份。
id相同,数值肯定相同;id不相同,数值一定不相同吗?不是。

代码示例:

#!/usr/bin/python
# -*- coding:utf-8 -*-

x = 10
print(id(x))
print(type(x))
print(x)

y = 10
print(id(y))
print(type(y))
print(y)

#判断x和y的内存地址是否相同
print(x is y)
#判断x和y的数值是否相同
print(x == 7)

运行结果:

1547159920
<class 'int'>
10
1547159920
<class 'int'>
10
True
False

Process finished with exit code 0

代码示例:(不是在PyCharm当中操作的)

>>> x = 300
>>> y = 300(
>>> id(x)
6368784
>>> id(y)
7343856
>>> x == y

类和对象的概念

  1. 把一类事物的静态属性和动态可以执行的操作组合在一起所得到的这个概念就是类
  2. 类的一个个体就是对象,对象是具体的,实实在在的事物
  3. 对象是特征与技能的结合体,其中特征和技能分别对应对象的数据属性和方法属性
  4. 对象(实例)本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法,绑定方法唯一绑定一个对象,同一个类的方法绑定到不同的对象上,属于不同的方法,内存地址都不会一样
    在类内部定义的属性属于类本身的,由操作系统只分配一块内存空间,大家公用这一块内存空间
  5. 创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性:而类中有两种属性:数据属性和函数属性,其中类的数据属性是共享给所有对象的,而类的函数属性是绑定到所有对象的。
  6. 创建一个对象(实例)就会创建一个对象(实例)的名称空间,存放对象(实例)的名字,称为对象(实例)的属性
  7. 在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类…最后都找不到就抛出异常。
  8. 类的相关方法:
类的相关方法(定义一个类,也会产生自己的名称空间)
类名.__name__   # 类的名字(字符串)
类名.__doc__    # 类的文档字符串
类名.__base__   # 类的第一个父类(在讲继承时会讲)
类名.__bases__  # 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__   # 类的字典属性、名称空间
类名.__module__ # 类定义所在的模块
类名.__class__  # 实例对应的类(仅新式类中)

其余概念

1. 创建出类会产生名称空间,实例化对象也会产生名称空间。

2. 用户自己定义的一个类,实际上就是定义了一个类型,类型与类是统一的。

3. 用户先是从自己的命名空间找,如果找不大,在从类的命名空间找。

  student1.langage = "1111"
    print(student1.__dict__)  ===>先是从自己的命名空间找
    print(Student.__dict__)   ===>然后在从类的命名空间找

4. 通过类来访问,访问的是函数,通过对象来访问,访问的是方法,在类内部定义的方式实际上是绑定到对象的身上来用的。

<function Student.fun at 0x000000000267DAE8>
<bound method Student.fun of <__main__.Student object at 0x0000000002684128>>

<function Student.fun at 0x00000000025CDAE8>
<bound method Student.fun of <__main__.Student object at 0x00000000025D4160>>
<bound method Student.fun of <__main__.Student object at 0x00000000025D4198>>

5. 总结:
类的数据属性是大家共有的,而且大家的内部地址是一样的,用的就是一个
       类的函数属性是绑定到大家身上的,内部地址不一样,绑定方法指的是绑定到对象身上。
       绑定方法:绑定到谁的身上,就是给谁用的,谁来调用就会自动把自己当做第一个参数传入。
       **定义在类内部的变量,是所有对象共有的,id全一样,
       **定义在类内部的函数,是绑定到所有对象的,是给对象来用的,obj.fun()会把obj本身当做
       一个参数来传递。

6. 在类内部定义的函数虽然可以由类来调用,但是并不是为了给类用的,在类内部定义的函数的目的就是为了绑定到对象身上的。
7. 在类的内部来说,__init__是类的函数属性,但是对于对象来说,就是绑定方法。
8. 命名空间的问题:先从对象的命名空间找,随后在从类的命名空间找,随后在从父类的命名空间找。

print(student1.x)


9. 在定义类的时候,可以想什么先写什么。
  • 示例程序1

编写一个学生类,产生一堆学生对象,要求有一个计数器(属性),统计总共实例了多少个对象。

#!/usr/bin/python
# -*- coding:utf-8 -*-


class Student():
    #在类内部定义的属性属于类本身的,由操作系统只分配一块内存空间,大家公用这一块内存空间。
    count = 0
    def __init__(self,name,age):
        self.name = name
        self.age = age
        Student.count +=1


if __name__ == '__main__':
    student1 = Student("lidong",25)
    print(student1.__dict__)
    student2 = Student("wangwu",28)
    print(student2.__dict__)
    print(Student.count)

运行结果:

{'age': 25, 'name': 'lidong'}
{'age': 28, 'name': 'wangwu'}
2

Process finished with exit code 0
  • 示例程序2:

(对象之间的交互,重点)

#!/usr/bin/python
# -*- coding:utf-8 -*-

"""
1、什么叫做抽象方法?含有@abc.abstractmethod标识符的就是?
2、原样抄下来也是重写?
"""

#定义盖伦类和瑞文类,并进行互相残血
#对象之间的交互问题(面向对象之间互相交互)
class Garen:
    camp = "Demacia"
    #定义一个对象的时候,指定了这个对象的生命值和杀伤力
    def __init__(self,nickname,life_value=200,aggre_value=100):
        self.nickname = nickname
        self.life_value = life_value
        self.aggre_value = aggre_value
    def attack(self,enemy):
        enemy.life_value = enemy.life_value - self.aggre_value

class Riven:
    camp = "Demacia"
    # 定义一个对象的时候,指定了这个对象的生命值和杀伤力
    def __init__(self, nickname, life_value=100, aggre_value=200):
        self.nickname = nickname
        self.life_value = life_value
        self.aggre_value = aggre_value
    def attack(self, enemy):
        #python为弱类型语言
        enemy.life_value = enemy.life_value - self.aggre_value

g = Garen("盖伦")
r = Riven("瑞文")
print("盖伦的生命值是%s"%g.life_value)
print("瑞文的生命值是%s"%r.life_value)

g.attack(r)
print("瑞文的生命值是%s"%r.life_value)
运行结果:

盖伦的生命值是200
瑞文的生命值是100
瑞文的生命值是0

Process finished with exit code 0

初始化构造函数__init_的作用

所谓初始化构造函数就是在构造对象的同时被对象自动调用,完成对事物的初始化,一个类只要生成一个类对象,它一定会调用初始化构造函数. 特点:
1>一个类中只能有一个初始化构造函数
2>不能有返回值
3>可以用它来为每个实例定制自己的特征

  • 示例程序:
#!/usr/bin/python
# -*- coding:utf-8 -*-


class Student():
    def __init__(self,name,age):
        self.name = name
        self.age = age
        print(self.name,self.age)


if __name__ == '__main__':
    #在构造对象的时候会自动调用初始化构造函数
    student = Student("Alex",100)
运行结果:

Alex 100

Process finished with exit code 0

self关键字的用法

为了辨别此时此刻正在处理哪个对象,self指针变量指向当前时刻正在处理的对象,即构造出来的对象
在构造方法中self代表的是:self指针变量指向当前时刻正在创建的对象 构造函数中self.name = name
的含义:将局部变量name的数值发送给当前时刻正在创建的对象中的name成员

  • 示例程序:
#!/usr/bin/python
# -*- coding:utf-8 -*-


class Student():
    def __init__(self):
        print("当前对象的地址是:%s"%self)

if __name__ == '__main__':
    student1 = Student()
    student2 = Student()
运行结果:

#!/usr/bin/python
# -*- coding:utf-8 -*-


class Student():
    def __init__(self):
        print("当前对象的地址是:%s"%self)

if __name__ == '__main__':
    student1 = Student()
    print(student1)
    student2 = Student()
    print(student2)
运行结果:

当前对象的地址是:<__main__.Student object at 0x00000000025ACF28>
<__main__.Student object at 0x00000000025ACF28>
当前对象的地址是:<__main__.Student object at 0x00000000025D4048>
<__main__.Student object at 0x00000000025D4048>

Process finished with exit code 0

在上面的程序中,student1、student2、self实际上都是指针变量,存放的是地址,指定当前时刻正在调用的那个对象。

数据封装

数据封装是面向对象编程的一个重要特点。任何程序,只要封装好,对外只提供接口,使用方便且安全。
实例本身拥有数据,那么访问数据可以直接通过在类的内部定义访问数据的函数,而不用从外部的函数访问,这样就实现了数据的封装。封装数据的函数和类本身是关联起来的,我们称之为类的方法。

  • 学生类中添加打印学生信息的函数。
class Student(object):
    def __init__(self, no, name, score):
        self.no = no
        self.name = name
        self.score = score

    def pr_ifo(self):
        print("{0}的学号是{1},分数是{2}".format(self.name, self.no, self.score))


student = Student(1101, "xiaohh", 88)    #实例化并传入参数
student.pr_ifo()    #通过实例变量调用类的内部函数

继承和多态

定义一个类的时候,可以从某个现有的类继承,新的类称为子类,被继承的类称为基类、父类或超类。
继承使得在设计相似的东西时更加方便。多态使得我们在使用类似东西时不用考虑它们细微的区别。 继承可以使得子类获得父类的全部功能。

  • 代码示例:

class Animal(object):

    def run(self):
        print("Animal run。")

class Bird(Animal):
    pass

class Cat(Animal):
    pass

animal = Animal()
print(animal.run())    #输出结果:Animal run。
bird = Bird()
print(bird.run())    #输出结果:Animal run。
cat = Cat()
print(cat.run())    #输出结果:Animal run。

对于Bird来说,Animal是它的父类,它是Animal的子类。Cat也类似。
此处只有父类Animal实现了run()方法,但是子类BirdCat可以使用run()方法。
对上述代码修改:

class Animal(object):

    def run(self):
        print("Animal run。")

class Bird(Animal):
    def run(self):
        print("Bird run。")

class Cat(Animal):
    def run(self):
        print("Cat run。")

animal = Animal()
print(animal.run())    #输出结果:Animal run。
bird = Bird()
print(bird.run())    #输出结果:Bird run。
cat = Cat()
print(cat.run())    #输出结果:Cat run。

可见,当子类和父类均有相同的方法时,优先调用子类的方法。
也就是说,当父类的方法无法满足子类的需求,子类就需要重构父类的方法。如果子类中有重构的方法,通过实例变量调用的时候优先调用子类的方法,若子类中没有,再去父类中寻找。

对于前面定义的Animal类执行命令:


print(isinstance(bird, Bird))    #输出结果:True
print(isinstance(bird, Animal))    #输出结果:True

上面结果说明Bird的实例bird的数据类型是Bird,同时也是Animal。
也就是说,在继承关系中如果一个实例的数据类型是某个子类,那么它的数据类型也可以被看做是父类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值