前面介绍了Python主要的内建对象类型(数字、字符串、列表、元组和字典),以及内建函数和标准库的用法,还有定义函数的方法。现在开始学习如何创建自己的对象。
一.对象
【百度百科】中的定义:
广义:在内存上一段有意义的区域,称作为一个对象。
在C中,具有特定长度的类型,可以称作为对象类型,函数不具有特定长度,所以不是对象类型。
在显式支持面向对象的语言中,“对象”一般是指类在内存中装载的实例,具有相关的成员变量和成员函数(也称为:方法)。
教材上的定义:
对象(object)基本上可以看做数据(特性)以及由一系列可以存取、操作这些数据的方法所组成的集合。
二.对象的特征
1.多态性
“多态性”一词最早用于生物学,指同一种族的生物体具有不同的特性。
在面向对象的程序设计理论中,多态性的定义是:同一操作作用于不同的类的实例,将产生不同的执行结果,即不同的类的对象收到相同的消息时,得到不同的结果。多态是面向对象的程序设计的重要特征之一,是扩展性在“继承”之后的又一重大表现 。对象根据所接受的消息而做出动作,同样的消息被不同的对象接受时可能导致完全不同的行为,这种现象称为多态性。
多态性包含编译时的多态性、运行时的多态性两大类。 即:多态性也分静态多态性和动态多态性两种。
静态多态性:
是指定义在一个类或一个函数中的同名函数,它们根据参数表(类型以及个数)区别语义,并通过静态联编实现,例如,在一个类中定义的不同参数的构造函数。
动态多态性:
是指定义在一个类层次的不同类中的重载函数,它们一般具有相同的函数,因此要根据指针指向的对象所在类来区别语义,它通过动态联编实现。
在用户不作任何干预的环境下,类的成员函数的行为能根据调用它的对象类型自动作出适应性调整,而且调整是发生在程序运行时,这就是程序的动态多态性。即,发出同样的消息被不同类型的对象接收时,有可能导致完全不同的行为。
例:
#加运算符对于整数和字符串都能起作用。
>>> 1+2
3
>>> 'Fish'+'License'
'FishLicense'
很多函数和运算符都是多态的,写的绝大多数程序可能都是,即便并没有意识到。
2.封装性
封装 (encapsulation):隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读取和修改的访问级别。
封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的数据源进行有机的结合,形成“类”,其中数据和函数都是类的成员。
封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员。
每个类的实例可以拥有保存自己状态的属性。一个类的实例也可以有改变自己状态的(定义在类中的)方法。
每个对象有它自己的状态,对象的状态由它的特性(比如名称)来描述。对象的方法可以改变它的特性。所以就像是一堆的函数(方法)捆在一起,并且给予它们访问变量(特性)的权利,它们可以在函数调用之间保存的值。
3.继承性
“继承”是面型对向软件技术当中的一个概念。如果一个类A继承自另一个类B,就把这个A称为"B的子类",而把B称为"A的父类"。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。
在令子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。另外,为子类追加新的属性和方法也是常见的做法。
有些编程语言支持多重继承,即一个子类可以同时有多个父类,比如C++编程语言;而在有些编程语言中,一个子类只能继承自一个父类,比如Java编程语言,这时可以利用接口来实现与多重继承相似的效果。
在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
比如说有个Shape类,可以用来在屏幕上画出指定的形状。现在需要创建一个叫做Rectangle的类,它不但可以在屏幕上画出指定的形状,而且还能计算该形状的面积。但又不想把Shape里面已经写好的draw方法再写一次。可以让Rectangle从Shape类继承方法。在Rectangle对象上调用draw方法时,程序会自动从Shape类调用该方法。
三.类
1.什么是类
类可以看做是种类或者类型的同义词,所有的对象都属于一个类,称为类的实例。
在面向对象的程序设计中,子类的关系是隐式的,因为一个类的定义,取决于它所支持的方法,类的所有实例都包含这些方法,所以所有子类的实例都有这些方法,定义子类只是定义更多(也有可能是重载已经存在的)的方法的过程。
在软件术语中,被继承的类一般称为“超类”,也有叫做父类。是继承中非常重要的概念,它和子类一起形象地描述了继承的层次关系。
2.创建类
由类体类成员,方法,数据属性组成。
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
- empCount 变量是一个类变量,它的值将在这个类的所有实例之间共享。你可以在内部类或外部类使用 Employee.empCount 访问。
- 第一种方法__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。
- self 代表类的实例,self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
3.创建实例对象
实例化类其他编程语言中一般用关键字 new,但是在 Python 中并没有这个关键字,类的实例化类似函数调用方式。
以下使用类的名称 Employee 来实例化,并通过 __init__ 方法接收参数。
"创建 Employee 类的第一个对象"
emp1 = Employee("Zara", 2000)
"创建 Employee 类的第二个对象"
emp2 = Employee("Manni", 5000)
4.访问属性
您可以使用点号 . 来访问对象的属性。使用如下类的名称访问类变量:
emp1.displayEmployee()
emp2.displayEmployee()
print "Total Employee %d" % Employee.empCount
- 可以将一个特性绑定到一个普通函数上,这样就不会有特殊的self参数了。
def displayCount(self):
print "Total Employee %d" % Employee.empCount
def function():
print "Total Employee is %d" % Employee.empCount
Emplyee.displayCount = function()
- 可以使用其他变量引用同一个方法。
function = Emplyee.displayCount
四.类的命名空间
所有位于 class 语句中的代码,其实都位于特殊的命名空间中,通常称之为类命名空间,这个命名空间可由类内所有的成员访问。Python 中,编写的整个程序默认处于全局命名空间内,而类体则处于类命名空间内。
- 1.类名.属性
使用类名.属性 只会寻找类中的静态变量名字
使用对象.属性 会现在对象自己的命名空间中找名字。如果找不到再到类的内存空间中去找
# 只要你使用静态变量,就用类名去调用,命名空间可由类内所有的成员访问。
class Person:
money = 0
mother = Person()
father = Person()
Person.money += 1000
Person.money += 1000
print(mother.money) #2000
print(father.money) #2000
print(Person.money) #2000
- 2.写一个类,能统计这个类被多少个对象实例化了,所有的对象共享这个结果,init 静态变量。
class Foo:
num = 0
def __init__(self):
Foo.num += 1
f1 = Foo()
print(Foo.num) # 1
f2 = Foo()
print(Foo.num) # 2
print(f1.num) # 2
- Python 允许在全局范围内放置可执行代码,当 Python 执行该程序时,这些代码就会获得执行的机会。类似地,Python 同样允许在类范围内放置可执行代码,当 Python 执行该类定义肘,这些代码同样会获得执行的机会。
下面代码示范了在全局空间和类命名空间内分别定义 lambda 表达式:
global_fn = lambda p: print('执行lambda表达式,p参数: ', p)
class Category:
cate_fn = lambda p: print('执行lambda表达式,p参数: ', p)
# 调用全局范围内的global_fn,为参数p传入参数值
global_fn('fkit') # ①
c = Category()
# 调用类命名空间内的cate_fn,Python自动绑定第一个参数
c.cate_fn() # ②
上面程序分别在全局空间、类命名空间内定义了两个 lambda 表达式,在全局空间内定义的 lambda 表达式就相当于一个普通函数,因此程序使用调用函数的方式来调用该 lambda 表达式,并显式地为第一个参数绑定参数值,如上面程序中 ① 号代码所示。
对于在类命名空间内定义的 lambda 表达式,则相当于在该类命名空间中定义了一个函数,这个函数就变成了实例方法,因此程序必须使用调用方法的方式来调用该 lambda 表达式,Python 同样会为该方法的第二个参数(相当于 self 参数)绑定参数值,如上面程序中 ② 号代码所示。
五.python对象销毁(垃圾回收)
Python 使用了引用计数这一简单技术来跟踪和回收垃圾。
在 Python 内部记录着所有使用中的对象各有多少引用。
一个内部跟踪变量,称为一个引用计数器。
当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。
a = 40 # 创建对象 <40>
b = a # 增加引用, <40> 的计数
c = [b] # 增加引用. <40> 的计数
del a # 减少引用 <40> 的计数
b = 100 # 减少引用 <40> 的计数
c[0] = -1 # 减少引用 <40> 的计数
垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充, 垃圾收集器也会留心被分配的总量很大(及未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环。