本文共 14118 字,大约阅读时间需要 47 分钟。
python面向对象编程全解。
一个类占有一个独立的空间,类中的属性叫做类变量,类中的函数,叫做类的方法。
类(Class):也可以成为类对象。类对象中包含了一批实例对象共有的属性和方法。
类变量:定义在类中且在函数体之外的变量。类变量在所有的实例变量中是共享的,类变量修改了,所有实例对象读取到的值都会修改。
实例变量:定义在类的实例方法中的变量,只作为当前实例的属性。
数据成员:类变量或者实例变量,以及用于处理类及其实例对象的相关的数据的统称。
方法:类中定义的函数。包含实例方法,类方法,静态方法。
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。其实只是在派生类中添加了同名方法,以至于查询方法时不会再向基类查询
继承:即一个派生类(derived class)继承基类(base class)的属性和方法。继承也允许把一个派生类的对象作为一个基类对象对待。(就是说派生类还可以再派生孙子类)
实例化:创建一个类的实例,类的具体对象。在实例对象开辟一个空间,并为实例对象添加对类的引用。并没有复制类中的属性和方法到实例对象中。
实例对象:通过类定义的数据结构实例。
在python中没有方法重载,因为本来函数的参数就是可以省略的。
后定义的函数会覆盖先定义的同名方法。实例方法:只能通过实例对象调用,因为实例方法第一个定义的参数必须是实例对象本身。
class Myclass: def foo(self): print(id(self),'foo') #id()是获取对象的空间地址a=Myclass()#既然是实例对象,那就要创建实例a.foo()#输出类里的函数地址print(id(a))#输出类对象的地址#结果地址一样 1 2 3 4 5 6 7 8 9
类方法:定义类方法,要使用装饰器@classmethod,定义的第一个参数一定是类的引用,不过可以通过类或者实例的引用。
class Myclass: @classmethod#类装饰器 def foo2(self): print(id(self),'foo2') print(id(Myclass)) #类对象,直接可以调用,不需要实例化。这句说明了类也是有存储空间的Myclass.foo2() #类方法,直接可以调用,不需要实例化对象 1 2 3 4 5 6 7 8
静态方法:定义静态方法使用装饰器@staticmethod,没有默认的必须参数,可以通过类和实例直接调用。静态方法就如同类外函数一样。若在静态函数内访问类变量也是需要 类名.变量名 的方式访问。
class Myclass: @staticmethod#静态方法 def foo3(): #没有self参数 print('foo3')Myclass.foo3() #通过类调用a=Myclass()a.foo3() #通过实例调用#结果foo3 1 2 3 4 5 6 7 8 9
实例化:为实例化对象新开辟一个空间。并为实例对象添加到类对象的引用,用于变量的查找。不过并没有为实例对象复制任何内容,这也就大大节省了空间。实例对象可以自定义自己的空间,但是不能修改类对象空间。
通过类名,可以调用类对象的类方法和静态方法,实例方法无法通过类名调用,因为实例方法的self必须是实例对象。类方法中self表示类对象。
通过实例对象,可以访问类对象的所有属性,调用类对象的所有方法。但是不能修改类对象中的属性和方法的指向(因为属性和方法都是引用变量),不过可以修改属性和方法指向的数据。
继承:继承的过程和实例化的过程相似,也是为派生类对象开辟一个独立的空间,并为派生类对象天剑了到基类对象的引用,用于变量查找。没有为派生类对象复制任何内容,大大节省了空间。派生类对象可以自定义自己的空间,为派生类对象添加派生类的类变量,实例方法,类方法、静态方法。
#!/usr/bin/python# -*- coding: UTF-8 -*-class Employee: '所有员工的基类' empCount = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.empCount += 1 def displayCount(self): print("TotalEmployee %d" % Employee.empCount) def displayEmployee(self): print("Name: ", self.name, ", Salary: ", self.salary) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
empCount 变量是一个类变量,所有类的实例对象都具有这个成员,它的值将在这个类的所有实例之间共享。你可以在内部类或外部类使用 Employee.empCount 访问。
第一种方法__init__()
方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时,就会先为实例对象开辟空间,添加到类对象的引用,然后就会调用该方法。(其实是先调用的__new__
方法,再调用的__init__
方法)
实例方法的self 代表类的实例对象,self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。Self代表了实例对象,而不是类,那就是:类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
实例化类其他编程语言中一般用关键字 new,但是在 Python 中,类的实例化类似函数调用方式。
以下使用类的名称Employee 来实例化,并通过 __init__
方法接受参数。
"创建 Employee 类的第一个对象"emp1 = Employee("Zara", 2000)"创建 Employee 类的第二个对象"emp2 = Employee("Manni", 5000) 1 2 3 4
下面是读取对象属性的实例:
#!/usr/bin/python# -*- coding: UTF-8 -*-class Employee: '所有员工的基类' empCount = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.empCount += 1 def displayCount(self): print("Total Employee %d" % Employee.empCount) def displayEmployee(self): print("Name : ", self.name, ", Salary: ", self.salary) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
"创建 Employee 类的第一个对象"emp1 =Employee("Zara", 2000)"创建 Employee 类的第二个对象"emp2 =Employee("Manni", 5000)emp1.displayEmployee()emp2.displayEmployee()print "TotalEmployee %d" % Employee.empCount 1 2 3 4 5 6 7
以下函数还可以对属性进行读取之外的操作:
getattr(obj,name[, default]) : 访问对象的属性。hasattr(obj,name): 检查是否存在一个属性。setattr(obj,name,value): 设置一个属性。如果属性不存在,会创建一个新属性。delattr(obj,name) : 删除属性。 1 2 3 4
__dict__ : 类的属性(包含一个字典,由类的数据属性组成)__doc__ :类的文档字符串__name__: 类名__module__: 类定义所在的模块(类的全名是'__main__.className',如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod)__bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组) 1 2 3 4 5
__init__ 构造函数,在生成对象时调用__del__ 析构函数,释放对象时使用__repr__ 打印,转换__setitem__按照索引赋值__getitem__按照索引获取值__len__获得长度__cmp__比较运算__call__函数调用__add__加运算__sub__减运算__mul__乘运算__div__除运算__mod__求余运算__pow__称方我们可以为这些函数进行重写,实现我们想要的功能。例如这里对所有的读写操作进行监督屏蔽。def __getitem__(self,key): #在字典中获取值时会自动调用 __getitem__函数,设置值时会自动调用__setitem__函数 print("-------------派生类字典读取值")def __setitem__(self,key,value): print("-------------派生类字典设置值")def __getattr__(self,name): #读取类属性(包括继承的属性)会自动执行__getattr__函数,设置属性会自动执行__setattr__函数 print("-------------读取派生类属性")def __setattr__(self,name,value): print("-------------设置派生类属性值") 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。
除了在前面继承时的内存操作必须要掌握外,你还需要知道
在python中继承中的一些特点:
1:在继承中基类的构造(__init__()
方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用。
2:在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数
3:Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。
class Parent: # 定义父类 parentAttr = 100 def __init__(self): print("调用父类构造函数") def parentMethod(self): print('调用父类方法') def setAttr(self, attr): Parent.parentAttr = attr def getAttr(self): print("父类属性 :", Parent.parentAttr)class Child(Parent): # 定义子类 def __init__(self): print("调用子类构造方法") # 无论子类还是父类,都要单独写一次_init_ def childMethod(self): print('调用子类方法') def getAttr(self): print('重写父类方法,因为父类方法不能满足需求')c = Child() # 实例化子类c.childMethod() # 调用子类的方法c.parentMethod() # 调用父类方法c.setAttr(200) # 再次调用父类的方法 - 设置属性值c.getAttr() # 再次调用父类的方法 - 获取属性值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
你可以使用issubclass()或者isinstance()方法来检测,一个类或对象是否为其他类或对象的子类。
issubclass() - 布尔函数判断一个类是另一个类的子类或者子孙类,语法:issubclass(sub,sup) isinstance(obj, Class) 布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。如果在继承元组中列了一个以上的类,那么它就被称作”多重继承” 。
语法:
派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后。
多态
如果父类方法的功能不能满足需求,可以在子类重写父类的方法。实例对象调用方法时会调用其对应子类的重写后的方法
Python同样支持运算符重载,实例如下:
class Vector: def __init__(self, a, b): self.a = a self.b = b def __str__(self): return 'Vector(%d, %d)' % (self.a, self.b) def __add__(self, other): return Vector(self.a + other.a, self.b + other.b)v1 = Vector(2, 10)v2 = Vector(5, -2)print(v1 + v2) 1 2 3 4 5 6 7 8 9 10 11 12
以上代码执行结果如下所示:
Vector(7,8) 1
1)类的私有属性
__private_attrs
:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时self.__private_attrs
。
2)类的私有方法
__private_method
:两个下划线开头,声明该方法为私有方法,不能在类地外部调用。在类的内部调用self.__private_methods
3)实例
class JustCounter: __secretCount = 0 # 私有变量 publicCount = 0 # 公开变量 def count(self): self.__secretCount += 1 self.publicCount += 1 print(self.__secretCount) # 在内部使用私有化属性,不会产生错误counter = JustCounter()counter.count()counter.count()print(counter.publicCount)print(counter.__secretCount) # 报错,实例不能访问私有变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14
实例对象无法访问私有的类变量,但是可以添加同名的私有实例变量。
__foo__
: 定义的是特列方法,类似 __init__()
之类的。
_foo
: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *
__foo
: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。
People.py模块
__all__=["default_age","default","Moniter"] # __all__变量,设置导入模块import*时将会自动引入的函数、变量、类default_age = 12 #定义模块变量def set_default_age(age=13): #定义模块函数 print("默认年龄为"+str(age)+"岁")class Parent(object): #定义模块类。()内指定基类,当是object可以省略不写 print("定义了Student类") #定义类时就会执行内部的执行语句 default_name='student' #定义一个类变量(不可修改变量) default_arr = [] #定义一个类变量(可修改变量) __default_age=12 #函数名或属性名前面为双下划线表示成员私有。只能在当前类或对象空间内使用。在存储时是存储成_Parent__default_age def __init__(self, name1='student1',age1=13): #init是构造函数,self代表类的实例对象。参数可以设置默认值。 self.name=name1 #自动新增两个实例变量 self.age=age1 print("基类构造函数设置了"+self.name) def getname(self): #实例方法,函数名为引用变量,可以进行赋值,即变更函数体。函数引用变量调用时使用(),不带括号代表变量。getname代表函数引用变量,getname()代表代表调用函数 print('基类读取名称'+self.name) return self.name def setname(self,name1): self.name=name1 print("基类设置名称"+self.name) @staticmethod # @staticmethod声明函数为静态方法,通过类对象和实例对象直接调用 def info(): #静态方法就像类外函数一样。如果函数内需要读写类变量,需要使用Parent.default_name print("派生类的静态函数"+Parent.default_name) @classmethod # @classmethod声明函数为类方法,第一个参数是能是类对象的引用,可以通过类或者实例直用 def setsex(self): # 类方法self表示对类的引用 self.sex = '男' # 添加类变量 print("派生类设置性别" + self.sex)# 派生类继承了基类的类变量和类方法和实例方法(没有实例变量,因为实例变量是在实例以后才存在的)class Child(Parent): #生成派生类,可以多重继承,但是应尽量避免。继承后会包含基类的函数和特性。 def __init__(self,name1="child"): #派生类构造函数不会自动调用基类构造函数 self.name = name1 #在当前实例对象中新增name实例变量 Parent.__init__(self) #两种方法,调用超类的构造函数。基类中就修改了name实例变量,新增了age实例变量 # super(Child,self).__init__(name1) print("派生类构造函数"+self.name) #这里读取的就是最后一次对name的修改(基类中对他的修改) def setname(self, name1): #重写基类中的方法。其实是在派生类中添加了一个新方法,因此在查找此函数时就必用向上查找了。 self.name = "新"+name1 print("基类设置名称" + self.name) def getage(self): #派生类添加新的实例方法 print("派生类读取年龄"+str(self.__default_age)) #派生类是无法读取基类的私有类变量的。因此这句话会报错print('Peopeo类开始运行') #导入模块或执行模块都会执行函数# 当一个module被执行时,moduel.__name__的值将是"__main__",而当一个 module被其它module引用时,module.__name__将是module自己的名字if __name__=="__main__": #只有在执行当前模块时才会运行此函数 set_default_age() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
调用函数
# 只有有__init__.py文件的文件夹才能是package,才支持引入import People #引用此模块,就相当于将此模块在此处展开。#python中一切变量都是引用变量,指向的对象包含不可变量(整型、字符串、元组)和可变量。# 因此如果修改了不可变量,实质是新开一个空间,修改引用指向。如果修改可变量,就是保持引用指向,在原位置修改数据# #调用模块变量、模块函数People.set_default_age(People.default_age)print('=============实例化===================')#实例化对象,开辟新内存,保留对类的引用,但是不复制属性和方法。同时调用init初始化,#因此实例变量可以访问类变量、类方法、静态方法、实例方法。以及自己创建的实例变量。# 类对象无法访问实例变量。因为并没有一个从类对象到实例对象的指针#实例变量可以读取的东西:类的公有属性和方法,自身的所有属性和方法。#实例变量可以修改的东西:类对象的引用变量指向的数据,和自身的所有属性和方法#实例变量不能修改的东西:类对象的引用变量的指向(包含属性和方法)parent1 = People.Parent('student1') #实例化,盗用初始化函数,创建了实例变量name和ageprint('基类对象:',People.Parent.__dict__) #打印对象的自有属性print('基类实例对象:',parent1.__dict__) #这一步可以看出实例化没有将类中的属性和方法引用复制到实例对象空间中。print('==============实例对象读写数据==================')parent1.default_name #实例可以访问类变量,因为变量沿原型链的查找parent1.default_name = 'Student' #实例无法修改类变量(引用变量)的指向,所以这个是在实例对象中添加了一个实例变量parent1.default_arr.append(1) #实例可以修改类变量指向的数据内容parent1.default_arr = [1,2] #实例无法修改类变量(引用变量)的指向,所以这个是在实例对象中添加了一个实例变量# print(parent1.__default_age) #实例对象不能访问类对象的私有类变量parent1.__default_age=14 #为实例对象添加实例变量(在执行时添加的不再是私有变量)print(parent1.__default_age) #实例对象可以访问到自身的所有属性和方法print('基类对象:',People.Parent.__dict__)print('基类实例对象:',parent1.__dict__)print('=============基类对象读写数据===================')#类对象可以访问和修改的内容:类的属性和方法People.Parent.default_name='Student' #修改类变量的指向People.Parent.default_arr=[11] #修改类变量的指向People.Parent.default_arr.append(12) #修改类变量指向的内容People.Parent.__default_age=14 #通过类名无法修改私有类变量,因为在存储中私有变量的存储名为_Parent__default_age。python并不建议修改私有变量,虽然可以通过这个名称修改变量值print('基类对象:',People.Parent.__dict__)print('基类实例对象:',parent1.__dict__)print('=============继承===================')#派生类,开辟新内存,保留对基类的引用,但是不复制属性和方法。# 因此派生类对象可以访问基类变量、基类方法、基类静态方法、基类实例方法。以及自己创建的派生类属性和方法。基类对象无法访问派生类对象。因为并没有一个从基类对象到派生类对象的指针#派生类对象可以读取的东西:基类的公有和保护属性和方法,自身的所有属性和方法。#派生类对象可以修改的东西:基类对象的引用变量指向的数据,和自身的所有属性和方法#派生类不能修改的东西:基类对象的引用变量的指向(包含属性和方法)print('基类对象:',People.Parent.__dict__) #打印对象的自有属性print('派生类对象:',People.Child.__dict__) #这一步可以看出实例化没有将类中的属性和方法引用复制到实例对象空间中。print('=============派生类对象读写数据===================')People.Child.default_name #派生类对象可以访问基类的类变量,因为变量沿原型链的查找People.Child.default_name = 'child' #派生类对象无法修改基类的类变量(引用变量)的指向,所以这个是在派生类对象中添加了一个类变量People.Child.default_arr.append(1) #派生类对象可以修改基类的类变量指向的数据内容People.Child.default_arr = [1,2] #派生类对象无法修改基类的类变量(引用变量)的指向,所以这个是在派生类对象中添加了一个类变量# print(People.Child.__default_age) #派生类对象不能访问基类对象的私有类变量People.Child.__default_age=14 #为派生类对象添加类变量(在执行时添加的不再是私有变量)print(People.Child.__default_age) #类对象可以访问到自身的所有属性和方法print('基类对象:',People.Parent.__dict__)print('派生类对象:',People.Child.__dict__)print('=============派生类的实例对象读写数据===================')#基类的实例对象和基类的关系等同于派生类的实例对象和派生类的关系# 派生类的初始化函数调用不会自动调用基类的初始化函数child1 = People.Child() #调用类的初始化函数,实例化一个对象,这里在派生类的实例对象中添加了name和age属性child1.setname('child') #调用派生类对象中的重写或继承的方法# child1.getage() #调用派生类对象添加的方法,方法访问了基类的私有变量,会报错print('派生类对象:',People.Child.__dict__)print('派生类实例对象:',child1.__dict__)print('==============静态方法==================')parent1.info() #调用静态函数,方法1People.Parent.info() #调用静态函数,方法2print('=============类方法===================')# print(parent1.sex) #类对象和实例对象中不存在sex,所以无法查找到,访问出错parent1.setsex() #调用类方法,在类对象中添加类变量print(parent1.sex) #通过实例对象访问类变量print('基类对象:',People.Parent.__dict__)print('基类实例1对象:',parent1.__dict__)print('===========原型链查询=====================')print(issubclass(People.Child,People.Parent)) # 布尔函数(Child,Parent)判断一个类是另一个类的子类或者子孙类,语法:issubclass(sub,sup)print(isinstance(child1, People.Parent)) #布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90