python 面向对象

什么是面向对象?

哈哈,其实此对象非彼对象,程序中的面向对象可不是这样,假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个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)
bart.print_score()

面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student。

类和实例

在 Python 中,定义类是通过 class 关键字:

class Student(object):
    pass

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

有了init方法,在创建实例的时候,就不能传入空的参数了,必须传入与init方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。

数据封装

面向对象的数据封装是指我们只需要调用其类的方法,而不需要知道其具体的内部实现。

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_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >=60:
            return 'B'
        else:
            return 'C'



bart = Student('Bart Simpson',59)
print(bart.get_grade())

访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

def __init__(self,name,score):
    self.__name = name
    self.__score = score



def print_score(self):
    print('%s: %s' %(self.name,self.score))

改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.name和实例变量.score了,如果需要访问需要增加2个方法

class Student(object):


    def __init__(self,name,score):
        self.__name = name
        self.__score = score


    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score
    def print_score(self):
        print('%s: %s' %(self.name,self.score))


    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >=60:
            return 'B'
        else:
            return 'C'



bart = Student('Bart Simpson',59)
print(bart.get_name())

如果要允许修改 score 呢

class Student(object):


    def __init__(self,name,score):
        self.__name = name
        self.__score = score


    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score
    def print_score(self):
        print('%s: %s' %(self.name,self.score))

    def set_score(self,score):
        self.__score = score

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >=60:
            return 'B'
        else:
            return 'C'



bart = Student('Bart Simpson',59)
print(bart.get_name())
bart.set_score(60)
print(bart.get_score())    

则输出

你也许会问,原先那种直接通过bart.score = 99也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

class Student(object):
……

    def set_score(self,score):
        if 0<=score<=100:
            self.__score = score
        else:
            raise ValueError('bad score')



bart = Student('Bart Simpson',59)
print(bart.get_name())
bart.set_score(101)
print(bart.get_score())

需要注意的是,在Python中,变量名类似xxx的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用namescore这样的变量名。

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问name是因为Python解释器对外把name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

>>> bart._Student__name
'Bart Simpson'