zoukankan      html  css  js  c++  java
  • 面向对象编程

    面向对象编程

    面向对象编程:
      核心是对象二字,对象就是一个用来盛放数据与功能的容器
      基于该思想编写程序就是创造一个个的容器
      优点:扩展性强
      缺点:编程的复杂度提升  
    
    在程序中,先定义类,后产生对象

    类与对象

    对象是"容器",用来存放数据与功能的
    
    类也是"容器",用来存放同类对象共有的数据与功能
    
    
    详情参见:https://www.cnblogs.com/ZhZhang12138/p/14251433.html

    封装

    详情参见:https://zhuanlan.zhihu.com/p/109310247

    1、引入

    面向对象编程有三大特征:封装、继承、多态。
    
    封装指的就是把数据与功能整合到一起。
    
    针对封装到对象或者类中的属性,我们还可以严格控制对它们的访问,分两步实现:隐藏与开放接口

    2、隐藏属性

    Python的class机制采用双下划线的方式将隐藏属性隐藏起来(设置成私有的),但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成 " _类名__属性名 " 的形式。
    class Foo:
        __N = 0 # 变形为_Foo__N
    
        def __init__(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
            self.__x = 10  # 变形为_Foo_x
    
        def __f1(self): # 变形为_Foo__f1
            print('__f1 run')
    
        def f2(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
            self.__f1() # 变形为self._Foo__f1
    
    
    print(Foo.__N) # 报错
    # AttributeError: type object 'Foo' has no attribute '__N'
    
    obj = Foo()
    print(obj.__x) # 报错
    # AttributeError: 'Foo' object has no attribute '__x'

    注意

    这种变形需要注意的问题是:
    
    1、在类外部无法直接访问双下滑线的属性,但是知道了类名和属性名就可以拼出名字:_类名__属性。然后就可以访问了,例如:Foo._A_N。
        所以说这种操作并没有严格意义上的限制外部访问,仅仅是语法意义上的变形。
    
    2、在类内部是可以直接访问双下滑线开头的属性的,因为在类定义阶段内部双下划綫开头的属性同一发生了变形。
    
    3、变形操作值在类定义阶段发生一次,在类定义之后的赋值操作,不会变形

    3、开放接口

    隐藏数据属性

    将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接的操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格的控制。
    class Teacher:
        def __init__(self, name, age):  # 将名字和年纪都隐藏起来
            self.__name = name
            self.__age = age
    
        def tell_info(self):  # 对外提供访问老师信息的接口
            print('姓名:%s,年龄:%s' % (self.__name, self.__age))
    
        def set_info(self, name, age):  # 对外提供设置老师信息的接口,并附加类型检查的逻辑
            if not isinstance(name, str):
                print('姓名必须是字符串类型')
                return
            if not isinstance(age, int):
                print('年龄必须是整型')
                return
            self.__name = name
            self.__age = age
    
    
    t = Teacher('lili',18)
    t.set_info('Lili','19')  # 年龄必须是整型
    # 年龄必须是整型
    t.tell_info() # 查看老师的信息
    # 姓名:lili,年龄:18

    隐藏函数属性

    目的是为了隔离复杂度,例如ATM程序的取款功能,该功能有好多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,
    而对于使用者来说,只需要开发取款这个功能接口即可,取余功能我们都可以隐藏起来。
    class ATM:
        def __card(self):  # 插卡
            print('插卡')
    
        def __auth(self):  # 身份认证
            print('用户认证')
    
        def __input(self):  # 输入金额
            print('输入取款金额')
    
        def __print_bill(self):  # 打印小票
            print('打印账单')
    
        def __take_money(self):  # 取钱
            print('取款')
    
        def withdraw(self):  # 取款功能
            self.__card()
            self.__auth()
            self.__input()
            self.__print_bill()
            self.__take_money()
    obj = ATM()
    obj.withdraw()

    4、property

    property:可以将类中的函数"伪装成"对象的数据属性,对象在访问该特殊属时会触发功能的执行,然后将返回值作为本次访问的结果。
    class People:
        def __init__(self, name, weight, height):
            self.name = name
            self.weight = weight
            self.height = height
    
        @property
        def bmi(self):
            return self.weight / (self.height ** 2)
    
    
    obj = People('lili', 75, 1.85)
    print(obj.bmi ) # 触发方法bmi的执行,将obj自动传给self,执行后返回值作为本次引用的结果
    # 21.913805697589478
    
    
    使用property有效的保证了属性访问的一致性
    
    
    class Foo:
        def __init__(self, val):
            self.__NAME = val  # 将属性隐藏起来
        @property
        def name(self):
            return self.__NAME
        @name.setter
        def name(self, value):
            if not isinstance(value, str):  # 在设定值之前进行类型检查
    
                print('%s must be str' %value)
                return
            self.__NAME = value  # 通过类型检查后,将值value存放到真实的位置self.__NAME
        @name.deleter
        def name(self):
            print('Can not delete')
    f = Foo('lili')
    f.name
    # lili
    f.name = 'LiLi'  # 触发name.setter装饰器对应的函数name(f,’Egon')
    f.name = 123  # 触发name.setter对应的的函数name(f,123),抛出异常TypeError
    del f.name  # 触发name.deleter对应的函数name(f),抛出异常PermissionError

    继承

    详情参见:https://zhuanlan.zhihu.com/p/109331525

                      https://www.cnblogs.com/ZhZhang12138/p/14261563.html#yifengzhuang

    1、继承介绍

    继承是创建新类的一种方式
    新建的类称之为子类或派生类
    被继承的类称之为父类,基类,超类
    
    继承的特点:
      子类可以遗传父类的属性
        
    通过类的内置属性__bases__可以查看类继承的所有父类

    2、继承与抽象

    要找出类与类之间的继承关系,需要先抽象,再继承。抽象即总结相似之处,总结对象之间的相似之处得到类,总结类与类之间的相似之处就可以得到父类,如下图所示

    基于抽象的结果,我们就找到了继承关系

    基于上图我们可以看出类与类之间的继承指的是什么’是’什么的关系(比如人类,猪类,猴类都是动物类)。

    子类可以继承/遗传父类所有的属性,因而继承可以用来解决类与类之间的代码重用性问题。

    3、属性查找

    有了继承关系,对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找……
    >>> class Foo:
    ...     def f1(self):
    ...         print('Foo.f1')
    ...     def f2(self):
    ...         print('Foo.f2')
    ...         self.f1()
    ... 
    >>> class Bar(Foo):
    ...     def f1(self):
    ...         print('Foo.f1')
    ... 
    >>> b=Bar()
    >>> b.f2()
    Foo.f2
    Foo.f1
    
    """
    b.f2()会在父类Foo中找到f2,先打印Foo.f2,然后执行到self.f1(),即b.f1(),仍会按照:对象本身->类Bar->父类Foo的顺序依次找下去,在类Bar中找到f1,因而打印结果为Foo.f1
    
    """
    父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的。
    >>> class Foo:
    ...     def __f1(self): # 变形为_Foo__fa
    ...         print('Foo.f1') 
    ...     def f2(self):
    ...         print('Foo.f2')
    ...         self.__f1() # 变形为self._Foo__fa,因而只会调用自己所在的类中的方法
    ... 
    >>> class Bar(Foo):
    ...     def __f1(self): # 变形为_Bar__f1
    ...         print('Foo.f1')
    ... 
    >>> 
    >>> b=Bar()
    >>> b.f2() #在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法
    Foo.f2
    Foo.f1

    4、继承的应用

    子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的init覆盖父类的

    class Student:
        school = '虹桥校区'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
        def choose(self):
            print('%s 选课成功' % self.name)
    
    
    stu1 = Student('jack', 18, 'mael')
    stu2 = Student('tom', 23, 'mael')
    stu3 = Student('lili', 28, 'femael')
    
    
    class Teacher:
        school = '虹桥校区'
    
        def __init__(self, name, age, gender, level):
            self.name = name
            self.age = age
            self.gender = gender
            self.level = level
    
        def score(self):
            print('%s 正在为学生打分' % self.name)
    
    
    teal1 = Teacher('egon', 18, 'male', 10)
    teal2 = Teacher('lxx', 28, 'female', 3)

    很明显子类Teacher中init内的前三行又是在写重复代码,若想在子类派生出的方法内重用父类的功能,有两种实现方式:

    super():返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,去当前类的父类中找属性,严格依赖继承

    class Base:
        school = '虹桥校区'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    # 方法一:“指名道姓”地调用某一个类的函数
    class Student(Base):
        school = '虹桥校区'
    
        def __init__(self, name, age, gender):
            Base.__init__(self,name, age, gender)  # #调用的是函数,因而需要传入self
    
        def choose(self):
            print('%s 选课成功' % self.name)
    
    # 方法二:super()
    # 调用super()会得到一个特殊的对象,
    # 该对象专门用来引用父类的属性,
    # 且严格按照MRO规定的顺序向后查找
    class Teacher(Base):
        school = '虹桥校区'
    
        def __init__(self, name, age, gender, level):
            super().__init__(name, age, gender)  #   #调用的是绑定方法,自动传入self
            self.level = level
    
        def score(self):
            print('%s 正在为学生打分' % self.name)
    
    
    stu1 = Student('jack', 18, 'mael')
    stu2 = Student('tom', 23, 'mael')
    stu3 = Student('lili', 28, 'femael')
    
    
    teal1 = Teacher('egon', 18, 'male', 10)
    teal2 = Teacher('lxx', 28, 'female', 3)

    5、菱形问题

    大多数面向对象语言都不支持多继承,而在Python中,一个子类是可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发著名的 Diamond problem菱形问题(或称钻石问题,有时候也被称为“死亡钻石”),菱形其实就是对下面这种继承结构的形象比喻

    A类在顶部,B类和C类分别位于其下方,D类在底部将两者连接在一起形成菱形。
    
    这种继承结构下导致的问题称之为菱形问题:如果A中有一个方法,B和/或C都重写了该方法,而D没有重写它,那么D继承的是哪个版本的方法:B的还是C的?如下所示
    
    
    class A(object):
        def test(self):
            print('from A')
    
    
    class B(A):
        def test(self):
            print('from B')
    
    
    class C(A):
        def test(self):
            print('from C')
    
    
    class D(B,C):
        pass
    
    
    obj = D()
    obj.test() # 结果为:from B

    要想搞明白obj.test()是如何找到方法test的,需要了解python的继承实现原理

    6、继承原理

    MRO方法

    """
    python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下
    """
    >>> D.mro() # 新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法
    [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

    python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

    """
    1.子类会先于父类被检查
    2.多个父类会根据它们在列表中的顺序被检查
    3.如果对下一个类存在两个合法的选择,选择第一个父类
    """

    所以obj.test()的查找顺序是,先从对象obj本身的属性里找方法test,没有找到,则参照属性查找的发起者(即obj)所处类D的MRO列表来依次检索,首先在类D中未找到,然后再B中找到方法test

    PS:

    1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
    2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,

    7、继承顺序

    经典类与新式类

    """
    在Python2中有经典类与新式类之分,没有显式地继承object类的类,以及该类的子类,都是经典类,显式地继承object的类,以及该类的子类,都是新式类。
    
    Python3中,即使没有显式地继承object,也会默认继承该类
    """
    
    
    如果继承关系为菱形结构,那么经典类与新式类会有不同MRO,分别对应属性的两种查找方式:深度优先和广度优先。
    
    菱形继承/死亡钻石:一个子类继承的多条分支最终汇聚到一个非object的类上

    经典类:深度优先

    新式类:广度优先

    即使没有直接继承关系,super仍然会按照mro继续往后查找

    #A没有继承B,但是A内super会基于C.mro()继续往后找
    class A:
        def test(self):
            super().test()
    class B:
        def test(self):
            print('from B')
    class C(A,B):
        pass
    
    c=C()
    c.test() #打印结果:from B
    
    
    print(C.mro())
    #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

    注意

    """
    当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。
    只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,
    每个方法也只会被调用一次
    (注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,
    千万不要通过看代码去找继承关系,一定要看MRO列表)
    """

    8、python Minins机制

    Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系。

    class Vehicle:  # 交通工具
        pass
    
    
    class FlyableMixin:
        def fly(self):
            '''
            飞行功能相应的代码        
            '''
            print("I am flying")
    
    
    class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
        pass
    
    
    class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
        pass
    
    
    class Car(Vehicle):  # 汽车
        pass
    
    # ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路
    """
      
    可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名为FlyableMixin,而不是Flyable,
      这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),
      这种命名方式就是用来明确地告诉别人(python语言惯用的手法),
      这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。
      所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle,而不是一个飞行器。
    """

    使用Mixin类实现多重继承要非常小心

    • 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀

    • 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类

    • 然后,它不依赖于子类的实现

    • 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)

    9、派生与方法重用

    子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的__init__覆盖父类的
    >>> class People:
    ...     school='清华大学'
    ...     
    ...     def __init__(self,name,sex,age):
    ...         self.name=name
    ...         self.sex=sex
    ...         self.age=age
    ... 
    >>> class Teacher(People):
    ...     def __init__(self,name,sex,age,title): # 派生
    ...         self.name=name
    ...         self.sex=sex
    ...         self.age=age
    ...         self.title=title
    ...     def teach(self):
    ...         print('%s is teaching' %self.name)
    ... 
    >>> obj=Teacher('lili','female',28,'高级讲师') #只会找自己类中的__init__,并不会自动调用父类的
    >>> obj.name,obj.sex,obj.age,obj.title
    ('lili', 'female', 28, '高级讲师')

    很明显子类Teacher中init内的前三行又是在写重复代码,若想在子类派生出的方法内重用父类的功能,有两种实现方式

    方法一:“指名道姓”地调用某一个类的函数

    >>> class Teacher(People):
    ...     def __init__(self,name,sex,age,title):
    ...         People.__init__(self,name,age,sex) #调用的是函数,因而需要传入self
    ...         self.title=title
    ...     def teach(self):
    ...         print('%s is teaching' %self.name)

    方法二:super()

    """
    调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找
    """
    >>> class Teacher(People):
    ...     def __init__(self,name,sex,age,title):
    ...         super().__init__(name,age,sex) #调用的是绑定方法,自动传入self
    ...         self.title=title
    ...     def teach(self):
    ...         print('%s is teaching' %self.name)

    提示

    """
    在Python2中super的使用需要完整地写成super(自己的类名,self) ,而在python3中可以简写为super()。
    """

    区别

    """
    方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super()仍然会按照MRO继续往后查找
    """
    >>> #A没有继承B
    ... class A:
    ...     def test(self):
    ...         super().test()
    ... 
    >>> class B:
    ...     def test(self):
    ...         print('from B')
    ... 
    >>> class C(A,B):
    ...     pass
    ... 
    >>> C.mro() # 在代码层面A并不是B的子类,但从MRO列表来看,属性查找时,就是按照顺序C->A->B->object,B就相当于A的“父类”
    [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,<class ‘object'>]
    >>> obj=C()
    >>> obj.test() # 属性查找的发起者是类C的对象obj,所以中途发生的属性查找都是参照C.mro()
    from B

    10、组合

    """
    在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。
    不同的是:继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;
    而组合则是一种“有”的关系,
    比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例
    """
    class Course:
        def __init__(self,name,period,price):
            self.name=name
            self.period=period
            self.price=price
        def tell_info(self):
            print('<%s %s %s>' %(self.name,self.period,self.price))
    
    class Date:
        def __init__(self,year,mon,day):
            self.year=year
            self.mon=mon
            self.day=day
        def tell_birth(self):
           print('<%s-%s-%s>' %(self.year,self.mon,self.day))
    
    class People:
        school='清华大学'
        def __init__(self,name,sex,age):
            self.name=name
            self.sex=sex
            self.age=age
    
    #Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码
    class Teacher(People): #老师是人
        def __init__(self,name,sex,age,title,year,mon,day):
            super().__init__(name,age,sex)
            self.birth=Date(year,mon,day) #老师有生日
            self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象
        def teach(self):
            print('%s is teaching' %self.name)
    
    
    python=Course('python','3mons',3000.0)
    linux=Course('linux','5mons',5000.0)
    teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23)
    
    # teacher1有两门课程
    teacher1.courses.append(python)
    teacher1.courses.append(linux)
    
    # 重用Date类的功能
    teacher1.birth.tell_birth()
    
    # 重用Course类的功能
    for obj in teacher1.courses: 
        obj.tell_info()
    
    
    此时对象teacher1集对象独有的属性、Teacher类中的内容、Course类中的内容于一身(都可以访问到),是一个高度整合的产物

    多态

    1、多态

    """
    多态指的是一类事物有多种形态
    
    动物有多种形态:人,狗,猪
    文件有多种形态:文本文件,可执行文件
    """
    import abc
    class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
        @abc.abstractmethod
        def talk(self):
            pass
    
    class People(Animal): #动物的形态之一:人
        def talk(self):
            print('say hello')
    
    class Dog(Animal): #动物的形态之二:狗
        def talk(self):
            print('say wangwang')
    
    class Pig(Animal): #动物的形态之三:猪
        def talk(self):
            print('say aoao')
    import abc
    class File(metaclass=abc.ABCMeta): #同一类事物:文件
        @abc.abstractmethod
        def click(self):
            pass
    
    class Text(File): #文件的形态之一:文本文件
        def click(self):
            print('open file')
    
    class ExeFile(File): #文件的形态之二:可执行文件
        def click(self):
            print('execute file')

    2、多态性

    一、什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)

    多态性是指在不考虑实例类型的情况下使用实例
    
    """
    在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),
    不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。 比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
    """

    多态性分为静态多态性与动态多态性

    静态多态性:如任何类型都可以用运算符+进行运算
    动态多态性:如下
    peo=People()
    dog=Dog()
    pig=Pig()
    
    #peo、dog、pig都是动物,只要是动物肯定有talk方法
    #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
    peo.talk()
    dog.talk()
    pig.talk()
    
    #更进一步,我们可以定义一个统一的接口来使用
    def func(obj):
        obj.talk()

    二、为什么要用多态性(多态性的好处)

    1.增加了程序的灵活性
      以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
    
    2.增加了程序额可扩展性
      通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用  

    三、鸭子类型

    """
    Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
    
    python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
    
    也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
    """
    #二者看起来都像文件,因而就可以当文件一样去用,然而它们并没有直接的关系
    class Txt: #Txt类有两个与文件类型同名的方法,即read和write
        def read(self):
            pass
        def write(self):
            pass
    
    class Disk: #Disk类也有两个与文件类型同名的方法:read和write
        def read(self):
            pass
        def write(self):
            pass

     绑定方法和非绑定方法

    """
    类中定义的函数分为两大类:绑定方法和非绑定方法
    
    其中绑定方法又分为绑定到对象的对象方法和绑定到类的类方法。
    
    """

    @classmethod

    """
    在类中正常定义的函数默认是绑定到对象的,而为某个函数加上装饰器@classmethod后,该函数就绑定到了类。
    """

    @staticmethod

    """
    为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法。该方法不与类或对象绑定,类与对象都可以来调用它,但它就是一个普通函数而已,因而没有自动传值那么一说。
    """
    """
    绑定方法:
      特点:绑定给谁就应该由谁来调用,谁来调用就会将自己当做第一个参数传入
    
    
    非绑定方法:
      特点:不与类和对象绑定,意味着谁都可以来调用,但是无论谁来调用就是一个普通函数,没有自动传参的效果
    
    """
    class People:
        def __init__(self,name):
            self.name = name
    
        # 但凡在类中定义一个函数,默认就是绑定给对象的,应该由对象来调用
        # 会将对象当做第一个参数自动传入
        def tell(self):
            print(self.name)
    
        # 类中定义的函数被classmothod装饰过,就绑定给类,应该由类来调用
        # 类来调用就会将类当做第一个参数自动传入
        @classmethod
        def f1(cls):
            print(cls)
    
        # 类中定义的函数被staticmethod装饰过,就成一个非绑定的方法,即一个普通函数,谁都可以调用
        # 无论谁来调用就是一个普通函数,没有自动传参的效果
        @staticmethod
        def f2(x,y):
            pass
    
    p1 = People('egon')
    p1.tell()
    
    p2 = People.f1()  # <class '__main__.People'>
    
    p1.f2(1,2)
    People.f2(1,2)

    总结

    绑定方法与非绑定方法的使用:
    若类中需要一个功能,该功能的实现代码中需要引用对象则将其定义成对象方法、需要引用类则将其定义成类方法、无需引用类或对象则将其定义成静态方法。

    异常处理

    1、什么是异常

    异常是程序发生错误的信号。程序一旦出现错误,便会产生一个异常,若程序中没有处理它,就会抛出该异常,程序的运行也随之终止。在Python中,错误触发的异常如下:

    语法错误

    """
    一种是语法上的错误SyntaxError,这种错误应该在程序运行前就修改正确
    """
    
    >>> if  
      File "<stdin>", line 1
        if
         ^
    SyntaxError: invalid syntax

    逻辑错误

    """
    常见的逻辑错误:
    """
    
    # TypeError:数字类型无法与字符串类型相加
    1+’2# ValueError:当字符串包含有非数字的值时,无法转成int类型
    num=input(">>: ") #输入hello
    int(num)
    
    # NameError:引用了一个不存在的名字x
    x
    
    # IndexError:索引超出列表的限制
    l=['egon','aa']
    l[3]
    
    # KeyError:引用了一个不存在的key
    dic={'name':'egon'}
    dic['age']
    
    # AttributeError:引用的属性不存在
    class Foo:
        pass
    Foo.x
    
    # ZeroDivisionError:除数不能为0
    1/0

    2、异常处理

    为了保证程序的容错性与可靠性,即在遇到错误时有相应的处理机制不会任由程序崩溃掉,我们需要对异常进行处理,处理的基本形式为

    try:
        被检测的代码块
    except 异常类型:
        检测到异常,就执行这个位置的逻辑

    完整的异常处理基本形式

    try:
        被检测的代码块
    except 异常类型1:
        pass
    except 异常类型2:
        pass
    ......
    else:
        没有异常发生时执行的代码块

    3、何时使用异常处理

    """
    在了解了异常处理机制后,本着提高程序容错性和可靠性的目的,读者可能会错误地认为应该尽可能多地为程序加上try...except...,这其是在过度消费程序的可读性,
    因为try...except本来就是你附加给程序的一种额外的逻辑,与你的主要工作是没有多大关系的。
    """

    可预知的

    """
     如果错误发生的条件是“可预知的”,我们应该用if来进行”预防”,如下
    """
    
    age=input('input your age>>: ').strip()
    if age.isdigit(): # 可预知只有满足字符串age是数字的条件,int(age)才不会触发异常,
        age=int(age)
    else:
        print('You must enter the number')

    不可预知的

    """
    如果错误发生的条件“不可预知”,即异常一定会触发,那么我们才应该使用try...except语句来处理。例如我们编写一个下载网页内容的功能,
    网络发生延迟之类的异常是很正常的事,而我们根本无法预知在满足什么条件的情况下才会出现延迟,因而只能用异常处理机制了
    """ import requests from requests.exceptions import ConnectTimeout # 导入requests模块内自定义的异常 def get(url): try: response=requests.get(url,timeout=3)#超过3秒未下载成功则触发ConnectTimeout异常 res=response.text except ConnectTimeout: print('连接请求超时') res=None except Exception: print('网络出现其他异常') res=None return res get('https://www.python.org')

    4、扩展

    # 断言
    l = [111,222,333]
    assert len(l) == 3  # 不满足条件则抛出异常
    print('执行后续代码')
    
    # raise
    raise IndexError('索引错误')  # 主动抛出异常
    
    
    # 自定义异常
    class Permission(BaseException):
        pass
    raise Permission('权限错误')
    '''
    Traceback (most recent call last):
      File "E:PYTHONpycharm projectprojectday21异常处理.py", line 14, in <module>
        raise Permission('权限错误')
    __main__.Permission: 权限错误
    
    '''
  • 相关阅读:
    VB.Net计算含日文的字符串长度
    修改三个属性,就可以让错误提示更详细
    ORACLE分页查询SQL语法
    探知主力仓位的原理
    Linux手机研发五大难关
    当SNS在垂直行业嫁接电子商务
    实时股票数据获取方式
    商业周刊:网民周末更爱移动Web 分类信息超社交
    Linux下的C编程实战
    想起了赵丹阳
  • 原文地址:https://www.cnblogs.com/ZhZhang12138/p/14868570.html
Copyright © 2011-2022 走看看