面向对象程序设计(Object-oriented programming,OOP)
一.类与对象
类(class): 对一类具有相同 属性的对象的 抽象。类的定义包含了数据的形式以及 对数据的操作
对象(object):类的实例,每个对象都是其类中的一个 具体实体。
# 定义一个 Dog 类
class Dog:
def __init__(self, name):
self.name = name
def talk(self):
print("[{}]:Wang Wang Wang.".format(self.name))
# 创建对象buck
buck = Dog("buck")
# 调用对象中的talk()方法
buck.talk()
对上述定义类的代码中的某些关键字进行简单阐述:
定义类: 通过class这个关键字定义一个类,类名叫Dog。
属性: name变量就是Dog这个类封装的一个属性。
方法: talk()函数就是Dog这个类中的方法。
self: 注意这是个特殊参数,当类实例化之后self即是对象本身。
创建对象:在类名之后添加括号,传入需要的参数,就创建了一个对象。
访问对象中的属性或方法:通过 对象.属性 或者 对象.方法 的形式。
__init__():类的构造函数,创建对象会调用该方法,后面会详细解释。
简单了解了类与对象的概念、定义类,创建对象、属性和方法等之后,我们接着阐述面向对象的三大特性:封装、继承、多态。
二.三大特性
1.封装性
类的成员:属性和方法
封装(Encapsulation)是通过限制只有特定类的对象可以访问这一特定类的成员,其中有两点需要注意:
一是类抽象出一些成员封装在某个地方;
二是通过某种形式可以访问这些成员。
下面的代码定义了一个Person类, 封装了人的name, age,还有一个自我介绍的hello() 方法。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def hello(self):
print("Hello, my name is %s, i'm %s years old" % (self.name, self.age))
# 封装到某处
person1 = Person("Lei Li", 20)
person2 = Person("Meimei Han",18)
# 访问属性
print(person1.name) # Lei Li
print(person2.name) # Meimei Han
# 访问方法
person1.hello() # Hello, my name is Lei Li, i'm 20 years old
person2.hello() # Hello, my name is Meimei Han, i'm 18 years old
1.1 封装到某处
当执行 person1 = Person("Lei Li", 20)时,self 等于 person1,并把 "Lei Li" 和 0 分别封装到了self/person1 的name和age中;
当执行person2 = Person("Meimei Han", 18)时,self 等于 person2,并把 "Meimei Han" 和 18分别封装到了self/person1的name和age中。
1.2 访问封装的内容
(1)访问属性
通过 对象.属性 的方式访问,如person1.name就是访问之前封装的person1这个对象的name属性,即"Lei Li"。
(2)访问方法
通过 对象.方法 的方式访问,如person1.hello() 访问到了hello()方法,hello中调用了self.name 和self.age,实际上此处self=person1,通过self间接访问了属性。
2 继承性
2.1 单继承
继承性(Inheritance)是指,在某种情况下,一个类会有“子类”。子类比原本的类(称为父类)要更加具体化。
例如上例 “人(Person)”这个类,它可能会有“男人类(Man)”、“女人类(Woman)”这两个子类。
子类会继承父类的属性和行为,并且也可包含它们自己的。如女人类(Woman),会继承人(Person)的“姓名name”、“年龄age”以及“自我介绍hello()”等成员,也有自己独有的成员“生孩子birth_children()”。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def hello(self):
print("Hello, my name is %s, i'm %s years old" % (self.name, self.age))
class Man(Person):
def __init__(self, name, age):
# 继承的第一种写法
Person.__init__(self, name, age)
class Woman(Person):
def __init__(self, name, age):
# 继承的第二种写法
super(Woman, self).__init__(name, age)
def birth_children(self, p):
print("[{}] birthed [{}]".format(self.name, p))
man1 = Man("YouYuan Liu", 25)
woman1 = Woman("Jeo Chen", 38)
print(man1.name) # YouYuan Liu
man1.hello() # Hello, my name is YouYuan Liu, i'm 25 years old
print(woman1.age) # 38
woman1.birth_children("little Jeo") # [Jeo Chen] birthed [little Jeo]
注意上述两种继承的写法。子类既继承了父类的所有成员,又有自己的独有成员:
2.2 多继承
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。
class A:
def run(self):
print("run A")
class B(A):
def run(self):
super(B, self).run()
print("run B")
class C(A):
def run(self):
super(C, self).run()
print("run C")
class D(B, C):
def run(self):
super(D, self).run()
print("run D")
class E(B, C):
pass
a = A()
b = B()
c = C()
d = D()
e = E()
b.run() # run A
# run B
d.run()
# run A
# run C
# run B
# run D
运行:
b.run() # run A # run B
这里不难理解,因为在class B 的run()方法中,显式地调用了其父类(class A)的run()方法,所以,会先执行类A的run()方法,然后再执行类B的run()方法。
那我们接着运行下面的代码:
d.run() # run A # run C # run B # run D
这就有点难以理解了。按照我们“正常的思维”理解:
调用D中的run()方法,不是应该找D的父类B的run()方法,然后B中的run()调用其父类A中的方法run()了吗,
那么顺序应该是run A-->run B -->run D啊,这里第二个位置怎么多了个run C 呢?
这是为什么?为什么?Why? 这是因为Python3中的多继承是按照"广度优先"(Breadth-First Search)顺序继承的。
什么是广度优先呢?我们将上面的A/B/C/D类继承关系画成一颗树:
广度优先,就意味着继承顺序变成了D ---> B ---> C ---> A。
展开而言,当调用D中的run(),由于D中run()主动调用父级run()即B中run();
B中run()主动调用父级run()即C中run();
C中run()主动调用父级run()即A中run()。所以自然打印顺序就成了:
run A ---> run C ---> run B ---> run D
好了,那么思考一下下面的代码会打印什么呢,你能解释打印结果吗?
e.run()
(2)Python2中的多继承
在Python2中,经典类和新式类的继承顺序不同,经典类是按照深度优先顺序,而新式类是按照广度优先的。 Python 2.x中默认都是经典类,只有显式继承了object才是新式类。
# python2.x中: # 经典类 默认 class A: pass # 新式类 class A(object): pass
Python 3.x中默认都是新式类,不必显式的继承object。
# python3.x中: # 以下均是新式类 class A: pass class A(object): pass
3.多态性
多态(Polymorphism)是指由继承而产生的相关的不同的类,其对象对同一消息会做出不同的响应。
例如,狗和猫都有“叫()”这一方法,但是调用狗的“叫()”,狗会汪汪叫;调用猫的“叫()”,鸡则会喵喵叫。
在Python中崇尚鸭子类型:
有种“一个接口,多种实现”的感觉。
class Dog(object):
def talk(self):
print("wang wang!")
class Cat(object):
def talk(self):
print("miao miao!")
def animal_talk(obj):
obj.talk()
dog= Dog()
cat= Cat()
animal_talk(dog)
animal_talk(cat)
三.为什么要用 面向对象
3.1 不用面向对象
def talk(name, age, city):
print("I am {}, {} years old, from {}".format(name, age, city))
talk("Liu yi fei", 30, "BJ")
talk("Liu you yuan", 25, "HB")
talk("Jeo Chen", 38, "TW")
3.2 面向对象
class Person(object):
def __init__(self, name, age, city):
self.name = name
self.age = age
self.city = city
def talk(self):
print("I am {}, {} years old, from {}".format(self.name, self.age, self.city))
obj1 = Person("Liu yi fei", 30, "BJ")
obj1.talk()
obj2 = Person("Liu you yuan", 25, "HB")
obj2.talk()
obj3 = Person("Jeo Chen", 38, "TW")
obj3.talk()
这样一看,不用面向对象似乎代码更短、更简洁? 那为啥还要用面向对象?
但是, 我们想想啊,假设还有500个人,另外还有跑步run(),吃eat(),睡sleep()等10种方法。
不用面向对象的话,1个人的10中方法就会重复写10次:方法("name", "age", "city")了;而面向对象只需要写一次。
当然这只是一个简单例子罢了,面向对象的优势远不止这些,在此不再多说。