【廖雪峰python教程学习】——(五)面向对象编程

本文是关于Python面向对象编程的学习笔记,涵盖了类和实例的概念、访问限制、继承和多态,以及如何获取对象信息。强调了类的抽象模板特性、私有变量的保护以及面向对象的开闭原则。此外,还讨论了实例属性和类属性的区别,以及在实际编程中应注意的事项。
摘要由CSDN通过智能技术生成


前言

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。


一、类和实例

牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

通过class关键字,定义Student

class Student(object):
    pass

根据Student类创建出Student的实例,创建实例是通过类名+()实现的:

>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。

class Student(object):

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

方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。

二、访问限制

在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问.

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,可以给Student类增加get_nameget_score这样的方法;如果要允许外部代码修改score,再给Stuednt类增加set_score方法:
在方法中,可以对参数做检查,避免传入无效的参数

class Student(object):
    ...

    def get_name(self):
        return self.__name

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

在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的
注意错误!!!

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

表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。

>>> bart.get_name() # get_name()内部返回self.__name
'Bart Simpson'

练习

class Student(object):
    def __init__(self, name, gender):
        self.__name = name
        self.__gender = gender
    def get_gender(self):
        return self.__gende
    def set_gender(self, gender):
        if gender == 'male' or gender == 'female':
                self.__gender = gender
            else:
                print("Unexpected input!")

三、继承和多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

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

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

继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:

dog = Dog()
dog.run()

cat = Cat()
cat.run()

可以对子类增加一些方法,比如Dog类:

class Dog(Animal):

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

    def eat(self):
        print('Eating meat...')

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态

当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样

a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型

可以用isinstance()判断一个变量是否是某个类型

>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
>>> isinstance(c, Animal)
True

在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类

def run_twice(animal):
    animal.run()
    animal.run()
>>> run_twice(Animal())
Animal is running...
Animal is running...
>>> run_twice(Cat())
Cat is running...
Cat is running...
>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...

新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

开闭原则
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
继承还可以一级一级地继承下来
在这里插入图片描述
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

四、获取对象信息

使用type()判断对象类型

判断对象类型,使用type()函数,返回对应的Class类型

>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:

>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

使用isinstance()

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。
继承关系:object -> Animal -> Dog -> Husky
isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>>> isinstance(h, Husky)
True

能用type()判断的基本类型也可以用isinstance()判断,并且还可以判断一个变量是否是某些类型中的一种
下面的代码就可以判断是否是list或者tuple:

>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

使用dir()

dir()函数,它返回一个包含字符串的list

五、实例属性和类属性

Python是动态语言,根据类创建的实例或者self变量可以任意绑定属性:

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

s = Student('Bob')
s.score = 90
#根据self变量
class Student(object):
    name = 'Student'

定义一个类属性,虽然此属性归类所有,但是类的所有实例都可以访问到。

>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

总结

千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

练习

# 为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
# -*- coding: utf-8 -*-
class Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        Student.count = Student.count + 1
        self.count = Student.count
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值