面向对象编程将数据和操作数据的方法封装在对象中,组织代码和数据的方式更加接近人的思维。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=score4.通过“类名(参数列表)”调用构造函数。调用后,将创建好的对象返回给相应变量。
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) #在类外无法用访问公共属性的方式访问私有属性get方法和set方法及@property用法详见link
隐藏对象的属性和实现细节,只对外提供必要的调用方法。 通过“私有属性”、“私有方法”实现封装。
继承可以让子类拥有父类的特性,提高了代码重用性。可以在子类增加新功能,或改进已有算法。 子类可继承父类除构造方法外的所有成员。 继承的语法格式:
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)通过类的方法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())只是形成两个变量,实际指向的是同一个对象。
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()