Python随学随记(4)
来源:互联网 发布:传奇霸业翅膀升级数据 编辑:程序博客网 时间:2024/05/01 15:30
面向对象编程(Object Oriented Programming )
简称OOP,是一种程序设计思想,OPP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行
为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象
自定义的对象数据类型就是面向对象中的类(Class)的概念
举个栗子:
处理一个成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict
表示:
std1 = { 'name': 'Michael', 'score': 98 }std2 = { 'name': 'Bob', 'score': 81 }
而处理学生成绩可以通过函数实现,比如打印学生的成绩:
def print_score(std): print '%s: %s' % (std['name'], std['score'])
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student
这种数据类型应该被视为一个对象,这个对象拥有name
和score
这两个属性(Property)
如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score
消息,让对象自己把自己的数据打印出来:
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print '%s: %s' % (self.name, self.score)
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)
面向对象的程序写出来就像这样:
bart = Student('Bart Simpson', 59)lisa = Student('Lisa Simpson', 87)bart.print_score()lisa.print_score()
类(Class)和实例(Instance)的概念是很自然的。
Class是一种抽象概念,比如我们定义的Class——Student
,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法
- 数据封装、继承和多态是面向对象的三大特点
类和实例
类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同
定义类是通过class
关键字:
class Student(object): pass
class
后面紧接着是类名,即Student
,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的
通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类
根据Student
类创建出Student的实例:
>>> bart = Student()>>> bart<__main__.Student object at 0x10a67a590>>>> Student<class '__main__.Student'>
可以看到,变量bart
指向的就是一个Student
的object
,后面的0x10a67a590
是内存地址,每个object
的地址都不一样,而Student
本身则是一个类。
可以自由地给一个实例变量绑定属性:
>>> bart.name = 'Bart Simpson'>>> bart.name'Bart Simpson'
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。
通过定义一个特殊的__init__
方法,在创建实例的时候,就把name
,score
等属性绑上去:
class Student(object): def __init__(self, name, score): self.name = name self.score = score
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身
>>> bart = Student('Bart Simpson', 59)>>> bart.name'Bart Simpson'>>> bart.score59
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self
,并且调用时,不用传递该参数,
除此之外,类的方法和普通函数没有什么区别,仍然可以用默认参数、可变参数和关键字参数
数据封装
在上面的Student
类中,每个实例就拥有各自的name
和score
这些数据
我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
>>> def print_score(std):... print '%s: %s' % (std.name, std.score)...>>> print_score(bart)Bart Simpson: 59
既然Student
实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student
类的内部定义访问数据的函数,这样,就把“数据”给封装起来了
这些封装数据的函数是和Student
类本身是关联起来的,我们称之为类的方法:
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print '%s: %s' % (self.name, self.score)
这样一来,我们从外部看Student
类,就只需要知道,创建实例需要给出name
和score
,而如何打印,都是在Student
类的内部定义的
这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节
封装的另一个好处是可以给Student
类增加新的方法,比如get_grade
:
class Student(object):... def get_grade(self): if self.score >= 90: return 'A' elif self.score >= 60: return 'B' else: return 'C'
直接在实例变量上调用:
>>> bart.get_grade()'C'
类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
>>> bart = Student('Bart Simpson', 59)>>> lisa = Student('Lisa Simpson', 87)>>> bart.age = 8>>> bart.age8>>> lisa.ageTraceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: 'Student' object has no attribute 'age'
访问限制
从前面Student
类的定义来看,外部代码还是可以自由地修改一个实例的name
、score
属性
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student
类改一改:
class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print '%s: %s' % (self.__name, self.__score)
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name
和实例变量.__score
了
但是如果外部代码要获取name
和score
怎么办?可以给Student
类增加get_name
和get_score
这样的方法:
class Student(object): ... def get_name(self): return self.__name def get_score(self): return self.__score
如果又要允许外部代码修改score
怎么办?可以给Student
类增加set_score
方法:
class Student(object): ... def set_score(self, score): self.__score = score
为什么要定义一个方法大费周折?
因为在方法中,可以对参数做检查,避免传入无效的参数:
class Student(object): ... def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score')
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是
不能直接访问__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量:
>>> bart._Student__name'Bart Simpson'
但是强烈不建议这么干,因为不同版本的Python解释器可能会把__name
改成不同的变量名
总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉
继承和多态
当我们定义一个class
的时候,可以从某个现有的class
继承,新的class
称为子类(Subclass)
而被继承的class
称为基类、父类或超类(Base class、Super class)
继承有什么好处?最大的好处是子类获得了父类的全部功能
当然,也可以对子类增加一些方法
class Animal(object): def run(self): print 'Animal is running...'class Dog(Animal): def run(self): print 'Dog is running...'class Cat(Animal): def run(self): print 'Cat is running...'
当子类和父类都存在相同的run()
方法时,我们说,子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
这样,我们就获得了继承的另一个好处:多态
要理解什么是多态,我们首先要对数据类型再作一点说明
当我们定义一个class
的时候,我们实际上就定义了一种数据类型
我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:
a = list() # a是list类型b = Animal() # b是Animal类型c = Dog() # c是Dog类型>>> isinstance(a, list)True>>> isinstance(b, Animal)True>>> isinstance(c, Dog)True>>> isinstance(c, Animal)True
看来c
不仅仅是Dog
,c
还是Animal
当我们创建了一个Dog
的实例c
时,我们认为c
的数据类型是Dog
没错,但c
同时也是Animal
也没错,Dog
本来就是Animal
的一种
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类,但是,反过来就不行
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal
类型的变量:
def run_twice(animal): animal.run() animal.run()
当我们传入Animal
的实例时,run_twice()
就打印出:
>>> run_twice(Animal())Animal is running...Animal is running...
当我们传入Dog
的实例时,run_twice()
就打印出:
>>> run_twice(Dog())Dog is running...Dog is running...
现在,如果我们再定义一个Tortoise
类型,也从Animal
派生:
class Tortoise(Animal): def run(self): print 'Tortoise is running slowly...'
当我们调用run_twice()
时,传入Tortoise
的实例:
>>> run_twice(Tortoise())Tortoise is running slowly...Tortoise is running slowly...
实际上,任何依赖Animal
作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态
多态的好处就是,当我们需要传入Dog
、Cat
、Tortoise
……时,我们只需要接收Animal
类型就可以了
因为Dog
、Cat
、Tortoise
……都是Animal
类型,然后,按照Animal
类型进行操作即可
由于Animal
类型有run()
方法,因此,传入的任意类型,只要是Animal
类或者子类,就会自动调用实际类型的run()
方法
对于一个变量,我们只需要知道它是Animal
类型,无需确切地知道它的子类型,就可以放心地调用run()
方法,而具体调用的run()
方法是作用在Animal
、Dog
、Cat
还是Tortoise
对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:
调用方只管调用,不管细节,而当我们新增一种Animal
的子类时,只要确保run()
方法编写正确,不用管原来的代码是如何调用的
这就是著名的:
“开闭”原则
对扩展开放:允许新增Animal
子类
对修改封闭:不需要修改依赖Animal
类型的run_twice()
等函数
继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系
而任何类,最终都可以追溯到根类object
,这些继承关系看上去就像一颗倒着的树, 比如如下的继承树:
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写
有了继承,才能有多态
在调用类实例方法的时候,尽量把变量视作父类类型,这样,所有子类类型都可以正常被接收
旧的方式定义Python类允许不从object类继承,但这种编程方式已经严重不推荐使用,任何时候如果没有合适的类可以继承,就继承自object
类
获取对象信息的方法
type( )
: type()
函数返回的是type类型, 如果我们要在if
语句中判断,就需要比较两个变量的type类型是否相同:
>>> type(123)==type(456)True>>> type('abc')==type('123')True>>> type('abc')==type(123)False
Python把每种type类型都定义好了常量,放在types
模块里,使用之前,需要先导入:
>>> import types>>> type('abc')==types.StringTypeTrue>>> type(u'abc')==types.UnicodeTypeTrue>>> type([])==types.ListTypeTrue>>> type(str)==types.TypeTypeTrue
最后注意到有一种类型就叫TypeType
,所有类型本身的类型就是TypeType,比如:
>>> type(int)==type(str)==types.TypeTypeTrue
isinstance( )
: 作用是判断class
的类型, 能用type()
判断的基本类型也可以用isinstance()
判断:
>>> isinstance('a', str)True>>> isinstance(u'a', unicode)True>>> isinstance('a', unicode)False
isinstance()
判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上
dir( )
:获得一个对象的所有属性和方法,可以使用dir()
函数,它返回一个包含字符串的list,比如,获得一个str
对象的所有属性和方法:
>>> dir('ABC')['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
在Python中,如果你调用len()
函数试图获取一个对象的长度
实际上,在len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:
>>> len('ABC')3>>> 'ABC'.__len__()3
仅仅把属性和方法列出来是不够的,配合getattr()
、setattr()
以及hasattr()
,我们可以直接操作一个对象的状态:
>>> class MyObject(object):... def __init__(self):... self.x = 9... def power(self):... return self.x * self.x...>>> obj = MyObject()
接着可以测试该对象的属性:
>>> hasattr(obj, 'x') # 有属性'x'吗?True>>> obj.x9>>> hasattr(obj, 'y') # 有属性'y'吗?False>>> setattr(obj, 'y', 19) # 设置一个属性'y'>>> hasattr(obj, 'y') # 有属性'y'吗?True>>> getattr(obj, 'y') # 获取属性'y'19>>> obj.y # 获取属性'y'19
如果试图获取不存在的属性,会抛出AttributeError
的错误
也可以传入一个default
参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404404>>> hasattr(obj, 'power') # 有属性'power'吗?True>>> getattr(obj, 'power') # 获取属性'power'<bound method MyObject.power of <__main__.MyObject object at 0x108ca35d0>>>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn>>> fn # fn指向obj.power<bound method MyObject.power of <__main__.MyObject object at 0x108ca35d0>>>>> fn() # 调用fn()与调用obj.power()是一样的81
- Python随学随记(4)
- Python随学随记(1)
- Python随学随记(2)
- Python随学随记(3)
- Python随学随记(5)
- Python随学随记(6)
- Python随学随记(7)
- Python随学随记(8)
- 本办法学Python 随记1
- 本办法学python 随记2
- Spring知识点 随学随记
- NSStirng和NSNumber随学随记
- java-随学随记之基础篇
- 随学随记之java的数据类型
- Python随记(二)字符串
- Python随记(三)字典
- Python 随记
- Python学习随记(持续更新)
- jquery遍历函数.li ???
- hdoj 5773 <变相LIS>
- Rails 中的持久化框架 Active recored
- poj 1789 Truck History
- Android Studio does not detect .aidl files
- Python随学随记(4)
- Hdu 5765 Bonds
- 人生十难
- vs2013编译FFMPEG. 连接错误问题.
- Java设计模式--单例模式
- ubuntu 下如何以root身份登录
- 二分+dp,在一个地方上卡了2小时
- 机器学习领域的五大流派
- 有两个32bit的数A、B,使用下面方式得到32bit的数C、D。哪一种可以使用C、D得到A、B的值?