Python从设计之初,就立足于面向对象,它的一个变量,实际上就是一个类对象。我们来看一个实验:
这里,变量i并非像面向过程语言中的变量,是一个单值量,而是一个类对象(继承了很多东西),因为我们定义的这个变量,赋初值为0.,所以Python把它当成一个整型变量,所以我们打印它的__class__属性(__class__指类名),看到的是’int’,而它的__doc__属性(__doc__属性指帮助信息)为class。
下面是变量i后面按下.之后列出的信息:
如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。
接下来我们先来简单的了解下面向对象的一些基本特征。
1.1:面向对象技术简介
· 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
· 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
· 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
· 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
· 实例变量:定义在方法中的变量,只作用于当前实例的类。
· 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。
· 实例化:创建一个类的实例,类的具体对象。
· 方法:类中定义的函数。
· 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
·
1.2 创建类
使用class语句来创建一个新类,class之后为类的名称并以冒号结尾,如下实例:
classClassName:
'类的帮助信息' #类文档字符串
class_suite #类体
类的帮助信息可以通过ClassName.__doc__查看。
class_suite 由类成员,方法,数据属性组成,class_suite并不是一个关键字。
1.3实例
以下是一个简单的Python类实例:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
classEmployee:
'所有员工的基类' #注意这不是注释,是类属性的一部分,撰写类的帮助信息
empCount = 0 #类变量
def__init__(self, name, salary): #__init__是类的方法——构造函数,name和salary是实例变量,也叫实例化变量
self.name = name #给类实例化,就是赋予实际的意义(就是实际的值)
self.salary = salary
Employee.empCount += 1 #在类内部使用类变量,与其他实例是共享的,相当于全局变量
defdisplayCount(self): #普通方法(与__init__不一样)
print"Total Employee %d" % Employee.empCount
defdisplayEmployee(self): #普通方法
print"Name : ", self.name, ", Salary: ", self.salary
· empCount变量是一个类变量,它的值将在这个类的所有实例之间共享。你可以在内部类或外部类使用 Employee.empCount 访问。
· 第一种方法__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法
如何使用这个类呢?
创建实例对象
实例化类其他编程语言中一般用关键字 new,但是在 Python 中并没有这个关键字,类的实例化类似函数调用方式。
以下使用类的名称 Employee 来实例化,实例化的过程中是通过 __init__ 方法来接受参数的。
"创建 Employee 类的第一个对象"
emp1 =Employee("张三",8000)
"创建 Employee 类的第二个对象"
emp2 =Employee("王五",10000)
访问属性
您可以使用点(.)来访问对象的属性。使用类的名称访问类变量:
emp1.displayEmployee() #访问类属性,即方法,也可叫类里面的函数,本质一样,只是叫法不一样
emp2.displayEmployee() #
print"Total Employee %d"%Employee.empCount #通过类名访问类变量
完整实例:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
classEmployee:
'所有员工的基类'
empCount = 0
def__init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
defdisplayCount(self):
print"Total Employee %d" % Employee.empCount
defdisplayEmployee(self):
print"Name : ", self.name, ", Salary: ", self.salary
#创建 Employee 类的第一个对象
emp1 = Employee("张三", 8000)
#创建 Employee 类的第二个对象
emp2 = Employee("王五", 10000)
emp1.displayEmployee()
emp2.displayEmployee()
print"Total Employee %d" % Employee.empCount
执行以上代码输出结果如下:
Name: 张三,Salary: 8000
Name: 王五,Salary: 10000
TotalEmployee2
Pycharm的实验结果
因为觉得displayCount方法意义不大,所以没有放进去。
看看__doc__帮助信息:
__doc__相当于一个类的描述信息,有时候也很有用。
添加属性
我们这里添加了属性age,但是添加之后想打印出来看看,发现打印不出来,为什么呢?
因为,我们在类中的方法DisplayEmployee中并没有age(年龄)的打印代码。所以不可能打印age出来。
那么,怎么知道age属性添加进到对象emp1中去了呢?
我们可以通过dir这个方法来列出对象emp1的所有属性,我们看到了age。
改变属性值
我们可以直接通过对象访问其属性,并进行修改。
删除属性
因为在第16行已经删除了属性age,所以第17行再要打印这个属性值,将错误!
1.4 python中操作类的内置方法
· getattr(obj, name[, default]) : 访问对象的属性。
· hasattr(obj,name) : 检查是否存在一个属性。
· setattr(obj,name,value) : 设置一个属性。如果属性不存在,会创建一个新属性。
· delattr(obj, name) : 删除属性。
hasattr(emp1, 'age')# 如果存在 'age' 属性返回 True。
getattr(emp1, 'age')# 返回 'age' 属性的值
setattr(emp1, 'age', 8)# 添加属性 'age' 值为 8
delattr(empl, 'age')# 删除属性 'age'
以上内容很容易做实验。
1.5 关于类中的self
如果说类有灵魂的话,那么它的灵魂就是self;
这里有个奇怪的问题是:类中定义的方法有self这个形参,但是在实际传参的时候,并没有传参给self,这是为什么呢?
这是因为,self是实例化的一个影子,就是当我们第12条语句执行的时候,就产生了一个对象,这个对象通过name=”张三”,salary=8000实例化了,此时python系统会自动的把这个对象本身赋予self这个参数。此时此刻,似乎很玄妙,第12条语句在创建类的实例emp1的时候,当emp1还没有形成的时候,却有了emp1的一个生命,或者灵魂叫self。而,在第14,15行的时候,我们其实已经有了emp1,但是为什么也不传参给self呢。我想应该是在最开始就可以用self来代表emp1,那接下来的类似应用中都可以self就代表对象本身,而不需要特指谁。
我想试试,既然使用的时候(第12,13,14,15行代码)都不传参给类中的方法,那么类中的self是否可以不要呢?
你看,不要self,编译的时候会报错,这个错误信息是说:在执行第12条语句的时候,也就是调用__init__函数的时候,我们传了3个参数进去,而实际上__init__只需要2个参数。
为什么是这样呢?我们不是明明只传两个参数吗,一个名字,一个薪水,怎么说我们传了三个参数呢?
此时,我们应该可以肯定了,python类中的每个方法的调用,系统默认第一个参数就是self了,只是这个self是隐形的,因为它是类对象的灵魂。
我还想做一个实验:既然类中定义的每个方法,第一个参数是self,而且python系统会默认的传一个参数给self,那我可否把对象直接传给它呢?可以吗,应该不行不通,因为我发现,在第12行,对象emp1都还没有生成,你怎么在方法里面把它当参数传进去了?所以,想到这里,就不用做这个实验了。
self代表类的实例,而非类
通过以上实验,我们能够分析出,self特指某一个类的对象的地址。
Self并不是一个关键字
Self并不是一个Python内置的关键字,而是一个约定俗成的一个名字。我们可以取其它名字。
1.6 python对象销毁
1.6.1 自动销毁
注意点:第3行是构造函数,函数名__init__是构造函数的名称,参数x=0,y=0是初始化参数列表,如果在第8行不传参数100,200进去,那么P1.x=0,P1.y=0。
问题是:我__del__函数是什么时候调用的,在哪里调用的?
其中原理是,当我们运行完,系统会自动的销毁对象P1占用的相关资源。所以,__del__是系统调用的。
1.6.2 通过del命令销毁
1.7 对象的引用
1.7.1 引用和被引用的地址
引用本身不占用内存空间,他只是一跟绳子。
1.7.2 引用计数器
Python 使用了引用计数这一简单技术来跟踪和回收垃圾。
在 Python 内部记录着所有使用中的对象各有多少引用。
一个内部跟踪变量,称为一个引用计数器。
当对象被创建时,就创建了一个引用计数,当这个对象不再需要时,也就是说,这个对象的引用计数变为0 时,它被垃圾回收。但是回收不是"立即"的,由解释器在适当的时机,将垃圾对象占用的内存空间回收。
我想知道,没有对象引用,引用计数器会是多少呢?
默认情况下等于2。为什么?
因为,在sys.getrefcount(P1)中,传入的但是也是一次引用,所以引用计数器=2;
我们再增加一个引用
但是在第12行中,调用sys.getrefcount(P1)并没有继续增加P1的引用计数器,我个人认为,是python系统刻意为之,就是不管sys.getrefcount(P1)调用多少次,只算一次。
1.7.3 删除引用不等于删除对象
第14行删除引用P3(注意,不是删除对象P3,P3不是对象是引用)
引用P3删除后,P1的引用计数器会减一;
这里有个问题:第14行执行,为什么不执行析构函数__del__?
这个原因还是因为第14行删除的不是对象,而是引用造成的。
如果删除P1,再访问P2,P3,P4会出现什么问题呢?这个实验我没有做,有兴趣的可以去做一下。
1.8 继承
1.8.1:何为继承
继承,是自然界普遍存在的一种现象,是从先辈那里得到已有的特征和行为。类的继承就是新类从已有类那里获得已有的属性和行为,或者说基类派生了具有基类特征又有新特征的类。
继承是软件可重用性的一种形式,新类通过继承从现有类中吸取属性和行为,并对其进行覆盖和改写,产生新类所需要的功能。
同样的,新类也可以派生出其他更新的类。
1.8.2:继承的语法
子类(派生类)具有父类(基类)的所有属性行为,且可以增加新的行为和属性。
继承语法
派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:
class 派生类名(基类名)://... 基类名写在括号里,基本类是在类定义的时候,在元组之中指明的。
classSubClassName(ParentClass1[,ParentClass2,...]):
'Optional class documentation string' //类的帮助信息
class_suite //子类代码部分
比如:class woman(people)://括号里的people是基类名,woman是子类(也叫派生类)。
在python中继承中的一些特点:
1:基类名是一个元组,元素可以是一个,也可以是多个,如ParentClass1,ParentClass2,
2:在继承中基类的构造(__init__()方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用。(下面有实例讲解1.8.3)
3:在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别在于类中调用普通函数时并不需要带上self参数
4:Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。
如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。
1.8.3 实例,重写,覆盖
现在,我们要通过people这个基类创建一个派生类woman,而且要增加一个属性性别(sex),该怎么做呢?
注意点:
1, 在第9行,添加了子类woman的额外属性sex。
2, 子类的属性一共有4个,其中3个继承自父类,所以在子类的构造函数中,需要传入4个参数。但是在python中,父类的构造函数不会自动调用,需要子类类显示调用,并传入参数self,调用方法见第11行。
3, 因为,woman加入了sex属性,所以woman要介绍自己,就需要重写父类的speak函数,因为父类的speak函数只有名字,年龄,体重三个属性,此时怎么办呢?
那就需要子类重写父类方法speak,加入新的内容。
4, 我们在第16行调用的时候,只会调用子类的speak,父类的speak被覆盖。
5, Super函数的使用见下面的博文。
http://blog.csdn.net/goodluckac/article/details/53100957
1.8.4 子类对象怎么调用父类方法
在python中,子类只能通过super函数调用父类的方法。其实,父类的所有属性和方法都被子类继承了,但是重写的方法只能通过super函数实现。(子类通过super访问父类同名函数,意义不大)
基类的属性因为被全部继承下来了,所以没有必要通过super函数访问,直接通过子类对象即可访问。
1.9 issubclass()和isinstance()函数
Issubclass(cls,classinfo)用于判断cls是不是classinfo的子类;
Isinstance(o,t)用于判断对象o是不是类t的某一个实例;
当我们在Pycharm中敲入is的时候,pycharm系统会帮我们跳出上面的这个属性联想图,其中最前面的f表示是函数(也即方法),后面的builtins表示是内置函数(方法)。
这两个函数返回的是布尔变量。
1.10 类的私有和公有(属性/方法)
在python中,类的私有成员和方法通过双下划线来表示,因此没有双下划线的属性或方法即为公有属性,公有属性可以在类外通过实例化的对象进行访问,但是私有成员和方法不能在类外通过实例化的对象进行访问,只能在类内访问。
上面是私有成员函数不能在类外访问的情况。
我们再看看私有成员变量的情况。
私有变量不能在类外直接访问,只能在类内访问(像第10行)。
私有方法,既然不能在类外访问,那么定义出来有何意义?
我们怎么调用__display_all()