面向对象(Object Oriented Programming,简称OOP)中有两个重要的概念:类(Class)和实例(Instance),类是抽象的模板,而实例是根据类创建出来的具体“对象”。
一、创建类、对象
1、创建类
class Cat():
color = 'white'
"""猫的基本特性描述"""
def __init__(self, name, age):
"""初始化参数"""
self.name = name
self.age = age
self.sex = 'male'
def catch(self):
"""猫捉老鼠"""
print(self.name + ' is catching the mouse!')
上边定义了一个Cat类,首先看__init__()方法(init前后各两个下划线),它是一个特殊方法,类似java中的构造函数,包含三个形参: self、name和age。其中形参self必不可少,还必须位于其他形参的前面,表示创建的实例本身,在__init__方法内部,就是把属性name、age绑定到实例上的操作,则name、age就是实例属性,可以通过实例访问,其中sex是一个有默认值的实例属性。注意color属性绑定在类本身,属于类属性。
Cat类中有一个catch()方法,和普通函数类似,只多了一个必须的self参数,和init方法类似,通过类的实例调用时也不用传递self参数。
2、创建对象
创建Cat实例时, Python将调用Cat类的方法__init__(),参数self会自动传递,只需提供name、age两个参数:
>>>cat = Cat('tom', 2)
# 访问name属性
>>>cat.name
'tom'
# 调用catch()方法
>>>cat.catch()
tom is catching the mouse!
二、访问属性
前边通过cat实例可以访问到name属性,如果不想属性直接被访问,可以这样修改Cat类:
class Cat():
def __init__(self, name, age):
self.__name = name
self.__age = age
省略了非关键代码,分别在name、age属性名前加了两个下划线__,这样属性就被私有化了,再试着访问一次:
>>>cat = Cat('tom', 2)
>>>cat.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
这样实例属性就不能被随意访问修改了,代码更加健壮。
之后要访问name属性,可以提供相应的发方法:
class Cat():
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_name(self):
return self.__name
>>>cat = Cat('tom', 2)
>>>cat.get_name()
如果要修改属性值,可以增加相应的set方法:
class Cat():
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_name(self):
return self.__name
def set_name(self, name):
return self.__name = name
>>>cat = Cat('tom', 2)
>>>cat.set_name('bob')
当然属性前如果没加双下划线,则可以直接修改属性值:
>>>cat = Cat('tom', 2)
>>>cat.name = 'bob'
前边提到了实例属性和类属性,在类属性非私有的条件下,可以直接通过实例访问类属性,如果存在同名的实例属性和类属性则实例属性将屏蔽掉类属性。
三、继承
先创建父类Animal:
class Animal():
def __init__(self, color):
self.color= color
def slogan(self):
print('Animals are friends of human!')
再创建Dog子类继承Animal:
class Dog(Animal):
def __init__(self, name, sex, color):
super().__init__(color)
self.name= name
self.sex = sex
def slogan(self):
print('Dogs are friends of human!')
def run(self):
print(self.name + ' is running!')
其中name、sex、run()分别是子类的属性和方法。同时重写了父类的slogan()方法。
定义子类时,必须在括号内指定父类的名称。super()是一个特殊函数,帮助Python将父类和子类关联起来,super().__init__(name, age)
会调用
Dog的父类的方法__init__(),让Dog的实例包含父类的所有属性。
通过继承,子类获得了父类的全部非私有功能:
>>>dog1 = Dog('kk', 'male', 'yellow')
>>>dog1.name
'kk'
>>>dog1.slogan()
Dogs are friends of human!
>>>dog1.run()
kk is running!
Python是支持多重继承的!
四、多态
以上边的例子为基础:
>>>a = Animal('white')
>>>d = Dog('kk', 'male', 'white')
>>>isinstance(a, Animal)
True
>>>isinstance(d, Dog)
True
>>>isinstance(d, Animal)
True
>>>isinstance(a, Dog)
False
用isinstance()方法可以判断一个实例的类型。
从上边的例子可以看出,d是Dog类型也是Animal类型,但是a不是Dog类型。所以,如果一个实例的类型是某个子类,那它的类型也可以被看做是父类,但反过来就不行。这就是多态的体现。
再看一个例子:
def show_slogan(animal):
animal.slogan()
>>>a = Animal('white')
>>>d = Dog('kk', 'male', 'white')
>>>show_slogan(a)
Animals are friends of human!
>>>show_slogan(d)
Dogs are friends of human!
什么意思呢?因为Dog是Animal的子类,都有run()方法,因此调用show_slogan()方法时只要传入Animal类或者子类的实例,就会自动调用实际类型的run()方法。因为多态的存在,新增一种Animal的子类时,只要确保run()方法编写正确,show_slogan()就可以正常执行。
五、鸭子类型
在静态语言中,例如Java,如果需要传入Animal类型,则传入的对象必须是Animal类型或者其子类,否则将无法调用run()方法。然而对于Python这样的动态语言来说,则不一定要传入Animal类型的对象,只需保证传入的对象有一个run()方法就可以了。
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。