python入门基础总结笔记(5)——面向对象编程
学习采用的是廖雪峰老师的python教程,很精彩详细,收获很大,聊表感谢!原文链接:https://www.liaoxuefeng.com/wiki/1016959663602400
目录
- 类和实例
- 访问限制——加下划线变私有变量
- 继承和多态
- 获取对象信息——三个函数
- 实例属性和类属性
定义
面向对象编程(Object Oriented Programming),简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程 VS 面向对象?
假设我们要处理学生的成绩表,为了表示一个学生的成绩:
- 面向过程的程序可以用一个dict表示:
std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }
def print_score(std):
print('%s: %s' % (std['name'], std['score']))
- 面向对象的程序设计思想,应该把Student这种数据类型视为一个对象,这个对象拥有name和score这两个属性:
class Student(object): #通过class关键字定义“类”
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
#结果
Bart Simpson: 59
Lisa Simpson: 87
1.类和实例
- 类
在Python中,定义类是通过class关键字:
class Student(object):
pass
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object)
- 实例
通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
注意:特殊方法“init”前后分别有两个下划线!!!
__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:
>>> bart = Student('Bart Simpson', 59) #self不用传,bart即self
>>> bart.name
'Bart Simpson'
>>> bart.score
59
- 数据封装
从外部看Student类,创建实例需要给出name和score,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
2.访问限制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name #无法通过bart.__name 访问内部
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
已经无法从外部访问实例变量.__name和实例变量.__score了,这样外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name和get_score这样的方法:
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
如果又要允许外部代码修改score怎么办? 可以再给Student类增加set_score方法:
class Student(object):
...
def set_score(self, score): #可以对参数做检查,避免传入无效的参数
if 0 <= score <= 100:
self.__score = score ###
else:
raise ValueError('bad score')
练习
请把下面的Student对象的gender字段对外隐藏起来,用get_gender()和set_gender()代替,并检查参数有效性:
class Student(object):
def __init__(self, name, gender):
self.name_ = name
self.__gender = gender
def get_gender(self):
return self.__gender
def set_gender(self,gender):
if gender=='male' or gender=='female':
self.__gender=gender
else:
print('wrong gender!')
# 测试:
bart = Student('Bart', 'male')
if bart.get_gender() != 'male':
print('测试失败!')
else:
bart.set_gender('female')
if bart.get_gender() != 'female':
print('测试失败!')
else:
print('测试成功!')
bart.set_gender('memale')
运行结果:
测试成功!
wrong gender!
3.继承和多态
下面编写了一个名为Animal的class,有一个run()方法可以直接打印:
class Animal(object):
def run(self):
print('Animal is running...')
当我们需要编写Dog和Cat类时,就可以直接从Animal类继承:
class Dog(Animal): #Animal是Dog的父类,Dog是子类
pass
class Cat(Animal): #Cat是子类
pass
dog = Dog() #Dog和Cat作为Animal的子类,就自动拥有了run()功能
dog.run()
cat = Cat()
cat.run()
#结果
Animal is running...
Animal is running...
为了显示 Dog is running… 和 Cat is running…,因此,对Dog和Cat类改进如下:
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
#运行结果
Dog is running...
Cat is running...
当子类和父类都存在相同的run()方法时,子类的run()覆盖了父类的run(),这也是继承的一个好处:多态。
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:
def run_twice(animal): #animal小写,接收Animal类型变量
animal.run()
animal.run()
>>> run_twice(Animal()) #传入Animal实例
Animal is running...
Animal is running...
>>> run_twice(Dog()) #传入Dog实例
Dog is running...
Dog is running...
对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定。
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
4.获取对象信息
4.1 type()函数
基本对象类型都可以用type()函数判断:
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
比较两个变量类型是否相同:
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
4.2 isinstance()函数
对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。
如果继承关系是:object -> Animal -> Dog -> Husky
isinstance()就可以告诉我们,一个对象是否是某种类型。
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky) #h变量指向的就是Husky对象
True
>>> isinstance(h, Dog) #h也还是Dog类型
True
>>> isinstance(h, Animal) #h也还是Animal类型
True
>>> isinstance(d, Husky) #但d(Dog)不属于Husky类型
False
能用type()判断的基本类型也可以用isinstance()判断:
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
4.3 dir() 函数
使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
其他:
>>> len('ABC') #len求对象长度
3
>>> 'ABC'.__len__()
3
>>> 'ABC'.lower() #小写化字符串
'abc'
5.实例属性和类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性。
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归Student类所有:
>>> class Student(object):
... name = 'Student' ###定义类属性name
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
在编写程序的时候,千万不要对实例属性和类属性使用相同的名字!
因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
练习
为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
class Student(object):
count = 0
def __init__(self, name):
self.name = name
Student.count += 1
# 测试:
if Student.count != 0:
print('测试失败!')
else:
bart = Student('Bart')
if Student.count != 1:
print('测试失败!')
else:
lisa = Student('Bart')
if Student.count != 2:
print('测试失败!')
else:
print('Students:', Student.count)
print('测试通过!')
#结果
Students: 2
测试通过!
小结:
1.实例属性属于各个实例所有,互不干扰;
2.类属性属于类所有,所有实例共享一个属性;
3.不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。