Task10:类与对象(2day)
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
1.类的实现:
仍以Student类为例,在Python中,定义类是通过class关键字:
>>> class Student(object):
pass
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的:
>>> bart=Student()
>>> bart
<__main__.Student object at 0x0000014C241F26A0>
>>> Student
<class '__main__.Student'>
可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类。
如同C语言一样,类里面一定会有方法,这里把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去:
注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:
class Student(object):
def __init__(self,name,score):
self.name=name
self.score=score
stu1=Student('Bob',80)
print(stu1.name)
print(stu1.score)
Bob
80
这里注意init前后需要加两个下划线,很关键!!!
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
2.类的数据封装
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))
def get_grade(self):
if(self.score>=90):
return 'A'
elif(self.score>=60):
return 'B'
else:
return 'C'
stu1=Student('Bob',80)
stu1.print_score()
print(stu1.get_grade())
Bob:80
B
3.限制访问
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在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))
def get_grade(self):
if(self.score>=90):
return 'A'
elif(self.score>=60):
return 'B'
else:
return 'C'
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__score了:
stu1=Student('Bob',80)
stu1.__name
Traceback (most recent call last):
File "C:/Users/lenovo/AppData/Local/Programs/Python/Python37/class.py", line 17, in <module>
stu1.__name
AttributeError: 'Student' object has no attribute '__name'
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是如果外部代码要获取name和score怎么办?我们参照索引器的思路,可以给Student类增加get_name和get_score这样的方法,如果要从外界改变私有变量的时候我们可以用set方法:
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 set_score(self,score):
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.score = 99也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:
def set_score(self,score):
if(0<=score<=100):
self.__score=score
else:
raise ValueError('wrong score')
需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
4.继承和多态
当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类。
比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印,当我们需要编写Dog和Cat类时,就可以直接从Animal类继承,对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。Cat和Dog类似。
继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:
class Animal(object):
def run(self):
print('Animal is running')
class Dog(Animal):
pass
class Cat(Animal):
pass
dog=Dog()
dog.run()
Animal is running
当然我们也可以复写Dog里面的run()函数:
class Dog(Animal):
def run(self):
print('Dog is running')
Dog is running
这样,我们就获得了继承的另一个好处:多态。
对于多态,我们在这里举一个例子,方便理解:
class Animal(object):
def run(self):
print('Animal is running')
class Dog(Animal):
def run(self):
print('Dog is running')
class Cat(Animal):
pass
def run_twice(animal):
animal.run()
animal.run()
run_twice(Animal())
run_twice(Dog())
run_twice(Cat())
Animal is running
Animal is running
Dog is running
Dog is running
Animal is running
Animal is running
我们首先定义了一个函数,函数所需要的变量要接受一个Animal类型的变量,然后我们定义了Animal、Dog和Cat类分别取实现函数,发现虽然是Animal类型的函数,却也可以实现Dog和Cat变量,再看Dog()的函数结果,调用了Dog自身类里面的run()方法。
多态的好处就是,当我们需要传入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()等函数。