文章目录
面向对象编程
面向对象的设计思想是抽象出Class,根据Class创建Instance。
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
类和实例
面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
定义类
通过class关键字:
class Student(object):
pass
- class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
创建实例
- 定义好Student类后,就可以根据Student类创建出Student的实例,创建实例是通过**类名+()**来实现:
bart=Student()
- 可以自由地给一个实例变量绑定属性,如给实例bart绑定一个name属性:
bart.name='Bart Simpson'
- 类可以起到模板的作用,so在创建实例的时候可以强制填写一些必须绑定的属性,如:通过定义__init__的方法,在创建实例时把name和score属性绑定上去:
class Student(object):
def __int__(self,name,score):
self.name=name
self.score=score
__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身,这样在创建实例的时候就必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:
bart=Student('Bart Simpson',59)
- 类中定义的函数的第一个参数永远是实例变量self,并且,调用时,不用传递该参数。
数据封装
使用类的方法:
# define classify
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))
# using
bart=Student('Bart Simpson',59)
bart.print_score()
- 定义时:除了第一个参数是self外,其他和普通参数一样
- 调用时:只要在实例变量上直接调用,除了self不用传递,其他参数正常传入
封装可以给类增加新的方法
如get_grade
class Student(object):
...
def get_grade(self):
if self.score>=99:
return 'A'
elif self.score>=60:
return 'B'
else:
return 'C'
小结
- 类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
- 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
- 在实例上调用方法,就直接操作了对象内部的数据,但无需知道方法内部的实现细节;
- Python允许对实例变量绑定任何数据—对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
# 变量1
bart = Student('Bart Simpson', 59)
# 变量2
bart.age = 8 -->此时bart的age为8
访问限制
- 从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性
1. 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
2. 实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
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))
此时无法从外部访问实例变量.__name和实例变量.__score了。
3. 外部代码要获取内部私有变量
如果外部代码要获取name和score,可以给Student类增加get_name和get_score这样的方法:
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
- 此时,只要调用bart.get_name()就可以直接获取bart的name
4. 允许外部代码修改score
给类增加set_score方法:
class Student(object):
...
def set_score(self,score):
self.__score=score
这种方法可以对参数做检查,避免传入无效参数,对上面代码修改如下:
class Student(object):
...
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
- 注:变量名类似__xxx__的是特殊变量,可以直接访问,而不是私有变量,因此不能用__name__这样的变量名
- 但下划线变量名如_name是实例变量名,外部可以访问,但约定俗成把这类变量看作私有变量
- 双下划线开头的私有变量可以通过‘_类名__变量名’来进行访问,但一般不要这么干
- 不要通过
bart.__name = 'New Name'
这样的方式修改变量
exercise
请把下面的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 in {'female','male'}:
self.__gender=gender
else:
raise ValueError('bad value')
bart = Student('Bart', 'male')
if bart.get_gender() != 'male':
print('测试失败!')
else:
bart.set_gender('female')
if bart.get_gender() != 'female':
print('测试失败!')
else:
print('测试成功!')
继承和多态
继承
- 父类:
class Animal(object):
def run(self):
print('Animal is running')
class名为Animal,打印使用run()的方法
- 子类,Dog和Cat类,直接从Animal类继承
class Dog(Animal):
def run(self):
print('Dog is running')
子类获得了父类的全部功能,对子类增加方法,可以修改其run()函数,如:
class Dog(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。
多态
定义一个class的时候,实际上定义了一种数据类型,如:
a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型
判断某个变量是否是某个类型可以用isinstance()判断:
>>> isinstance(a, list)
True
>>> isinstance(c, Dog)
True
>>> isinstance(c, Animal)
True
其中,c不仅是Dog,还是Animal,因为Dog是从Animal继承下来的
即:是子类的一定是父类,但是是父类的不一定是子类
- 任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
e.g:
# 接受Animal类型的变量的函数
def run_twice(animal):
animal.run()
animal.run()
# 传入Animal、Dog等实例
>>> run_twice(Animal())
Animal is running...
Animal is running...
>>> run_twice(Dog())
Dog is running...
Dog is running...
- 多态的好处:当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思
- 调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的
- 继承还可以一级一级地继承下来,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:
静态语言 vs 动态语言
- 静态语言(如Java),若需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
- 动态语言(如Python),不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:
class Timer(object):
def run(self):
print('Start...')
小结
- 继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
- 动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
获取对象信息
使用type()
判断对象类型,使用type()函数
>>> type(123)
>>> type('str')
>>> type(None)
若一个变量指向函数或类,也可以用type()判断:
>>> type(abs)
判断两个数据类型是否一样:
>>> type('abc')==type(123)
判断一个对象是否是函数:
import types
def fn():
pass
>>> type(fn)==types.FunctionType
True
type(abs)==types.BuiltinFuntionType
True
type(lambda x:x)==types.LambdaType
True
type((x for x in range(10)))==types.GeneratorType
True
使用isinstance
判断class的类型,可以使用isinstance()函数:
设继承关系为:object -> Animal -> Dog -> Husky
# 1.创建三种类型的对象
a=Animal()
b=Dog()
h=Husky()
# 2.判断
>>>isinstance(h,Husky)
True
>>> isinstance(h, Dog)
True # 因为Husky是Dog继承下来的,所以h还是Dog类型
- isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
- 能用type()判断的基本类型也可以用isinstance()判断:
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
- 可以判断一个变量是否是某些类型中的一种:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
- 总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
使用dir()
此函数可以获得一个对象的所有属性和方法,返回一个包含字符串的list,如:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
__len__方法返回长度
len('ABC') <==>'ABC'.__len__()
- 自己写的类,也想用len(myObj)的话,可以自己写一个__len__()方法:
class MyDog(object):
def __len__(self):
return 100
# using
>>>dog = MyDog()
>>>len(dog)
100
其余属性或方法
XXX.__len__()
‘ABC’.lower() #返回小写的字符串
- 直接操作一个对象的状态,使用getattr()、setattr()、hasattr()
其操作均为,如:getattr(变量名,属性名)
可以传入一个default参数,如果属性不存在,就返回默认值:
如:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
操作示例:
class Myobject(object):
def __init__(self):
self.x=9
def power(self):
return self.x * self.x
# using
>>>obj = Myobject()
# tesing property
>>>hasatter(obj,'x') #有属性'x'吗
True
>>>setattr(obj,'y',19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>>getattr(obj,'y') # 获取属性'y'
19
# getting object method
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
小结
通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。
def readImage(fp):
if hasattr(fp,'read'):
return readData(fp)
return None
如果想从文件流fp中读取图像,首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。
实例属性和类属性
python是动态语言,根据类创建的实例可以任意绑定属性。
1. 给实例绑定属性的方法:通过实例变量,或通过self变量
如:
class Student(object):
def __init__(self, name):
self.name = name
# using
s=Student('Bob')
s.score=90
2. 给类绑定属性的方法:可以直接在class中定义属性,这种属性是类属性,归这个类所有,但类的所有实例都可以访问到
同时,实例属性的优先级比类属性高,因此如果给实例绑定属性,则它会屏蔽掉类的这个属性,但是类的这个属性也并未消失,仍可以访问类的这个属性
删除实例的属性,可以用 del 实例.对应属性
如:
# 定义类属性
class Student(object):
name = 'Student' # name是Student的类属性
# testing
>>>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仍然可以访问
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
- 写程序时,不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
exercise
为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
class Student(object):
count=0
def __init__(self,name): #因为这里有name这个参数,所以创建实例的时候必须在括号里实例化
self.name=name
Student.count+=1
# 创建实例+testing code
# aaa=Student('a')
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
测试通过!
小结
- 实例属性属于各个实例所有,故不干扰
- 类属性属于类所有,所有实例共享一个属性
- 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误