做一个现实的理想主义者

理解Python中的面向对象 - 【耍蛇】

2018.09.28

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

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

优点:极大的降低了程序的复杂度

缺点:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。

应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。


面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。对象是特征和技能的结合,其中特征和技能分别对应对象的数据属性和方法属性

优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。

缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。

应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

id、type和value的概念

在python当中一切皆对象,每产生一个对象会对应三个属性:id类型type数值

  • 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 == y)

运行结果:

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

代码示例:

>>> x = 300
>>> y = 300
>>> id(x)
4365559472
>>> id(y)
4365559504
>>> x == y
True

类和对象的概念

  1. 把一类事物的静态属性和动态可以执行的操作组合在一起所得到的这个概念就是类
  2. 类的一个个体就是对象,对象是具体的,实实在在的事物
  3. 对象是特征与技能的结合体,其中特征和技能分别对应对象的数据属性和方法属性
  4. 对象(实例)本身只有数据属性,但是python的class机制会将类的函数绑定在对象上,称为对象的方法,或者叫绑定方法,绑定方法唯一绑定一个对象,同一个类的方法绑定到不同的对象上,属于不同的方法,内存地址都会不一样。

在类内部定义的属性属于类本身的,操作系统只分配一块内存空间,大家公用这一块内存空间。

  1. 创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性。而类中有两种属性:数据属性和函数属性,其中类的数据属性是共享给所有对象的,而类的函数属性是绑定到所有对象的。
  2. 创建一个对象(实例)就会创建一个对象(实例)的名称空间,存放对象(实例)的名字,称为对象(实例)的属性
  3. 在Obj.name会从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类……最后都找不到就抛出异常
  4. 类的相关方法:
类的相关方法(定义一个类,也会产生自己的名称空间)

类名.__name__    #类的名字(字符串)
类名.__doc__     #类的文档字符串
类名.__base__    #类的第一个父类
类名.__bases__   #类所有父类构成的元组
类名.__dict__    #类的字典属性、名称空间
类名.__module__  #类定义所在的模块
类名.__class__   #实例对象的类
  1. 其余概念:
- 创建出类会产生名称空间,实例化对象也会产生名称空间
- 用户自己定义的一个类,实际上就是定义了一个类型,类型与类是统一的
- 用户先是从自己的命名空间找,如果找不到,再从类的命名空间找:

student1.langage = '11111'
print(student1.__dict__) #先是从自己的命名空间找
print(student.__dict__)  #然后再从类的命名空间中找

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

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

- 总结:

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

- 在类内部定义的函数虽然可以由类来调用,但是并不是为了给类用的,在类内部定义的函数的目的就是为了绑定到对象身上的。

- 在类的内容来说,__init__是类的函数属性,但是对于对象来说,就是绑定方法。
- 命名空间的问题:先从对象的命名空间找,随后在类的命名空间找,随后再从类的命名空间找。

print(student1.x)

- 在定义类的时候,可以想什么先写什么

示例程序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)

运行结果:

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

示例程序2:对象之间的交互

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

# 什么叫做抽象对象?含 @abc.abstractmethod标识符的就是?
# 原样抄下来也是重写?

# 定义盖伦类和瑞文类,并进行相互残血
# 对象之间的交互问题——面向对象之间相互交互

class Garen():
    """docstring for Garen"""
    camp = 'Demacia'
    def __init__(self, nickname, life_value = 200, aggre_value = 200):
        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):
        enemy.life_value = enemy.life_value - self.aggre_value


g = Garen('盖伦')
r = Riven('瑞文')

print('盖伦的生命值是%s'%g.life_value)
print('瑞文的生命值是%s'%g.life_value)

g.attack(r)
print('瑞文的生命值是%s'%r.life_value)

运行结果:

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

初始化构造函数 __init__ 的作用

Comments
Write a Comment