python入门基础总结笔记(5)——面向对象编程

python入门基础总结笔记(5)——面向对象编程

学习采用的是廖雪峰老师的python教程,很精彩详细,收获很大,聊表感谢!原文链接:https://www.liaoxuefeng.com/wiki/1016959663602400

目录

  • 类和实例
  • 访问限制——加下划线变私有变量
  • 继承和多态
  • 获取对象信息——三个函数
  • 实例属性和类属性

定义

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

面向过程 VS 面向对象?
假设我们要处理学生的成绩表,为了表示一个学生的成绩:

  1. 面向过程的程序可以用一个dict表示:
std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }
def print_score(std):
    print('%s: %s' % (std['name'], std['score']))
  1. 面向对象的程序设计思想,应该把Student这种数据类型视为一个对象,这个对象拥有name和score这两个属性:
class Student(object):    #通过class关键字定义“类”

    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()

#结果
Bart Simpson: 59
Lisa Simpson: 87

1.类和实例

在Python中,定义类是通过class关键字:

class Student(object):
    pass

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object)

  • 实例
    通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去:
class Student(object):

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

注意:特殊方法“init”前后分别有两个下划线!!!

__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:

>>> bart = Student('Bart Simpson', 59)  #self不用传,bart即self
>>> bart.name
'Bart Simpson'
>>> bart.score
59
  • 数据封装
    从外部看Student类,创建实例需要给出namescore,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;

2.访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,就变成了一个私有变量(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))
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name     #无法通过bart.__name 访问内部
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

已经无法从外部访问实例变量.__name和实例变量.__score了,这样外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name和get_score这样的方法:

class Student(object):
    ...
    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

如果又要允许外部代码修改score怎么办? 可以再给Student类增加set_score方法:

class Student(object):
    ...
    def set_score(self, score):   #可以对参数做检查,避免传入无效的参数
        if 0 <= score <= 100:
            self.__score = score   ###
        else:
            raise ValueError('bad score')

练习
请把下面的Student对象的gender字段对外隐藏起来,用get_gender()和set_gender()代替,并检查参数有效性:

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):
        if gender=='male' or gender=='female':
          self.__gender=gender
        else:
          print('wrong gender!')


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

运行结果:

测试成功!
wrong gender!

3.继承和多态

下面编写了一个名为Animal的class,有一个run()方法可以直接打印:

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

当我们需要编写Dog和Cat类时,就可以直接从Animal类继承

class Dog(Animal):   #Animal是Dog的父类,Dog是子类
    pass
class Cat(Animal):   #Cat是子类
    pass

dog = Dog()     #Dog和Cat作为Animal的子类,就自动拥有了run()功能
dog.run()
cat = Cat()
cat.run()

#结果
Animal is running...
Animal is running...

为了显示 Dog is running… 和 Cat is running…,因此,对Dog和Cat类改进如下:

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

#运行结果
Dog is running...
Cat is running...

当子类和父类都存在相同的run()方法时,子类的run()覆盖了父类的run(),这也是继承的一个好处:多态

要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

def run_twice(animal):    #animal小写,接收Animal类型变量
    animal.run()
    animal.run()

>>> run_twice(Animal())  #传入Animal实例
Animal is running...
Animal is running...

>>> run_twice(Dog())    #传入Dog实例
Dog is running...
Dog is running...

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定。

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写

4.获取对象信息

4.1 type()函数

基本对象类型都可以用type()函数判断:

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

比较两个变量类型是否相同:

>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

4.2 isinstance()函数

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。

如果继承关系是:object -> Animal -> Dog -> Husky

isinstance()就可以告诉我们,一个对象是否是某种类型。

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky)   #h变量指向的就是Husky对象
True
>>> isinstance(h, Dog)     #h也还是Dog类型
True
>>> isinstance(h, Animal)  #h也还是Animal类型
True
>>> isinstance(d, Husky)    #但d(Dog)不属于Husky类型
False

能用type()判断的基本类型也可以用isinstance()判断:

>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:

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

4.3 dir() 函数

使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

其他:

>>> len('ABC')    #len求对象长度
3
>>> 'ABC'.__len__()
3

>>> 'ABC'.lower()   #小写化字符串
'abc'

5.实例属性和类属性

由于Python是动态语言,根据类创建的实例可以任意绑定属性。

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

s = Student('Bob')
s.score = 90

如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归Student类所有:

>>> class Student(object):
...     name = 'Student'     ###定义类属性name
...

>>> 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类增加一个类属性,每创建一个实例,该属性自动增加:

class Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        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('测试通过!')

#结果
Students: 2
测试通过!

小结:

1.实例属性属于各个实例所有,互不干扰;
2.类属性属于类所有,所有实例共享一个属性;
3.不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值