面向对象编程
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
面向对象三大特点:封装,集成,多态。
类和实例
类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
class Student(object): def __init__(self,name,source): self.name=name self.source=source def printSource(self): print("%s的成绩是:%s"%(self.name,self.source)) #创建出一个学生对象,这个对象拥有name和score这两个属性(Property) lisa=Student('Lisa',98) sam=Student('Sam',70) #调用对象的printSource方法 lisa.printSource() sam.printSource()
lisa.age=18
print(lisa.age)
print(sam.age)
输出:
Lisa的成绩是:98
Sam的成绩是:70
18
Traceback (most recent call last):
File "/home/wangxy/PycharmProjects/module/Student.py", line 22, in <module>
print(sam.age)
AttributeError: 'Student' object has no attribute 'age'
访问限制
1.如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,即成为私有变量(private),只有内部可以访问,外部不能访问。
2.在Python中,变量名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__
、__score__
这样的变量名
3.以一个下划线开头的实例变量名,比如_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”
4.设置为私有变量后,如果要获取属性可以设置方法get_name,修改属性可以使用set_name ,和java类似
class Student(object): def __init__(self,name,source): self.__name=name self.__source=source def print_source(self): print("%s的成绩为:%s"%(self.__name,self.__source)) def get_name(self): return self.__name def get_source(self): return self.__source def set_name(self,name): self.__name=name #在方法中,可以对参数做检查,避免传入无效的参数.而直接赋值无法做到 def set_source(self,source): if 0<=source<=100: self.__source=source else: print("无效的数据") Tina=Student('Tina',59) Tina.set_source(80) Tina.print_source(); Tina.__name 输出: Tina的成绩为:80 Traceback (most recent call last): File "/home/wangxy/PycharmProjects/module/StuPrivate.py", line 29, in <module> Tina.__name AttributeError: 'Student' object has no attribute '__name'
继承和多态
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
当子类和父类都存在相同的run()
方法时,我们说,子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
。这样,我们就获得了继承的另一个好处:多态。
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:
class Animal(object): def run(self): print('Animal is running……')
from TestAnimal.Animal import Animal #继承了Animal类 class Dog(Animal): #继承后对父类的方法进行重写,这叫多态 def run(self): print('Dog is running') dog=Dog() #继承后自动拥有父类方法 dog.run()
对于一个变量,我们只需要知道它是Animal
类型,无需确切地知道它的子类型,就可以放心地调用run()
方法,而具体调用的run()
方法是作用在Animal
、Dog
、Cat
还是Tortoise
对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal
的子类时,只要确保run()
方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增Animal
子类;
对修改封闭:不需要修改依赖Animal
类型的run_twice()
等函数。
静态语言 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。
对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了:
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()
方法,返回其内容。但是,许多对象,只要有read()
方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()
方法的对象。
获取对象信息
一、判断对象类型使用type()方法:
#判断对象类型,使用type(),返回的是对象的类 print(type(a)) print(type(123)) #判断两个变量的type类型是否相同 print(type(a)==type(d)) print(type(123)==int) #判断一个变量是否是函数,使用type模块中定义的常量 print(type(Animal.run)==types.FunctionType) print(type(abs)==types.BuiltinFunctionType)
二、判断class的类型,使用isinstance()
#判断一个变量是否是某个类型 print('a is animal?',isinstance(a,Animal)) print('a is dog?',isinstance(a,Dog)) #下面判断得出d不仅是dog,还是animl类 print('d is animal?',isinstance(d,Animal)) print('d is dog?',isinstance(d,Dog))
三、能用type判断的也能用isinstance()判断
#使用isinstance()判断类型 print('\'a\' is str?',isinstance('a',str)) #判断一个变量是否是某些类型的一种 print('[1,2,3] is list or tuple?',isinstance([1,2,3],(list,tuple)))
四、使用dir(object)
如果要获取一个对象的所有属性和方法,可以使用dir(object)
#获取一个对象的所有属性和方法 print(dir(d)) 结果: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'run']
仅仅把属性和方法列出来是不够的,配合getattr()
、setattr()
以及hasattr()
,我们可以直接操作一个对象的状态:
#操作一个对象的状态:hasattr(),getattr(),setattr() class myObject(object): def __init__(self): self.x=9 def power(self): return self.x * self.x test=myObject(); #test对象有没有x属性 print(hasattr(test,'x')) #设置test对象的y=20 setattr(test,'y',20) print(hasattr(test,'y')) #获取test的x属性值 print(getattr(test,'x')) #获取不存在的属性 getattr(test,'z') #获取不存在的属性,可以设置默认值,当属性不存在时,输出默认值 print(getattr(test,'z',404)) 输出: True True 9 Traceback (most recent call last): File "/home/wangxy/PycharmProjects/module/TestAnimal/Animal.py", line 75, in <module> getattr(test,'z') AttributeError: 'myObject' object has no attribute 'z' 404
#也可以获取属性的方法: print(getattr(a,'run')) 输出 <bound method Animal.run of <__main__.Animal object at 0x7fee8db2ea20>>
通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。
一个正确的用法的例子如下:
def readImage(fp): if hasattr(fp, 'read'): return readData(fp) return None
假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()
就派上了用场。
请注意,在Python这类动态语言中,根据鸭子类型,有read()
方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()
方法返回的是有效的图像数据,就不影响读取图像的功能。
实例属性和类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self
变量:
class Student(object): def __init__(self,name): self.name=name s1=Student('wxy') s1.source=90 print(s1.name,s1.source)
类属性:归类所有,所有实例都可以访问到。
class Stu(object): #类属性 name='Student' s=Stu() #创建实例 print(s.name)#打印实例name属性,因为不存在,所以会继续查找类的name属性 s.name='wxy' #向实例赋值 print(s.name)#打印实例name属性,实例属性优先级高于类属性,因此,打印实例属性 print(Stu.name)#类属性并未消失,使用stu.name可以访问 del s.name #删除实例属性 print(s.name)#实例属性已被删除,则会显示类属性