第11章 面向对象编程
面向过程:根据操作数据的函数或语句块来设计程序。
面向对象(OOP, object-oriented programming):把数据和功能结合起来,用对象包裹组织程序。
类是用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。
类和对象是面向对象编程的两个主要方面。类创建一个新类型,而对象是类的 实例。
类似于你有一个 int 类型的变量,存储整数的变量是 int 类的实例(对象)。
注意,即便是整数也被作为对象(属于int类)。这和C++、Java(1.5版之前)把整数纯粹作为类型是不同的。通过 help(int) 了解更多这个类的详情。 C#和Java 1.5程序员会熟悉这个概念,因为它类似于 封装与解封装 的概念。 |
类的属性有域和方法。域即类的变量,可以存储数据;方法即类的函数,具有某种功能。
域有两种类型——属于每个实例/类的对象或属于类本身,它们分别被称为实例变量和类变量。
类使用 class 关键字创建,类的域和方法被列在一个缩进块中。
self
类的方法与普通函数有一个特别的区别,类方法必须有一个额外的第一个参数名称 self(这个变量指向对象本身),调用时无需为该参数赋值,python 会提供这个值。如果你有一个不需要参数的方法,也需要给这个方法定义一个 self 参数。
Python中的 self 等价于C++中的 self 指针和 Java、C#中的 this 参考。 |
self 的原理:类 MyClass 的实例 MyObject,调用对象的方法 MyObject.method(arg1, arg2)时,python 会自动转为 MyClass.method(MyObject, arg1, arg2)。
# -*- coding: utf-8 -*- # Filename: simplestclass.py class Person: pass p = Person() print p
使用 class 语句后跟类名创建一个新类,类体为一个空白块,由 pass 语句表示。
创建一个对象/实例:使用类名后跟一对圆括号。打印的是存储对象的计算机内存地址。
对象的方法
类/对象可以拥有像函数一样的方法,这些方法与函数的区别只是一个额外的 self 变量。
# -*- coding: utf-8 -*- # Filename: method.py class Person: def sayHi(self): print 'Hello, how are you?' p = Person() p.sayHi() # This short example can also be written as person().sayHi()
Person().sayHi()
sayHi 方法没有任何参数,但仍然在函数定义时有 self。
__init__() 方法
__init__() 方法是一种特殊的方法,称为类的构造函数或初始化方法,当类的对象/实例创建时就会调用,用来对对象做一些初始化。
注意:__init__ 的开始和结尾都是双下划线。
# -*- coding: utf-8 -*- # Filename: class_init.py class Person: def __init__(self, name): self.name = name def sayHi(self): print 'Hello, my name is', self.name p = Person('Swaroop') p.sayHi() # This short example can also be written as Person('Swaroop').sayHi() Person('Swaroop').sayHi()
__init__ 方法定义为取一个参数 name(以及普通的参数self)。__init__创建了一个新的域,称为 name。
最重要的是,我们没有专门调用 __init__ 方法,只是在创建一个类的新实例的时候,把参数包括在圆括号内跟在类名后面,从而传递给 __init__ 方法。这是这种方法的重要之处。
__init__ 方法类似于C++、C# 和 Java 中的 constructor 。 |
类与对象的方法
数据是与类和对象的名称空间 绑定 的普通变量,即这些名称只在这些类与对象的前提下有效。
有两种类型的 域——类的变量和对象的变量,它们根据是类还是对象 拥有 这个变量而区分。
类的变量 由一个类的所有对象(实例)共享使用。只有一个类变量的拷贝,当某个对象对类的变量做改动时,这个改动会反映到所有其他的实例上。
对象的变量 由类的每个对象/实例拥有。每个对象有自己对这个域的一份拷贝,即它们不是共享的,在同一个类的不同实例中,虽然对象的变量有相同的名称,但是互不相关的。
# -*- coding: utf-8 -*- # Filename: objvar.py class Person: '''Represents a person.''' population = 0 def __init__(self, name): '''Initializes the person's data.''' self.name = name print '(Initializeing %s.)' % self.name # When this person is created, he/she adds to the population Person.population += 1 def __del__(self): '''I am dying.''' print '%s says bye.' % self.name Person.population -= 1 if Person.population == 0: print 'I am the last one.' else: print 'There are still %d people left.' % Person.population def sayHi(self): '''Greeting by the person. Really, that's all it does.''' print 'Hi, my name is %s.' % self.name def howMany(self): '''Prints the current population.''' if Person.population == 1: print 'I am the only person here.' else: print 'We have %d persons here.' % Person.population swaroop = Person('Swaroop') swaroop.sayHi() swaroop.howMany() kalam = Person('Abdul Kalam') kalam.sayHi() kalam.howMany() swaroop.sayHi() swaroop.howMany()
本例说明了类与对象的变量的本质。population 属于 Person 类,是类的变量;name 变量属于对象(使用 self 赋值),是对象的变量。
两个特殊方法 __init__ 和 __del__ :
(1)__init__() 方法 被称为类的构造函数,用来初始化 Person 实例。self.name 的值根据每个对象指定,表明了它作为对象的变量的本质。
记住,只能使用 self 变量来参考同一个对象的变量和方法,称为 属性参考。
(2)__del__() 方法 被称为类的析构函数,在对象消逝时被调用。对象消逝即对象不再被使用,它所占用的内存将返还给系统。当对象不再使用时, __del__ 方法运行,如果很难保证究竟在 什么时候 运行。用户可以指明它的运行,使用 del 语句。
类的文档字符串可以通过 ClassName.__doc__ 查看:
运行时使用 Person.__doc__ 和 Person.sayHi.__doc__ 分别访问类与方法的文档字符串。
给C++/Java/C#程序员的注释 Python中所有的类成员(包括数据成员)都是 公共的 ,所有的方法都是 有效的 。 只有一个例外:如果你使用的数据成员名称以 双下划线前缀 比如 __privatevar,Python的名称管理体系会有效地把它作为私有变量。 这样就有一个惯例,如果某个变量只想在类或对象中使用,就应该以单下划线前缀。 而其他的名称都将作为公共的,可以被其他类/对象使用。 记住这只是一个惯例,并不是Python所要求的(与双下划线前缀不同)。 同样,注意__del__方法与 destructor 的概念类似。 |
Python对象销毁(垃圾回收)
1. 同Java语言一样,Python使用了引用计数这一简单技术来追踪内存中的对象。
2. 垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。
上述实例中如执行:del u1,则u1对象被销毁,打印:User destroyed
Python 内置类属性
__doc__ 类的文档字符串
__name__ 类名
__module__ 类定义所在的模块(类的全名是 '__main__.className',如果类位于一个导入模块 mymod 中,那么 className.__module__=mymod)
__bases__ 类的所有父类构成元素(包含了一个由所有父类组成的元组)
__dict__ 类的属性(包含一个字典,由类的数据属性组成)
继承
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过 继承 机制。继承完全可以理解成类之间的 类型和子类型 关系。
1、声明类的时候括号中写要继承的父类
2、类的继承衍生出子类,子类可以继承或重写父类的方法,子类可以自定义新的方法或成员变量。
例:教师和学生有共同属性,也有专有属性。可以创建一个共同的类 SchoolMember ,让教师和学生的类 继承 这个类,即它们都是这个类型(类)的子类型,然后再为这些子类型添加专有的属性。
向 SchoolMember 中增加/改变任何功能,会自动反映到子类型中。在子类型中做改动不会影响到别的子类型。子类型在任何需要父类型的场合可以被替换成父类型,即对象可以被视作父类的实例,这种现象被称为多态现象。
SchoolMember 称为 基本类 或 超类;Teacher 和 Student 类被称为 导出类 或 子类。
# -*- coding: utf-8 -*- # Filename: inherit.py class SchoolMember: '''Represents any school member''' def __init__(self,name,age): self.name = name self.age = age print '(Initialized SchoolMember: %s)' % self.name def tell(self): '''Tell my details.''' print 'Name:"%s" Age:"%s"' % (self.name, self.age), class Teacher(SchoolMember): '''Represents a teacher.''' def __init__(self,name,age,salary): SchoolMember.__init__(self,name,age) self.salary = salary print '(Initialized Teacher: %s)' % self.name def tell(self): SchoolMember.tell(self) print 'Salary:"%d"' % self.salary class Student(SchoolMember): '''Represents a student.''' def __init__(self,name,age,marks): SchoolMember.__init__(self,name,age) self.marks = marks print '(Initialized a Student: %s)' % self.name def tell(self): SchoolMember.tell(self) print 'Marks:"%d"' % self.marks t = Teacher('Mrs. Shriry',40,30000) s = Student('Tom',22,75) print # prints a blank line members = [t,s] for member in members: member.tell() # works for both Teacher and Student
为了使用继承,我们把基本类的名称作为一个元组跟在定义类时类名称之后。
基本类的 __init__ 方法专门用 self 变量调用,可以初始化对象的基本类部分
——python 不会自动调用基本类的 constructor,必须专门调用。
在方法调用之前加上类名称前缀,然后把 self 变量及其他参数传递给它。
python 首先查找对应类型的方法,如果不能在导出类中找到对应的方法,才开始到基本类中逐个查找。
基本类在类定义的时候,在元组中指明。如果在继承元组中列出了一个以上的类,称为 多重继承。
Python类私有属性与方法
类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。
在类内部的方法中使用时self.__private_attrs。
类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,不能在类地外部调用。
在类的内部调用self.__private_methods
Python实例方法、类方法、静态方法
实例方法,类方法,静态方法都可以通过实例或者类调用,只不过实例方法通过类调用时需要传递实例的引用(python 3可以传递任意对象,其他版本会报错)
实例方法针对的是实例,第一个参数是self,普通对象方法至少需要一个self参数,代表类对象实例;
类方法针对的是类,@classmethod 它表示接下来的是一个类方法,类方法的第一个参数cls,它们都可以继承和重新定义;
静态方法用于作为程序中的共享资源,直接通过类去调用,不用实例化对象,不需要self参数,可以认为是全局函数,@staticmethod 它表示接下来的是一个静态方法