文章目录
- 类和实例
- 数据封装
- 访问限制
- 练习:请把下面的Student对象的gender字段对外隐藏起来,用get_gender()和set_gender()代替,并检查参数有效性:
类和实例
类(Class)和实例(Instance)是面向对象最重要的概念。
类,是抽象的模版(下面的Student类,)
实例,是根据类创建出来的具体的
Student类为例,定义类:
1 2 3 4 5 | '用class定义类' class Student(object): pass 'class后面是类名(Student),类名通常是以大写开头的单词,紧接着是(object), 表示该类是从哪个类集成下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。' |
定义好了
1 2 3 4 5 | >>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class '__main__.Student'> |
变量
1 2 3 | '可以自由的给每一个实例变量绑定属性' bart.name = 'Fan' print(bart.name)--->'Fan' |
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的
1 2 3 4 5 | class Student(object): # 注意:特殊方法“__init__”前后分别有两个下划线!!! def __init__(self, name, score): self.name = name self.score = score |
注意到
有了
1 2 3 | bart = Student('Bart Simpson', 59) print(bart.name)--->'Bart Simpson' print(bart.score)--->59 |
普通的函数相比:
在类
数据封装
面向对象编程的一个重要特点就是数据封装。在上面的
1 2 3 4 5 | >>> def print_score(std): ... print('%s: %s' % (std.name, std.score)) ... >>> print_score(bart) Bart Simpson: 59 |
但是,既然
1 2 3 4 5 6 7 8 | 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)) |
要定义一个方法,除了第一个参数是
1 2 | bart = Student('Fan',85) bart.print_score()--->Fan:85 |
这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
封装的另一个好处是可以给Student类增加新的方法,比如get_grade:
1 2 3 4 5 6 7 8 9 10 | class Student(object): ... def get_grade(self): if self.score >= 90: return 'A' elif self.score >= 60: return 'B' else: return 'C' |
1 2 3 4 | lisa = Student('Lisa', 99) bart = Student('Bart', 59) print(lisa.name, lisa.get_grade()) print(bart.name, bart.get_grade()) |
访问限制
在
但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的
1 2 3 4 5 | >>> bart = Student('Bart Simpson', 59) >>> bart.score 59 >>> bart.score = 99 >>> bart.score--->99 |
如果要让内部属性不被外部访问,可以
所以,我们把Student类改一改:
1 2 3 4 5 6 7 8 9 | class Student(object): def __init__(self, name, score): '注意这里__name是双下划线' self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score)) |
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__score了:
1 2 3 4 5 | >>> bart = Student('Bart Simpson', 59) >>> bart.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name' |
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是如果**外部代码要获取name和score怎么办?**可以给
1 2 3 4 5 6 7 8 | class Student(object): ... def get_name(self): return self.__name def get_score(self): return self.__score |
如果又要允许外部代码修改
1 2 3 4 5 | class Student(object): ... def set_score(self, score): self.__score = score |
你也许会问,原先那种直接通过
bart.score = 99 也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:
1 2 3 4 5 6 7 8 | class Student(object): ... def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score') |
需要注意的是,在Python中,变量名类似
__xxx__ 的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__ 这样的变量名。
当看到以一个下划线开头的
双下划线开头的实例变量是不是一定不能从外部访问呢?
其实也不是。不能直接访问
1 2 | >>> bart._Student__name 'Bart Simpson' |
但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。
总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。
最后注意下面的这种错误写法:
1 2 3 4 5 6 | >>> bart = Student('Bart Simpson', 59) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 设置__name变量! >>> bart.__name 'New Name' |
表面上看,外部代码“成功”地设置了
也就是说使用
1 2 | >>> bart.get_name() # get_name()内部返回self.__name 'Bart Simpson' |
练习:请把下面的Student对象的gender字段对外隐藏起来,用get_gender()和set_gender()代替,并检查参数有效性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Student(object): def __init__(self, name, gender): self.name = name self.__gender = gender def get_gender(self): return self.__gender #set_gender()传入两个参数,对想要修改的私有属性进行修改 def set_gender(self,gender): if (gender.lower() =='male' or gender.lower() == 'female'): self.__gender=gender else : raise ValueError('bad value') A = Student('Fan','Female') A.set_gender('Male') print(A.get_gender()) |