Python学习笔记七 面向对象编程

参考教程:廖雪峰官网https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000

面向对象编程

面向对象编程——Object Oriented Programming ,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

通过一个实例来对比面向过程和面向对象程序的区别。

例如要处理学生的成绩,按面向过程的方式,则需要先定义学生的成绩,再通过一个函数把信息打印出来:

std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }
def print_score(std):
    print('%s: %s' % (std['name'], std['score']))

print_score(std1)
print_score(std2)

如果采用面向对象的程序设计思想,思考的侧重点不是程序的执行流程。首先对于学生的分数、姓名的处理可以通过设计一种数据类型去实现,这种数据类型拥有姓名、分数这两个属性,其实这就对应了对象和属性的特性,并且我们可以给这个对象设计其特有的函数(方法)。

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))
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

对于上例,Student就是一个类(class),而bart和lisa就是这个类的两个实例(instance)对象。

一、类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),比如上例中的Student就是一个类,而其中bart和lisa就是依据这个类创建出来的具体对象,即实例。

类名通常用大写字母开头表示,参数表示这个类是从哪个类继承下来的,如果没有合适的继承类,就使用object类,object类是所有类都会继承的类。类的实例通过“类名()"创建。

类的定义中可以通过__init__方法给类创建其属性,如:

#第一个参数表示创建的实例本身
def __init__(self,name,score):
      self.name=name
      self.score=score

而在创建实例的时候,不需要传递第一个self参数,Python解释器会自行把实例变量传进去:

bar=Student('Bart Simpson',60)

通过创建上面的实例对象bart之后,就可以用bart.name/bart.score去访问该实例的属性。

而对于类来说,属性的访问,包括一些常用的操作都可以在类定义代码中统一实现,这也称为类的方法。如给上面的Student类创建一个输出其属性的方法:

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

在实例对象调用该方法的时候也不需要传递self参数:

bart.print_score()

需要注意的是,和静态语言不同,Python允许对实例对象绑定任何数据:

#创建两个Student类的实例
bart=Student('Bart',60)
lisa=Student('Lisa',80)
#给bart绑定age属性
bart.age=12
#输出12
print(bart.age)
#报错,提示Student类没有age属性
print(lisa.age)

二、访问限制

为保证属性不被外部访问,可以在类的定义中对于需要保护的属性名前面加上'__',这样这个属性就变成了私有变量(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))

修改后就无法访问实例对象的__name和__score属性了。

如果外部需要访问这两个属性,那可以增加get_name和get_score方法:

def get_name(self):
        return self.__name

def get_score(self):
        return self.__score

如果更进一步还可以让外部通过一些特定的方法可以修改属性值,那么就需要给类增加set方法:

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

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

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

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

另外对于上例中的私有变量__name和__score要记得它们实质上的名称是_Student__name和_Student__score;所以在外部对实例对象设置__name是可行的,但只是给该实例增加了一个名为__name的属性。而通过类方法get_name()可以发现实际属性值并没有变化。

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'
>>> bart.get_name() # get_name()内部返回self.__name
'Bart Simpson

练习:

'''
请把下面的Student对象的gender字段对外隐藏起来
用get_gender()和set_gender()代替,并检查参数有效性:
'''
# -*- coding: utf-8 -*-
class Student(object):
    def __init__(self, name, gender):
        self.name = name
        self.__gender = gender
    def get_gender(self):
        return self.__gender
    def set_gender(self,gender):
        self.__gender=gender

# 测试:
bart = Student('Bart', 'male')
if bart.get_gender() != 'male':
    print('测试失败!')
else:
    bart.set_gender('female')
    if bart.get_gender() != 'female':
        print('测试失败!')
    else:
        print('测试成功!')

 三、继承和多态

在OOP程序设计中,在定义一个类的时候,可以在参数中指定这个类所继承的已存在的类,比如之前的Student继承了object类。新定义的类称为子类(Subclass),而被继承的类称为基类、父类或超类(Base class、Super class)。

比如已有如下的一个Animal类:

class Animal(object):
    def run(self):
        print('Animal is running...')

我们可以编写其他的类作为这个Animal类的子类,只需要在定义类时将参数设为'Animal':

class Dog(Animal):
    pass
class Cat(Animal):
    pass

Dog().run()
mycat=Cat()
mycat.run()

这样Dog和Cat类也自动拥有了run()这个方法。这里Cat和Dog类就是Animal的子类,而Animal就是它们的父类。

但我们也可以发现,在Dog和Cat的实例对象调用run()时候,输出的内容仍然是父类定义的'Animal is running...',这里我们也可以给Dog类和Cat类设置同名的run()方法:

class Dog(Animal):
    def run(self):
        print('Dog is running...')
class Cat(Animal):
    def run(self):
        print('Cat is running...')

Dog().run()
mycat=Cat()
mycat.run()

这样输出就变成了:

Dog is running...
Cat is running...

当子类和父类拥有同名方法时候,在子类中的方法就“覆盖”了父类的方法,在子类的实例对象调用这个方法时也就直接使用了子类定义的方法。

当定义了一个类的时候,其实在当前的Python运行环境里就是多了一种数据类型,这可以通过isinstance()判断:

print(isinstance(10,int))
print(isinstance('abc',str))
print(isinstance(Dog(),Animal))
print(isinstance(Cat(),Animal))
print(isinstance(Dog(),Dog))
print(isinstance(Cat(),Cat))

'''
输出:
True
True
True
True
True
True
'''

但要注意下面这种情况:

#对于通过父类创建的实例对象
#其数据类型是不属于该父类的子类的

#False
print(isinstance(Animal(),Dog))

而对于子类创建的实例对象,其数据类型既是子类,也可以是父类。这就是多态的一种理解。

要充分理解多态的好处,可以看下面这个例子:

def run_twice(animal):
    animal.run()
    animal.run()

#当传入Animal类的实例时:
run_twice(Animal())
'''输出:
Animal is running...
Animal is running...
'''
#当传入Dog类或Cat类的实例时:
run_twice(Dog())
'''输出:
Dog is running...
Dog is running...
'''
#如果新增一个类
class Sheep(Animal):
    pass
run_twice(Sheep())
'''输出:
Animal is running...
Animal is running...
'''

#我们可以发现并不需要修改run_twice方法,就可以引用sheep的实例
#这是因为run_twice方法接收的是Animal类型的数据,而sheep是其子类
#更进一步的实质是:Animal类型具备run方法,所以其子类都有这个方法
#所以实质上在动态语言中,对于run_twice这样的方法只需要参数具备run方法即可,如下实例

class X(object):
    def run(self):
        print('X is running')

run_twice(X())
'''输出:
X is running
X is running
'''

四、实例属性和类属性

class Student(object):
    def __init__(self,name):
        self.name=name

s=Student('bob')
#输出'bob'
print(s.name)
s.score=100
#输出100
print(s.score)
#报错:Student类没有score属性
print(Student('Aly').score)

除了在类的__init__()方法中给类设置属性外,也可以在类的定义中直接设置:

class Student(object):
    def __init__(self,name):
        self.name=name
    score=98

#可以直接通过类名访问score属性
print(Student.score)
#也可以通过类的实例访问
print(Student('Bob').score)

此时如果给类的实例对象赋予一个同名的属性,则会覆盖掉类的属性:

anny=Student('Anny')
anny.score=100
print(anny.score)  #输出100,此时类属性score被覆盖
henry=Student('Henry')
print(henry.score)  #输出98,此时为继承类的属性

#给henry增加score属性
henry.score=99
print(henry.score)  #输出99,此时类属性score被覆盖

#实例中增加的属性可以用del删除
del henry.score
print(henry.score)  #输出98,此时又继承了类的属性

练习:

'''
为了统计学生人数,可以给Student类增加一个类属性
每创建一个实例,该属性自动增加
'''
class Student(object):
    count=0
    def __init__(self,name):
        self.__name=name
        self.set_count()
    def set_count(self):
        Student.count=Student.count+1
        
# 测试:
if Student.count != 0:
    print('测试失败!')
else:
    bart = Student('Bart')
    if Student.count != 1:
        print('测试失败!')
    else:
        lisa = Student('Bart')
        if Student.count != 2:
            print('测试失败!')
        else:
            print('Students:', Student.count)
            print('测试通过!')

        
原文地址:https://www.cnblogs.com/tsembrace/p/8605073.html