5.面向对象编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
在Python中,定义类是通过class关键字,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的
创建实例是通过类名+()实现
Python与静态语言不同的是可以自由地给一个实例变量绑定属性,两个实例变量可能都是同一个类的不同实例,但拥有的属性名称都可以不同
通过定义一个特殊的__init__方法,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数

# 定义类
class Student(object):
    pass
# 创建实例
bart = Student()
# 自由地给一个实例变量绑定属性
bart.name = 'Bart Simpson'

# 定义可强制绑定属性的类
class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score
# 创建带有属性的类
bart = Student('Bart Simpson', 59)

数据封装

在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'

访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮
如果外部代码要获取和修改属性,可以增加getter和setter
在Python中,变量名类似__xxx__,以双下划线开头并且以双下划线结尾的是特殊变量,特殊变量是可以直接访问的,不是private变量
以一个下划线开头的实例变量名按照约定俗成的规定“视为私有变量”
双下划线开头的实例变量也可以通过_(Classname)__(privateproperty)的形式访问,如可以通过_Student__name来访问__name变量,但是强烈不建议这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。
总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。

练习

请把下面的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):
        if (gender=='male') or (gender=='female'):
            self.gender = gender
        else:
            print('Illegal Argument!')

继承和多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
继承获得了父类的所有功能,并且可以实现多态
对于一个变量,我们只需要知道它的父类类型,无需确切地知道它的子类型,就可以放心地调用方法,而具体调用的方法是作用在哪一个对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种父类的子类时,只要确保方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增子类;
对修改封闭:不需要修改依赖父类类型的方法函数

鸭子(duck)类型

动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的,不用传入父类类型,只要传入的对象有一个所必须的方法即可

获取对象信息

获取对象的类型和方法:使用type()函数判断对象类型,对于class的继承关系可以使用isinstance()函数
总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”
要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

>>> dir('123')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果调用len()函数试图获取一个对象的长度,实际上在len()函数内部会自动去调用该对象的__len__()方法
配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:has判断有没有该属性,get获得该属性,set设置属性

>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()
# getattr()、setattr()以及hasattr()的用法
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

# 如果试图获取不存在的属性,会抛出AttributeError的错误
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'

# 可以传入一个default参数,如果属性不存在,就返回默认值
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

# 获取属性方法
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81

只有在不知道对象信息的时候,我们才会去获取对象信息
假设希望从文件流fp中读取图像,首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。
请注意,在Python这类动态语言中,根据鸭子类型,有read()方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()方法返回的是有效的图像数据,就不影响读取图像的功能。

实例属性和类属性

Python是动态语言,根据类创建的实例可以任意绑定属性。给实例绑定属性的方法是通过实例变量,或者通过self变量
如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归Student类所有,当定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。类似于Java中的static member
在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

class Student(object):
    name = 'Student' # 类属性
练习

为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:

# -*- coding: utf-8 -*-
class Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        Student.count = Student.count + 1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值