面向过程编程:把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
面向对象编程:把计算机程序视为一组对象的集合,把对象作为程序的基本单元,一个对象包含了数据和操作数据的方法。而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
一 类和实例
二数据封装、继承和多态
三访问权限
四获取对象信息
五实例属性和类属性
一类和实例
类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
这里有个很搞笑的关于类,实例,对象,方法的例子,看了应该很容易理解:
例1:
>>> class Student(object): //类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
... def __init__(self, name, score): //类可以起到模板的作用,因此,可以在创建实例的时候,把一些必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
... self.name = name
... self.score = score
... def print_score(self):
... print('%s: %s' % (self.name, self.score))
... def get_grade(self): //定义一个方法,除了第一个参数是self外,其他和普通函数一样
... if self.score >= 90:
... return 'A'
... elif self.score >= 60:
... return 'B'
... else:
... return 'C'
...
>>> lisa = Student('Lisa', 99) //有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去.
// lisa= Student() 如果没有__init__方法,则可以用直接创建实例,
//lisa.name = ‘Lisa’ 后面一个个绑定属性
//lisa.score = 99
>>> lisa //变量lisa 指向的就是一个Student的实例,后面的0x10ddc9f60是内存地址,每个object的地址都不一样
<__main__.Student object at 0x10ddc9f60>
>>> Student //Student本身则是一个类。
<class '__main__.Student'>
>>> lisa.print_score
<bound method Student.print_score of <__main__.Student object at 0x10ddc9f60>>
>>> lisa.name
'Lisa'
>>> lisa.score
99
>>> lisa.print_score()
Lisa: 99
>>> lisa.get_grade()
'A'
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
二 数据封装、继承和多态
数据封装、继承和多态是面向对象的三大特点。
数据封装:
我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何使用,都是在Student类的内部定义的。类的方法就负责将这些数据和逻辑被“封装”起来了,这些方法是和Student类本身是关联起来的,直接从类的内部定义访问数据。调用很容易,隐藏了复杂的内部逻辑。
继承和多态:
当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
最大的好处是子类获得了父类的全部功能。也可以对子类增加一些方法,
允许我们对代码做一点改进,当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。
例2
>>> class Animal(object):
... def run(self):
... print('Animal is running...')
...
>>> class Dog(Animal):
... pass
...
>>> dog = Dog()
>>> dog.run() //子类获得了父类的全部功能
Animal is running...
>>> class Dog(Animal):
... def run(self): //也可以对子类增加一些方法
... print('Dog is running...')
...
>>> dog = Dog()
>>> dog.run()
Dog is running...
>>> isinstance(dog, Dog) //一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类
True
>>> isinstance(dog, Animal)
True
>>> def run_twice(animal):
... animal.run()
... animal.run()
>>> run_twice(Animal()) //编写一个函数,这个函数接受一个Animal类型的变量
Animal is running...
Animal is running...
>>> run_twice(Dog()) //只要是Animal类或者子类,就会自动调用实际类型的run()方法
Dog is running...
Dog is running...
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
三 访问限制
在上例中定义的Student类,外部代码可以自由的修改实例的属性:
例3
>>> lisa.score = 80
>>> lisa.print_score()
Lisa: 80
可以将属性变为private变量,这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
例4:
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))
def get_name(self): //外部代码要获取name和score的方法
return self.__name
def get_score(self):
return self.__score
def set_score(self, score): //允许外部代码修改score,在方法中,可以对参数做检查,避免传入无效的参数
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
>>> lisa.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
四 获取对象信息
当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?
关于对象类型:
方法一:type()主要可以判断基本类型,例如int, str, chr 等,也可以判断一个变量是指向一个函数或者类。
方法二:isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。(优先使用)
例5:
>>> isinstance(h, Animal)
>>> isinstance(d, Dog) and isinstance(d, Animal)
>>> isinstance([1, 2, 3], (list, tuple))
关于对象方法:
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法
仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
五 实例属性和类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self变量:
例6:
class Student(object):
def __init__(self, name):
self.name = name //self变量
s = Student('Bob')
s.score = 90 //实例变量
类属性:直接在class中定义属性。
例7:
class Student(object):
name = 'Student'
实例属性和类属性的区别:
例8:
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
例9 应用:为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
>>> class Student(object):
... count = 0
... def __init__(self, name):
... self.name = name
... Student.count += 1
...
>>> Student.count
0
>>> bart = Student('Bart')
>>> Student.count
1
>>> bart = Student('Bart')
>>> Student.count
2
分析:
self.count=count+1 ,会报错,因为count不存在,要想得到count值,要用self.count.
如果是用self.count = self.count + 1的话,虽然语法没有问题,但是count每次并不会增加。因为self.count实质上是绑定了一个新的实例属性count,已经与类中原先声明的变量“count”不是同一个了,考察实例属性与类属性区别
注意:在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。