Python学习 day9-2021.3.7(面对对象基础·下)


Python入门课程系列:

  • Python学习 day1:认识Python
  • Python学习 day2:判断语句与循环控制
  • Python学习 day3:高级数据类型
  • Python学习 day4:函数基础
  • Python学习 day5:函数
  • Python学习 day6:内置函数
  • Python学习 day7:面向对象基础·上
  • Python学习 day8:面向对象基础·中

目标:

  1. 通过声明私有化属性、方法,保护和控制数据(重点)
  2. 通过property属性的使用,即控制好数据又方便访问(重点、难点)
  3. 明确 _ new _ 方法的作用和用法(重点)
  4. 通过单例模式控制实例个数(难点)
  5. 使用异常处理机制,处理异常,提高代码健壮性
  6. 利用动态语言特点,动态添加属性和方法
  7. 利用 _ slot _ 属性控制可动态的属性

1. 私有化属性

前面学习面对对象过程中,修改类属性都是直接通过类名修改的。如果有些重要属性不想被被人修改,或者防止意外修改,就可以将属性定义为私有属性,并添加一个可调用的方式去访问。
语法:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。
使用私有属性的场景:1. 把特定的一个属性隐藏起来,不想被类的外部直接调用;2. 保护属性的值不被随意改变;3. 保护属性不被派生类【子类】继承

class Person:
    def __init__(self):
        self.__name='lele' #实例属性前加两个下划线将其私有化
        self.age=14
        pass
    def __str__(self):
        return '{}的年龄是{}'.format(self.__name,self.age) #私有化的属性在内部可以使用
yt=Person()
print(yt.__name) #私有化后不能在类的外部直接访问
# AttributeError: 'Person' object has no attribute '__name'
print(yt)  #在类的内部依然可以使用
#lele的年龄是14

class Student(Person):
    pass
stu=Student()
print(stu.__name) #父类的私有化属性,子类不能继承
# AttributeError: 'Person' object has no attribute '__name'

小结:

  1. 私有化的【实例】属性不能在外部直接访问,但可以在类的内部可以访问、修改。
  2. 子类不能继承父类的私有化属性,只能继承父类公共的属性和行为
  3. 在属性前面加两个下划线就可以定义私有属性

2. 私有化方法

私有化方法与私有化属性一样,有些重要的方法,不允许外部调用,防止子类意外重写,把普通的方法设置成私有化方法。
语法:在方法名前面加两个下划线

class Animal:
    def __eat(self):  #实例方法
        print('乖乖吃')
        pass
    def run(self):
        print('飞快跑')
        pass
class Bird(Animal):
    pass
b1=Bird()
b1.run()
#飞快跑
b1.__eat()
#AttributeError: 'Bird' object has no attribute '__eat'

访问和修改私有变量:

一般写两个方法一个访问,一个修改,由方法去控制访问。

class Person:
    def __init__(self):
        self.__age=18 #定义一个私有化属性,属性名字前面加两个__下划线
    def get_age(self): #访问私有类属性
        return self.__age
    def set_age(self,age): #修改私有属性
        if age<0:
            print('年龄不能小于0')
        else:
            self.__age=age

(这样操作给调用者的感觉就是调用了一个方法,并不是访问属性。如何让调用者直接以访问属性的方式访问而且以我们能控制的方式提供给调用者?这是就可以使用属性函数property。)

单下划线、双下划线、头尾下划线说明:

  • _ xxx前面加一个下划线,以单下划线开头的表示是protected类型的变量,即保护类型只允许其本身与子类访问,不能使用from xxx import*的方式导入。(一般使用双前下划线)
  • _ xxx _ 前后两个下划线,为魔法方法,一般是python自有,开发者不要创建这类型的方法。
  • xxx _ 后单下划线,避免属性名与python关键字冲突

3. Property属性

前面学习中,我们一直在用“类对象.属性”的方式访问类中定义的属性,其实这种做法是欠妥的,因为它破坏了类的封装原则。正常情况下,类包含的属性应该是隐藏的,只允许通过类提供的方法来间接实现对类属性的访问和操作。因此,在不破坏类封装原则的基础上,为了能够有效操作类中的属性,类中应包含读(或写)类属性的多个 getter(或 setter)方法,这样就可以通过“类对象.方法(参数)”的方式操作属性。此外,Python 中提供了 property() 函数,可以实现在不破坏类封装原则的前提下,让开发者依旧使用“类对象.属性”的方式操作类中的属性。
实现方式:
1.在类型定义值为property对象的类属性

property() 函数的基本使用格式:
属性名=property(fget=None, fset=None, fdel=None, doc=None)。
fget 参数:用于指定获取该属性值的类方法。
fset 参数:用于指定设置该属性值的方法。
fdel 参数:用于指定删除该属性值的方法
doc 参数:是一个文档字符串,用于说明此函数的作用。
在使用 property() 函数时,以上 4 个参数可以仅指定第 1 个、或者前 2 个、或者前 3 个,当前也可以全部指定。也就是说,property() 函数中参数的指定并不是完全随意的。

#方法1
class Person(object):
    def __init__(self):
        self.__age=18  # 定义一个函数,属性名字前面加两个下划线
    def get_age(self):  # 访问私有化实例属性
        return self.__age
    def set_age(self,age):  # 修改私有化实例属性
        if age < 0:
            print('年龄不能小于0')
        else:
            self.__age=age
    age=property(get_age,set_age)
    # 定义一个类属性,当对这个age设置值时调用set_age,当获取值时调用get_age.注意:必须时以get,set开头的方法名,才能被调用。
    #通过调用函数property并将存取方法作为参数创建了一个特性,然后将名称age关联到这个特性。这样就能以同样的方式对待get_age、set_age和age,而无需关心它们是如何实现的。
xiaoming=Person()
print(xiaoming.age) #若不设置property,这一行会报错
#18
xiaoming.age=15 #只需设置age即可,不必管类的内部get_age和set_age是如何运作。通过property封装的age是类的接口
print(xiaoming.age)
#15

#方法2
class Person(object):
    def __init__(self):
        self.__age=18  # 定义一个函数,属性名字前面加两个下划线
    @property #使用装饰器对age进行装饰,提供一个getter方法
    def age(self):  # 访问私有化实例属性
        return self.__age

    @age.setter  # 使用装饰器进行装饰,提供一个setter方法
    def age(self,age):  # 修改私有实例属性
        if age < 0:
            print('年龄不能小于0')
        else:
            self.__age=age
xiaoming=Person()
print(xiaoming.age)
#18
xiaoming.age=15
print(xiaoming.age)
#15

修改私有属性的途径:

  1. 通过方法去实现(set get)
  2. 借助属性函数property去实现

4. _ new _ 方法

  • _ new _ 方法的作用是:创建并返回一个实例对象,如果 _ new _ 方法只调用了一次,就会得到一个对象。继承自object的新式类才有new这一魔法方法。
  • 注意事项
    1. _ new _ 是在一个对象实例化的时候所调用的第一个方法
    2. _ new _ 至少必须要有一个参数cls,代表要实例化的类 ,此参数在实例化时由Python解释器自动提供,其他参数是要来直接传递给_ init _ 方法
    3. _ new _ 决定是否要使用该 _ init _ 方法,因为 _ new _ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 _ new _ 没有返回实例对象,则 _ init _ 不会被调用
    4. 在 _ new _ 方法中,不能调用自己的 _ new _ 方法,即:return cls. _ new _ (cls),否则会报错(RescursionError: maximum recursion depth exceeded:超过最大递归深度)
  • 尽管没有添加标识,但new方法是一个静态方法
class Animal():
    def __init__(self):
        self.color='红色'
        pass
    #在python当中,如果不重写new方法,如下是默认的new
    def __new__(cls, *args, **kwargs):  #cls表示当前class
        #方法1 用super来创建 
        return super().__new__(cls, *args, **kwargs)  #super是调用父类
        #方法2 用object来创建
        return object.__new__(cls, *args, **kwargs)
    pass
tiger=Animal() #实例化的过程中会自动调用new去创建实例
print(tiger.color)
#红色

在新式类(python3)中,_ new _ 才是真正的实例化方法,为类提供外壳制造出实例框架,然后调用该框架内的构造方法 _ init _ 来进行丰满操作。
以建房子来比喻,new方法负责开发地皮,打地基,并将原料存放在工地上,而init负责从工地取原料,建造出地皮开发图纸规定的大楼,并负责细节设计、建造、最终完成。

5. 单例模式

单例模式要求一个类有且只有一个实例,并且提供了一个全局的访问点。

  • 单例模式是常用设计模式的一种。目的:确保某一个类只有一个实例存在。就比如我们打开电脑的回收站 ,在系统中只能打开一个回收站,也就是说这整个系统中只有一个实例,重复打开也是使用这个实例。
  • 简单的说就是不管创建多少次对象,类返回的对象都是最初创建的,不会再新建其他对象。
#基于 _ new _ 方法实现单例对象的创建
#例1
class SingleCase(object):
    __instance=None #保存实例对象
    def __init__(self,name,age):
        print(name,age)
    def __new__(cls, *args, **kwargs):
        #如果类属性__instance的值为None,那么创建一个对象
        #如果类属性__instance的值不为None,返回 __instance保存的对象
        if not cls.__instance:
            cls.__instance=super(SingleCase, cls).__new__(cls) #调用父类_new_方法生成一个实例对象
            return cls.__instance
        else:
            return cls.__instance

obj1=SingleCase('huahua',18)
obj2=SingleCase('miao',8)
print(id(obj1))
print(id(obj2))
#huahua 18
#miao 8
#140691412397840   #id相同,说明实例化两次对象,实际上都是一个对象
#140691412397840

#例2
class DataBaseClass(object):
    def __new__(cls, *args, **kwargs):
        # cls._instance=cls.__new__(cls)   不能使用自身的new方法,容易造成一个深度递归,应该调用父类的new方法
        if not hasattr(cls,'_instance'): #如果不存在就开始创建
        #hasattr()函数用于判断对象是否包含对应的属性
            cls._instance=super().__new__(cls, *args, **kwargs)
        return cls._instance
    pass
db1=DataBaseClass()
print(id(db1))
db2=DataBaseClass()
print(id(db2))
#140663654493968
#140663654493968   #并不是只能返回同一个对象,而是每次返回相同的对象

利用类属性保存初次创建的实例对象,第二次实例化的 时候就判断类属性是否有保存实例对象 ,如果有就返回类属性保存的,如果没有就调用父类 _ new _ 方法创建新的实例对象

  • 应用 :网站的计数器模块、权限验证模块、windows资源管理器、系统回收站、数据库连接池等等

6. 错误与异常处理

在代码出现错误时 ,当程序执行到错误代码的时候,程序会直接终止并报错,这是因为 python检测到一个错误时,解释器就无法继续执行了 ,出现了错误的提示,这就是“异常”。

语法格式:

  • try:可能出现错误的代码块
  • except:出错之后执行的代码块
  • else:没有出错的代码块
  • finally:不管有没有出错都执行的代码块
  1. try ... except语句:
    将可能出错的代码放到try里面 ,except可以指定类型捕获异常。用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。如果你不想在异常发生时结束你的程序,只需在try里捕获它。这样程序员就不会因为一段代码包异常而导致整个程序崩溃
  • except在捕获错误异常的时候,是需要根据具体的错误类型来捕获的。
  • 用一个块可以捕获多个不同类型的异常,但一次只能捕获一个异常
print(b) #因为这一步的错误导致后面的代码都无法运行
print('你好')
print(3333)

try:
    print(b) 
    pass
except NameError as msg:
    #捕获到的错误才会在这里执行
    print(msg) #输出异常
    pass
except IndexError as msg:
    #多设置几种类型的错误,就可以多捕获几种类型的错误 
    print(msg) #输出异常
    pass
except Exception as result: #❗️可以捕获所有的错误类型,使用这个就不必设置要捕获的错误类型
    print(result)
    #在此去处理捕获到的错误
    pass
print('你好')
print(3333)
#name 'b' is not defined
#你好
#3333
  • 不需要在每个可能出错的地方去捕获,只要在合适的层次去捕获错误就可以了
def A(s):
    return 10/int(s)
    pass
def B(s):
    return A(s)*2
def main():
    try:
        B('0')
        pass
    except Exception as msg:
        print(msg)
        pass
    pass
main()
#division by zero

异常的抛出机制:
如果在运行时发生异常,解释器会查找相应的异常捕获类型。如果在当前函数里面没有找到的话,它会将异常值传递给上层的调用函数,看能否处理。如果在最外层没有找到的话 ,解释器就会退出,程序down掉。

  1. try ... except...else语句:
try:
    print (aa)
    pass
except Exception as msg:
    print(msg)
else:
    print('当try里面的代码没有出现异常的情况下,我才会执行')
#name 'aa' is not defined

try:
    print ('我是没有错误的')
    pass
except Exception as msg:
    print(msg)
else:
    print('当try里面的代码没有出现异常的情况下,我才会执行')
#我是没有错误的
#当try里面的代码没有出现异常的情况下,我才会执行
  1. try ... except...finally语句:
try:
    int('fff')
    pass
except Exception as msg:
    print(msg)
    pass
finally:
    print('释放文件的资源、数据库的资源等等')
    print('不管有没有出错都执行的代码块')
#invalid literal for int() with base 10: 'fff'
#释放文件的资源、数据库的资源等等
#不管有没有出错都执行的代码块
  1. 自定义异常
  • 自定义异常都要直接或间接即成Error或Exception类
  • 由开发者主动抛出自定义异常,在python中使用raise关键字
class ToolongException(Exception): ##自定义异常类型需要继承Exception
    def __init__(self,leng):
        '''
        :param leng: 长度
        '''
        self.len=leng
        pass
    def __str__(self):
        return '您输入的姓名数据长度是'+str(self.len)+'超出限定长度'
    pass
def name_Test():
    name=input('请输入姓名:')
    try:
        if len(name)>5:
            raise ToolongException(len(name))
        else:
            print(name)
            pass
        pass
    except ToolongException as result:
        print(result)
        pass
    finally:
        print('')
name_Test()

⚠️python中常见的异常错误类型:

异常名称 描述
BaseException 所有异常的基类
ArithmeticError 所有数值计算错误的基类
AssertionError 断言语句失败
AttributeError 对象没有这个属性
DeprecationWarning 关于被弃用的特征的警告
EnvironmentError 操作系统错误的基类
EOFError 没有内建输入,到达EOF 标记
Exception 常规错误的基类
FloatingPointError 浮点计算错误
FutureWarning 关于构造将来语义会有改变的警告
GeneratorExit 生成器(generator)发生异常来通知退出
ImportError 导入模块/对象失败
IndentationError 缩进错误
IndexError 序列中没有此索引(index)
IOError 输入/输出操作失败
KeyboardInterrupt 用户中断执行(通常是输入^C)
KeyError 映射中没有这个键
LookupError 无效数据查询的基类
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
NotImplementedError 尚未实现的方法
OSError 操作系统错误
OverflowError 数值运算超出最大限制
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
StandardError 所有的内建标准异常的基类
StopIteration 迭代器没有更多的值
SyntaxError Python 语法错误
SyntaxWarning 可疑的语法的警告
SystemError 一般的解释器系统错误
SystemExit 解释器请求退出
TabError Tab 和空格混用
TypeError 对类型无效的操作
UnboundLocalError 访问未初始化的本地变量
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeError Unicode 相关的错误
UnicodeTranslateError Unicode 转换时错误
UserWarning 用户代码生成的警告
ValueError 传入无效的参数
Warning 警告的基类
WindowsError 系统调用失败
ZeroDivisionError 除(或取模)零 (所有数据类型)

7.Python动态添加属性和方法

动态语言:运行时可以改变其结构的语言,例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或事其他结构上的变化。如 php、JavaScript、Python都是动态语言,C、C#、java是静态语言
所以python可以在程序运行过程中添加属性和方法

  • 动态添加类属性和实例属性
class Animal(object):
    def __init__(self,name,age):
        self.name=name
        self.age=age
    pass
#定义了两个初始属性name和age,但没有颜色,想去添加颜色又不能修改类
cat=Animal('小白',5)
cat.color='白色'  #动态绑定color属性(只针对实例对象,类本身不改变)
print(cat.color)
#白色

Animal.size='mini' #动态添加类属性
print(cat.size) 
#mini
  • 动态添加实例方法(需要用到types)
#案例1
class Animal(object):
    def __init__(self,name,age):
        self.name=name
        self.age=age
    pass
cat=Animal('kitty',3)
import types #添加方法的库
def run(self): #动态添加实例方法
    print('飞快跑')
cat.run=types.MethodType(run,cat) #动态方法的绑定方式 run是要绑定的方法,cat是要绑定的实例
cat.run()
#飞快跑

#案例2
import types
def dynamicMethod(self):
    print('{}的体重是{}kg,在{}读大学'.format(self.name,self.weight,Student.school))
    pass

class Student:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    pass
    def __str__(self):
        return '{}今年{}岁了'.format(self.name,self.age)
    pass
zyh=Student('小华',20)
print(zyh)
#小华今年20岁了
zyh.weight=50 #动态添加实例属性
Student.school='北航' #动态添加类属性
zyh.printInfo=types.MethodType(dynamicMethod,zyh) #动态的绑定方式
zyh.printInfo()
#小华的体重是50kg,在北航读大学
  • 动态添加类方法和静态方法
    (绑定到对象就仅限那个对象使用,绑定到类里面就以后从这个类实例化出的对象都可以使用)
    使用方法:类名.方法名=xxxx
class Student:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    pass
    def __str__(self):
        return '{}今年{}岁了'.format(self.name,self.age)
    pass
@classmethod
def classTest(cls):
    print('这是一个类方法')
    pass
Student.TestMethod=classTest
Student.TestMethod()
#这是一个类方法
xx=Student('maomao',20)
xx.TestMethod()
#这是一个类方法
@staticmethod
def staticTest():
    print('这是一个静态方法')
    pass
Student.StaticTest=staticTest
Student.StaticTest()
#这是一个静态方法
hh=Student('huihui',20)
hh.StaticTest()
#这是一个静态方法

8. _ slots _ 属性

python是动态语言,在运行的时候可以动态添加属性。如果要限制在运行的时候给类添加属性,python允许在定义class的时候,定义一个特殊的 _ slots _ 变量,来限制该class实例能添加的属性。
只有在 _ slots _ 变量中的属性才可以被添加,没有在 _ slots _ 变量中的属性会添加失败。可以防止其他人在调用类的时候胡乱添加属性或方法。 _ slots _ 属性子类不会继承,只在当前类中有效。

作用:

  1. 限制要添加的实例属性
  2. 节约内存空间
class Animal(object):
   __slots__ = ('name','age') #元组类型
   pass
dog=Animal()
dog.name='旺财'
print(dog.name)
#旺财
# dog.color='白色'
# print(dog.color)
# #AttributeError: 'Animal' object has no attribute 'color'
print(dog.__dict__) #所有可用的属性都在这里储存,非常占用内存。
#可以看到,在定义了slots变量之后,student类的实例已经不能随意创建不在_slots_定义的属性了
#同时,实例当中也不再有__dict__结构

在继承关系当中使用slots:子类未声明 _ slots _ 时,不会继承父类的 _ slots _ ,此时子类是可以随意进行属性赋值的

class Student(object):
    __slots__=('name','age')
    pass
class subStudent(Student):
    pass
ln=subStudent()
ln.gender='男'
ln.pro='计算机信息管理'
print(ln.gender,ln.pro)
#男 计算机信息管理

如果子类声明了 _ slots _ 时 ,也会继承父类的 _ slots _ ,这时子类 _ slots _ 的范围为自身+父类的 _ slots _ 。

class Student(object):
    __slots__=('name','age')
    pass
class subStudent(Student):
    __slots__ = ('gender')
    pass
ln=subStudent()
ln.name='lili'
print(ln.name)
#lili
ln.pro='计算机信息管理'
#AttributeError: 'subStudent' object has no attribute 'pro'
print(ln.pro)

总结:

  • 私有化属性:两个下划线开头,声明该属性为私有,不能在类的外部被使用或访问
  • 私有化方法:即在方法名前加两个下划线
  • Property属性:
    类属性,即在类中定义为property对象的类属性
    装饰器,即在方法上使用装饰器
  • _ new _ 方法:: _ new _方法的作用是创建 并返回一个实例对象
  • 单例模式:不管创建多少次对象,类返回的对象都是最初创建的,不会再新建其他对象
  • 错误与异常处理:
    try:可能出现错误的代码块
    except:出错之后执行的代码块
    else:没有出错的代码块
    finally:不管有没有出错都执行的代码块
  • python动态添加属性和方法:在程序运行过程中添加属性和方法
  • _ slots _ 方法: 在定义class的时候,定义一个特殊的 _ slots _ 变量,来限制该class实例能添加的属性

课后作业·问答题

  1. Python中的new方法作用是什么?
  2. 什么是单例模式?单例模式适用于什么场景?
  3. 私有化方式与私有化属性在子类中能否继承?
  4. 在Python中什么是异常?
  5. Python中是如何处理异常的?
  6. Python中异常处理语句的一般格式,可以使用伪代码的形式描述。
  7. _ slots _ 属性的作用
  8. 私有化属性的作用?
  9. 在类外面是否能修改私有属性
  10. 如果一个类中,只有指定的属性或者方法能被外部修改,那么该如何限制外部修改

课后作业·实操题

  1. 编写一段代码已完成下面的要求:
    定义一个Person类,类中要有初始化方法,方法中要有人的姓名和年龄两个私有属性
    提供获取用户信息的函数
    提供获取私有属性的方法
    提供可以设置私有属性的方法
    设置年龄的范围在0-120的方法,如果不在这个范围内,不能设置成功
  2. 请写一个单例模式
  3. 创建一个类,并定义两个私有化属性,提供一个获取属性的方法和设置属性的方法。利用property属性给调用者提供属性方法的调用获取和设置私有属性方法的方式
  4. 创建一个Animal类,实例化一个cat对象,请给cat对象动态绑定一个run方法,给类绑定一个类属性color,给类绑定一个类方法打印字符串“ok”。

你可能感兴趣的:(Python学习 day9-2021.3.7(面对对象基础·下))