面向对象编程将数据和操作数据的方法封装在对象中,组织代码和数据的方式更加接近人的思维。Python完全支持面向对象的基本功能,如继承、多态、封装等。
面向过程更加关注的是程序的逻辑流程,是一种“执行者”思维,适合用于编写小规模的程序。首先思考“如何按步骤实现?”,并将步骤对应成方法,一步一步完成。
面向过程适合简单、不需要协作的任务。
面向对象更加关注的是软件中对象之间的关系,是一种“设计者”思维,适合用于编写大规模程序。
面向对象可以帮助我们从宏观、整体上把握、分析整个系统,但具体到实现部分的围观操作(一个个方法),仍需要面向过程的思维。
面向对象的思考方式:遇到问题,先从问题中找“名词”,确定名词中哪些可以作为“类”,再根据问题需求确定类的属性和方法,确定类之间的关系。
通过类,定义数据类型的属性(数据)和方法(行为)。“类将行为和状态打包在一起”。
对象是类的具体实体,一般称为“类的实例”。同一个类创建出的对象,每个对象会共享这个类的行为(类中定义的方法),但会有自己的属性值(不共享状态(数据))。
类也称为“类对象”,类的实例也称为“实例对象”。
一个Python对象包含如下部分:
1.id(identity识别码)
2.type(对象类型)
3.value(对象的值)
(1)属性(attribute)
(2)方法(method)
创建对象,需要定义构造函数__init__()方法,用于执行“实例对象的初始化”工作,即对象创建后, 初始化当前对象的相关属性(无返回值)。
init()要点:
1.固定名称。
2.第一个参数固定,必须为self。self即为刚创建好的实例对象。(其实名字可以任意修改,但通常写为self)
3.构造函数通常用来初始化实例对象的实例属性。如下代码即为初始化实例属性name和score:
def __init__(self,name,score):
self.name=name #self.实例属性名=初始值
self.score=score
4.通过“类名(参数列表)”调用构造函数。调用后,将创建好的对象返回给相应变量。
s1=Student('小红',99)
5.init()方法:初始化创建好的对象,即:给实例属性赋值。当该类被实例化的时候就会执行该函数。
6.init()方法是可选的,如果不提供,Python 会给出默认的__init__方法。
7.new()方法:用于创建对象,但一般无需定义此方法。
即为从属于实例对象的属性,也成为“实例变量”。
实例属性要点:
1.在__init__()方法中通过如下代码定义:
self.实例属性名=初始值
2.在本类其他实例方法中,通过如下代码访问:
self.实例属性名
3.创建实例对象后,通过实例对象访问:
objt1=类名() #创建对象,调用__init__()初始化属性
objt01.实例属性名=值 #可以给已有属性赋值,也可新加属性
例:
class Student:
def __init__(self,name,score):
self.name=name
self.score=score
s1=Student('xiaohong',99) #通过类名()调用构造函数,在堆中参照“模具”(类对象)创建一个含有实例属性(和实例方法)的实例对象,s1为对象的引用
print(s1.name)
print(s1.score)
s1.age=18 #给创建好的实例对象增加属性
print(s1.age)
s2=Student('xiaofang',98)
print(s2.score)
print(s2.age) #引用为s2的对象中并不包含age属性
实例方法是从属于实例对象的方法。
定义格式:
def 方法名(self [,形参列表]): #第一个参数必须为self,self指当前实例对象
函数体
调用格式:
对象.方法名([实参列表]) #不需要,也不能给self传参!
例:
class Student:
def __init__(self,name,score):
self.name=name
self.score=score
def say_score(self):
print('{0}的分数为{1}'.format(self.name,self.score))
s1=Student('xiaohong',99)
print(s1.name,s1.score)
s1.say_score() #调用实例方法
Student.say_score(s1) #解释器的实际解译。调用实例方法的本质是,找到类对象内的方法(而非实例对象内的方法的引用)进行调用,以实例对象地址为实参。
1.dir(object): 获得实例对象的所有属性和方法
2.object.dict: 获得实例对象的属性字典
3.isinstance(对象,类型): 判断对象是否为指定类型
解释器执行class语句时,就会根据类对象type,创建类对象Student。
class Student:
def __init__(self,name,score):
self.name=name
self.score=score
def say_score(self):
print('{0}的分数为{1}'.format(self.name,self.score))
print(id(s1))
print(type(s1)) #s1是实例对象,其对象类型为Student
print(id(Student))
print(type(Student)) #Student类本身也是对象,其对象类型为type
Stu=Student
s2=Stu('xiaofang',99)
print(s2)
将类赋值给新变量Stu,也能实现相关调用,说明确实创建了类对象。
类属性是从属于类对象的属性,也称为“类变量”。
类属性可被所有实例对象共享。
类属性定义方式:
Class 类名:
类变量名=初始值
在类中或类外面,读写方式:
类名.类变量名
例:
class Student:
school='ustc' #类属性(类变量)
count=0 #类属性(类变量)
def __init__(self,name,score):
self.name=name #实例属性
self.score=score
Student.count+=1 #每调用一次__init__(),即每初始化一个实例对象,计数器就+1
def say_score(self): #实例方法
print('{0}的学校是{1}'.format(self.name,Student.school))
print('{0}的分数为{1}'.format(self.name,self.score))
s1=Student('xiaohong',99) #将Student类实例化,创建实例对象
s2=Student('xiaofang',98)
s3=Student('xiaoming',97)
s4=Student('xiaowang',95)
s2.say_score()
print('一共创建了{0}个实例对象'.format(Student.count))
类方法是从属于类对象的方法,需通过装饰器@classmethod定义,格式如下:
@classmethod
def 类方法名(cls [,形参列表]):
函数体
类方法要点:
1.第一个参数必须为cls,指的是类对象本身
2.调用类方法格式:
类名.类方法名(参数列表) #不需要也不能给cls传值
3.类方法中访问实例属性和实例方法,会报错。类方法操作类属性,实例方法操作实例属性
4.子类继承父类方法时,传入cls是子类对象而非父类对象
类中定义的与类对象无关的方法,与在模块中定义的普通函数没有区别,但需要通过类调用。
通过装饰器@staticmethod定义,格式如下:
@staticmethod
def 静态方法名([形参列表]):
函数体
调用静态方法格式:
调用类方法格式:
类名.静态方法名(参数列表)
与类方法一样,静态方法中访问实例属性和实例方法,会报错。
del()被称为析构方法,用于实现对象被销毁时所需的操作。如:释放对象占用的资源。
要点:
1.Python有自动垃圾回收机制,当对象中引用计数为0时,由垃圾回收器自动调用__del__()方法。
2.当使用del 语句删除对象时,会调用他本身的析构函数。另外当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数也会被调用一次,清空对象内存空间。
3.del()也是可选的,如果不提供,则Python 会在后台提供默认析构函数。
class P:
def __del__(self):
print('销毁对象{0}'.format(self))
p1=P()
p2=P()
print('p1:',p1)
print('p2:',p2)
del p2
print('结束')
可见,程序结束时垃圾处理器自动调用了__del__()方法,清除了p1指向的实例对象。
定义了__call__()方法的对象为可调用对象,可以像函数一样被调用。
class Salary:
def __call__(self,salary):
print('计算工资')
year_salary=salary*12
day_salary=salary//22.5
hour_salary=day_salary//8
return dict(year_salary=year_salary,month_salary=salary,day_salary=day_salary,hour_salary=hour_salary)
s=Salary()
print(s(50000)) #解释器实际解译的是下面一句
print(Salary.__call__(s,50000))
Python中,若在类体中定义了多个重名的方法,则只有最后一个方法有效。
建议:不要使用重名的方法。
Python是动态语言,既可动态地为类添加新的方法,也可动态地修改类中已有的方法。
例:
class Haha:
def haha(self):
print('哈哈')
def hehe(s): #为Haha类动态增加hehe()方法
print('{0}说:呵呵'.format(s))
def haha2(s): #动态修改Haha类的haha()方法
print('{0}说:哈哈'.format(s))
Haha.hehe=hehe
Haha.haha=haha2
h=Haha()
h.hehe()
h.haha()
Python对于类的成员没有严格的访问控制限制。关于私有属性和私有方法,有如下要点:
1.通常约定,两个下划线开头的属性为私有(private),其他为公共的(public)
2.类内部可访问私有属性(方法)
3.类外部不能直接访问私有属性(方法),但可通过**“_类名__私有属性(方法)名”**访问私有属性(方法)。
class Employee:
__location='北京' #将location设定为私有类属性
def __init__(self,name,age):
self.name=name
self.__age=age #将age设定为私有属性
def __work(self): #将work设定为私有方法
return '工作地点是{0},年龄是{1}'.format(self.__location,self.__age)
e=Employee('xiaohong',18)
print(e._Employee__age)
print(e._Employee__location)
print(e._Employee__work())
print(dir(e)) #获取实例对象的所有属性和方法。可见该对象中的age属性被存储为_Employee__age,work'方法被存储为_Employee__work
print(e.__age) #在类外无法用访问公共属性的方式访问私有属性
class Employee:
def __init__(self,name,salary):
self.__name=name
self.__salary=salary
def get_salary(self):
return self.__salary
def set_salary(self,salary):
if 1000
class Employee:
def __init__(self,name,salary):
self.__name=name
self.__salary=salary
@property
def get_salary(self):
return self.__salary
@get_salary.setter
def get_salary(self,salary):
if 1000
隐藏对象的属性和实现细节,只对外提供必要的调用方法。
通过“私有属性”、“私有方法”实现封装。
继承可以让子类拥有父类的特性,提高了代码重用性。可以在子类增加新功能,或改进已有算法。
子类可继承父类除构造方法外的所有成员。
继承的语法格式:
class 子类类名(父类1 [,父类2,...]): #可多重继承,但多重继承会导致类的整体层次变得很复杂,不建议使用
类体
默认父类为object类,其中定义了一些所有类都有的默认实现,如__new__()
定义子类时,必须在其构造函数中调用父类的构造函数。调用格式如下:
父类名.__init__(self [,参数列表])
例:
class Person:
def __init__(self,name,age):
self.name=name
self.__age=age #将age定义为私有属性
def say_age(self):
print('年龄是{0}'.format(self.__age))
class Student(Person): #继承Person类
def __init__(self,name,age,score):
Person.__init__(self,name,age) #在Student类的构造函数调用Person类的构造函数。必须显式地调用,否则解释器不会自动调用。
self.score=score
stu01=Student('小芳',19,95)
print(stu01.name)
print(stu01._Person__age) #在类外(和子类中)调用父类的私有属性,都需要特殊方法
print(stu01.score)
class Person:
def __init__(self,name,age):
self.name=name
self.__age=age
def say_age(self):
print('年龄是{0}'.format(self.__age))
class Student(Person):
def __init__(self,name,age,score):
Person.__init__(self,name,age)
self.score=score
def say_age(self): #重写say_age()方法
print('{0}的年龄是{1}'.format(self.name,self._Person__age)) #父类的私有属性可被子类继承,但在子类中需要特殊方法方可访问
stu01=Student('小芳',19,95)
stu01.say_age()
通过类的方法mro()或类的属性__mro__可输出该类的继承层次结构。
(MRO:Method Resolution Order 方法解析顺序。Python支持多继承,若子类调用的方法名字在多个父类中存在,在子类未指定父类名时,解释器将按从左至右的顺序搜索)
用dir()获取object类的属性:
用dir()获取Person类的属性:
可见Person类作为Object类的子类,继承了所有属性,并另外增加了6个属性。
method是方法,但实际上也是属性,其类型为method。
若在子类中想获取到父类的方法,可使用代码:
super().方法名(参数列表)
等同于:
父类名.方法名(self [,参数列表])
super()代表父类的定义,而非父类的对象。
不同对象调用同一个方法,产生不同行为。
class Color:
def say_color(self):
return '很多种颜色'
class Red(Color):
def say_color(self):
return '红色'
class Blue(Color):
def say_color(self):
return '蓝色'
class Green:
def say_color(self):
return '绿色'
def color(c):
if isinstance(c,Color):
print(c.say_color())
else:
print('不知道')
color(Green())
color(Blue())
color(Red())
color(Color())
class Person:
def __init__(self,name):
self.name=name
def __add__(self,other):
return '{0}+{1}'.format(self.name,other.name)
def __mul__(self,other):
return self.name*len(other.name)
p1=Person('小红')
p2=Person('小芳')
print(p1+p2)
print(p1.__add__(p2))
print(p1*p2)
print(p1.__mul__(p2))
print(p1.__dict__) #对象的属性字典
print(p1.__class__) #对象所属的类
print(Student.__bases__) #类的基类元组(多继承情况下)
print(Student.__base__) #类的基类
print(Student.__mro__) #类的继承层次结构
print(Color.__subclasses__()) #子类列表
只是形成两个变量,实际指向的是同一个对象。
Python中的拷贝一般都是浅拷贝。
只拷贝对象,不拷贝对象包含的子对象。因此,源对象和拷贝的对象会引用同样的子对象。
递归拷贝对象中包含的子对象。因此,源对象和拷贝对象引用的子对象不同。
例:
import copy
class iPhone:
def __init__(self,screen,cpu):
self.screen=screen
self.cpu=cpu
class Screen:
def show(self):
print('screen:',self)
class Cpu:
def calculate(self):
print('cpu:',self)
ip1=iPhone(Screen(),Cpu())
ip2=ip1
ip3=copy.copy(ip1)
ip4=copy.deepcopy(ip1)
print(ip1,ip1.screen,ip1.cpu)
print(ip2,ip2.screen,ip2.cpu) #ip2指向的对象及其包含的子对象都与ip1一致
print(ip3,ip3.screen,ip3.cpu) #ip3指向的对象与ip1不一致,但包含的子对象一致
print(ip4,ip4.screen,ip4.cpu) #ip4指向的对象与ip1不一致,包含的子对象也不一致
除继承外,也可通过组合实现代码复用。
例1:
class iPhone:
def __init__(self,screen,cpu):
self.screen=screen
self.cpu=cpu
class Screen:
def show(self):
print('screen:',self)
class Cpu:
def calculate(self):
print('cpu:',self)
ip1=iPhone(Screen(),Cpu())
ip1.screen.show()
ip1.cpu.calculate()
例2:
class A:
def say(self):
print('hahaha')
class B:
def __init__(self,a):
self.a=a
B(A()).a.say()