python学习笔记-面向对象编程-20200319

面向对象编程

面向对象的设计思想是抽象出Class,根据Class创建Instance。
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

定义类

通过class关键字:

class Student(object):
    pass
  • class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
创建实例
  • 定义好Student类后,就可以根据Student类创建出Student的实例,创建实例是通过**类名+()**来实现:
bart=Student()
  • 可以自由地给一个实例变量绑定属性,如给实例bart绑定一个name属性:
bart.name='Bart Simpson'
  • 类可以起到模板的作用,so在创建实例的时候可以强制填写一些必须绑定的属性,如:通过定义__init__的方法,在创建实例时把name和score属性绑定上去:
class Student(object):
    def __int__(self,name,score):
        self.name=name
        self.score=score

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

bart=Student('Bart Simpson',59)
  • 类中定义的函数的第一个参数永远是实例变量self,并且,调用时,不用传递该参数。
数据封装

使用类的方法:

# define classify
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))
        
# using 
bart=Student('Bart Simpson',59)
bart.print_score()
  • 定义时:除了第一个参数是self外,其他和普通参数一样
  • 调用时:只要在实例变量上直接调用,除了self不用传递,其他参数正常传入
封装可以给类增加新的方法

如get_grade

class Student(object):
    ...
    
    def get_grade(self):
        if self.score>=99:
            return 'A'
        elif self.score>=60:
            return 'B'
        else:
            return 'C'
小结
  • 类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
  • 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
  • 在实例上调用方法,就直接操作了对象内部的数据,但无需知道方法内部的实现细节;
  • Python允许对实例变量绑定任何数据—对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
# 变量1
bart = Student('Bart Simpson', 59)
# 变量2
bart.age = 8  -->此时bart的age为8

访问限制

  • 从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性

1. 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
2. 实例的变量名如果以__开头,就变成了一个私有变量(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了。
3. 外部代码要获取内部私有变量
如果外部代码要获取name和score,可以给Student类增加get_name和get_score这样的方法:

class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score
  • 此时,只要调用bart.get_name()就可以直接获取bart的name
    4. 允许外部代码修改score
    给类增加set_score方法:
class Student(object):
    ...
    
    def set_score(self,score):
        self.__score=score

这种方法可以对参数做检查,避免传入无效参数,对上面代码修改如下:

class Student(object):
    ...

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')
  • 注:变量名类似__xxx__的是特殊变量,可以直接访问,而不是私有变量,因此不能用__name__这样的变量名
  • 但下划线变量名如_name是实例变量名,外部可以访问,但约定俗成把这类变量看作私有变量
  • 双下划线开头的私有变量可以通过‘_类名__变量名’来进行访问,但一般不要这么干
  • 不要通过bart.__name = 'New Name'这样的方式修改变量
exercise

请把下面的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 in {'female','male'}:
            self.__gender=gender
        else:
            raise ValueError('bad value')
        
bart = Student('Bart', 'male')
if bart.get_gender() != 'male':
    print('测试失败!')
else:
    bart.set_gender('female')
    if bart.get_gender() != 'female':
        print('测试失败!')
    else:
        print('测试成功!')

继承和多态

继承
  • 父类:
class Animal(object):
    def run(self):
        print('Animal is running')

class名为Animal,打印使用run()的方法

  • 子类,Dog和Cat类,直接从Animal类继承
class Dog(Animal):
    def run(self):
        print('Dog is running')

子类获得了父类的全部功能,对子类增加方法,可以修改其run()函数,如:

class Dog(Animal):
    def run(self):
        print('Dog is running...')
    def eat(self):
        print('Eating meat...')

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

多态

定义一个class的时候,实际上定义了一种数据类型,如:

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

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

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

其中,c不仅是Dog,还是Animal,因为Dog是从Animal继承下来的
即:是子类的一定是父类,但是是父类的不一定是子类

  • 任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
    e.g:
# 接受Animal类型的变量的函数
def run_twice(animal):
    animal.run()
    animal.run()
# 传入Animal、Dog等实例
>>> run_twice(Animal())
Animal is running...
Animal is running...

>>> run_twice(Dog())
Dog is running...
Dog is running...
  • 多态的好处:当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思
  • 调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的
  • 继承还可以一级一级地继承下来,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:
    * [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oU3J2TeQ-1584628951170)(en-resource://database/1184:0)]
静态语言 vs 动态语言
  1. 静态语言(如Java),若需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
  2. 动态语言(如Python),不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:
class Timer(object):
    def run(self):
        print('Start...')
小结
  • 继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
  • 动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

获取对象信息

使用type()

判断对象类型,使用type()函数

>>> type(123)
>>> type('str')
>>> type(None)

若一个变量指向函数或类,也可以用type()判断:

>>> type(abs)

判断两个数据类型是否一样:

>>> type('abc')==type(123)

判断一个对象是否是函数:

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

判断class的类型,可以使用isinstance()函数:
设继承关系为:object -> Animal -> Dog -> Husky

# 1.创建三种类型的对象
a=Animal()
b=Dog()
h=Husky()
# 2.判断
>>>isinstance(h,Husky)
True
>>> isinstance(h, Dog)
True           # 因为Husky是Dog继承下来的,所以h还是Dog类型
  • isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
  • 能用type()判断的基本类型也可以用isinstance()判断:
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
  • 可以判断一个变量是否是某些类型中的一种:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
  • 总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
使用dir()

此函数可以获得一个对象的所有属性和方法,返回一个包含字符串的list,如:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
__len__方法返回长度
len('ABC')  <==>'ABC'.__len__()
  • 自己写的类,也想用len(myObj)的话,可以自己写一个__len__()方法:
class MyDog(object):
    def __len__(self):
        return 100
# using
>>>dog = MyDog()
>>>len(dog)
100

其余属性或方法

XXX.__len__()
‘ABC’.lower() #返回小写的字符串

  • 直接操作一个对象的状态,使用getattr()、setattr()、hasattr()
    其操作均为,如:getattr(变量名,属性名)
    可以传入一个default参数,如果属性不存在,就返回默认值
    如:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

操作示例:

    class Myobject(object):
        def __init__(self):
            self.x=9
        def power(self):
            return self.x * self.x
    # using
    >>>obj = Myobject()
    # tesing property
    >>>hasatter(obj,'x') #有属性'x'吗
    True
    >>>setattr(obj,'y',19)  # 设置一个属性'y'
    >>> hasattr(obj, 'y') # 有属性'y'吗?
    True
    >>>getattr(obj,'y')  # 获取属性'y'
    19
    # getting object method
    >>> 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
小结

通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。

def readImage(fp):
    if hasattr(fp,'read'):
        return readData(fp)
    return None

如果想从文件流fp中读取图像,首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。

实例属性和类属性

python是动态语言,根据类创建的实例可以任意绑定属性。
1. 给实例绑定属性的方法:通过实例变量,或通过self变量
如:

class Student(object):
    def __init__(self, name):
        self.name = name
# using
s=Student('Bob')
s.score=90

2. 给类绑定属性的方法:可以直接在class中定义属性,这种属性是类属性,归这个类所有,但类的所有实例都可以访问到
同时,实例属性的优先级比类属性高,因此如果给实例绑定属性,则它会屏蔽掉类的这个属性,但是类的这个属性也并未消失,仍可以访问类的这个属性
删除实例的属性,可以用 del 实例.对应属性
如:

# 定义类属性
class Student(object):
    name = 'Student'    # name是Student的类属性
# testing
>>>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仍然可以访问
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
  • 写程序时,不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
exercise

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

class Student(object):
	count=0
	def __init__(self,name):     #因为这里有name这个参数,所以创建实例的时候必须在括号里实例化
		self.name=name   
		Student.count+=1
# 创建实例+testing code
# aaa=Student('a')
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
测试通过!

小结
  • 实例属性属于各个实例所有,故不干扰
  • 类属性属于类所有,所有实例共享一个属性
  • 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

该用户没有用户名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值