Task10:类与对象(2day)

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()等函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值