python-面向对象进阶

python-面向对象进阶

三大特性:继承,多态,封装

1,初识继承

  继承指的是类与类之间的关系,是一种什么“是”什么的关系,继承的功能之一就是用来解决代码重用问题。

  继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可以成为基类或超类,新建的类称为派生类或子类。

# 父类/基类/超生类
# 子类/派生类(继承父类)
# _bases_则是查看所有继承的父类

  代码示例如下:

class ParentClass1:
    pass
class ParentClass2:
    pass
class SubClass1(ParentClass1):
    pass
class SubClass2(ParentClass1,ParentClass2):
    pass

print(SubClass1.__bases__)
print(SubClass2.__bases__)

###
(<class '__main__.ParentClass1'>,)
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

2,继承与抽象(先抽象再继承)

 抽象即抽取类似或者说比较像的部分。

 抽象分成两个层次:

  1.将奥巴马和梅西这俩对象比较像的部分抽取成类;

  2.将人,猪,狗这三个类比较像的部分抽取成父类。

  抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

 

 继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

  抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

3, 继承与重用性

 继承与重用性

 在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时

 我们不可能从头开始写一个类B,这就用到了类的继承的概念。

 通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用

class Hero:
    def __init__(self, nickname, life_value, aggressivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggressivity = aggressivity
    def attack(self, enemy):
        enemy.life_value -= self.aggressivity

class Gailen(Hero):
    pass

class Riven(Hero):
    pass

gailen = Gailen('草丛伦', 120, 40)
print(gailen.nickname, gailen.life_value, gailen.aggressivity)  # 这里能实现,说明了它是继承了Hero的属性

 提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大节省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.

 再说属性查找

  查找顺序:对象自己 → 对象自己的类 → 父类

# 查找顺序:对象自己 → 对象自己的类 → 父类
class Foo:
    def f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.f1()  # b.f1()

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

b = Bar()
b.f2()

###
Foo.f2
Bar.f1

4,派生

 当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

class Hero:    
    def __init__(self, nickname, life_value, aggressivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggressivity = aggressivity
    def attack(self, enemy):
        enemy.life_value -= self.aggressivity

class Riven(Hero):
    camp='Noxus'
    def attack(self,enemy):  #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
        print('from riven')
    def fly(self): #在自己这里定义新的
        print('%s is flying' % self.nickname)

 在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要其传值。

class Riven(Hero):
    camp='Noxus'
    def __init__(self,nickname,aggressivity,life_value,skin):
        Hero.__init__(self,nickname,aggressivity,life_value) #调用父类功能
        self.skin=skin #新属性
    def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
        Hero.attack(self,enemy) #调用功能
        print('from riven')
    def fly(self): #在自己这里定义新的
        print('%s is flying' %self.nickname)

r1=Riven('锐雯雯',57,200,'比基尼')
r1.fly()
print(r1.skin)

'''
运行结果
锐雯雯 is flying
比基尼

'''

5,继承的实现原理

1,方法解析顺序(MRO)列表

  python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如

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):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性

"""
from D
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
"""

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

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

  在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如果继承了多个父类,那么属性的查找方式有两种,分别是:深度优先和广度优先

2,区别新式类和经典类

# 在python2中-->经典类:没有继承object的类,以及它的子类都称经典类
class Foo:
    pass
class Bar(Foo):
    pass

# 在python2中-->新式类:继承object的类,以及它的子类都称之为新式类
class Foo(object):
    pass
class Bar(Foo):
    pass

# 在python3中-->新式类:一个类没有继承object类,默认就继承了object
class Foo():  # --> class Foo(object):  
    pass
print(Foo.__bases__)

###
(<class 'object'>,)

3,深度优先和广度优先方式查找

            从左边开始就一直走到底,然后后面再这样一直轮下去

 

            新式类不会走到头,快要到头的时候折返回来往另外一个找,最后一个爹一条路走到底

  代码示例

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):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性

#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类

  print(F._mro_)执行结果

(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

4,子类重用父类的方法或属性

   在子类派生出的新方法中,往往需要重用父类的方法,我们有两种方式实现

 1,法一:指名道姓(不依赖继承)

    指名道姓,不依赖继承,即父类名.父类方法()

class Hero:
    def __init__(self, nickname, life_value, aggressivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggressivity = aggressivity
    def attack(self, enemy):
        enemy.life_value -= self.aggressivity

class Gailen(Hero):
    camp = 'Demacia'
    def attack(self, enemy):
        Hero.attack(self, enemy)  # 指名道姓
        print('from Gailen Class')

class Riven(Hero):
    camp = 'Noxus'

gailen = Gailen('草丛伦', 100, 30)
riven = Riven('锐雯雯', 80, 50)
print(riven.life_value)
gailen.attack(riven)
print(riven.life_value)

"""
80
from Gailen Class
50
"""

  

class Hero:
    def __init__(self, nickname, life_value, aggressivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggressivity = aggressivity
    def attack(self, enemy):
        enemy.life_value -= self.aggressivity

class Gailen(Hero):
    camp = 'Demacia'
    def __init__(self, nickname, life_value, aggressivity, weapon):
        # self.nickname = nickname
        # self.life_value = life_value
        # self.aggressivity = aggressivity
        Hero.__init__(self, nickname, life_value, aggressivity)

        self.weapon = weapon
    def attack(self, enemy):
        Hero.attack(self, enemy)  # 指名道姓
        print('from Gailen Class')

gailen = Gailen('草丛伦', 100, 30, '大宝剑')
print(gailen.__dict__)

"""
{'nickname': '草丛伦', 'life_value': 100, 'aggressivity': 30, 'weapon': '大宝剑'}
"""

 2,法二:super()(依赖继承)

    super(),依赖继承

# 方式二
class Hero:
    def __init__(self, nickname, life_value, aggressivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggressivity = aggressivity
    def attack(self, enemy):
        enemy.life_value -= self.aggressivity

class Gailen(Hero):
    camp = 'Demacia'
    def attack(self, enemy):
        super(Gailen, self).attack(enemy)  # 依赖继承,super(自己的类名,self)
        print('from Gailen Class')

class Riven(Hero):
    camp = 'Noxus'

gailen = Gailen('草丛伦', 100, 30)
riven = Riven('锐雯雯', 80, 50)

print(riven.life_value)
gailen.attack(riven)
print(riven.life_value)

"""
80
from Gailen Class
50
"""

  在python3中super()中已经默认传入参数,可以不用传参数

class Hero:
    def __init__(self, nickname, life_value, aggressivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggressivity = aggressivity
    def attack(self, enemy):
        enemy.life_value -= self.aggressivity

class Gailen(Hero):
    camp = 'Demacia'
    def __init__(self, nickname, life_value, aggressivity, weapon):
        # self.nickname = nickname
        # self.life_value = life_value
        # self.aggressivity = aggressivity
        super().__init__(nickname, life_value, aggressivity)  # 在python3中默认可以不用在super中写入参数

        self.weapon = weapon
    def attack(self, enemy):
        Hero.attack(self, enemy)  # 指名道姓
        print('from Gailen Class')

gailen = Gailen('草丛伦', 100, 30, '大宝剑')
print(gailen.__dict__)

"""
{'nickname': '草丛伦', 'life_value': 100, 'aggressivity': 30, 'weapon': '大宝剑'}
"""

 3,super()依赖mro列表查找

  这两种方式的区别是:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super仍然会按照mro继续往后查找。

# A没有继承B,但是A内super会基于C.mro()继续往后找
class A:
    def f1(self):
        print('from A')
        super().f1()
class B:
    def f1(self):
        print('from B')
class C(A,B):
    pass

print(C.mro())
c = C()
c.f1()  # 打印结果:from B

"""
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
from A
from B
"""

6,组合(类的组合)

  软件重用的重要方式除了继承之外还有另外一种方式,即:组合

  组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

1,组合与重用性

  组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同。

  当类之间有显著不同,并且较小的类是较大的类所需要的组件时,这时用组合比较好。

  代码示例

class People:
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

class Teacher(People):
    def __init__(self, name, age, sex, level, salary):
        super().__init__(name, age, sex)
        self.level = level
        self.salary = salary

class Student(People):
    def __init__(self, name, age, sex, class_time):
        super().__init__(name, age, sex)
        self.class_time = class_time

class Course:
    def __init__(self, course_name, course_price, course_period):
        self.course_name = course_name
        self.course_price = course_price
        self.course_period = course_period
    def tell_info(self):
        print('课程名为<%s> 课程价钱为<%s> 课程周期为<%s>' % (self.course_name, self.course_price, self.course_period))

class Date:
    def __init__(self, year, mon, day):
        self.year = year
        self.mon = mon
        self.day = day
    def tell_info(self):
        print('I was born in %s年%s月%s日' % (self.year, self.mon, self.day))

teacher1 = Teacher('luoma', 28, 'male', 10, 3000)
teacher2 = Teacher('laoluo', 38, 'male', 30, 3000)
python = Course('python', 3000, '3mons')
linux = Course('linux', 2000, '4mons')

teacher1.course = python
teacher2.course = python


print(python)
print(teacher1.course)  # teacher1.course == python
print(teacher2.course)
print(teacher1.course.course_name)   # teacher1.course.course_name == Course.course_name

teacher1.course.tell_info()

student1 = Student('laoqiu', 18, 'male', 8)

# 给老师这个对象定制了课程属性,让这个属性指向另一个对象,将老师类和课程类组合到一起
student1.course1 = python
student1.course2 = linux
student1.course1.tell_info()
student1.course2.tell_info()

student1.courses = []
student1.courses.append(python)
student1.courses.append(linux)
print(student1.courses)

# 创建一个学生对象和一个时间类,可以达到这种效果
d = Date(1999, 4, 20)
student1.birth = d
print(student1.birth.tell_info())
print(student1.course1.tell_info())

"""
<__main__.Course object at 0x02CECCF0>
<__main__.Course object at 0x02CECCF0>
<__main__.Course object at 0x02CECCF0>
python
课程名为<python> 课程价钱为<3000> 课程周期为<3mons>
课程名为<python> 课程价钱为<3000> 课程周期为<3mons>
课程名为<linux> 课程价钱为<2000> 课程周期为<4mons>
[<__main__.Course object at 0x02CECCF0>, <__main__.Course object at 0x02CECD10>]
I was born in 1999年4月20日
None
课程名为<python> 课程价钱为<3000> 课程周期为<3mons>
None
"""

  

7,抽象类与归一化设计

1,什么是接口

  你好,给我开个查询接口>>>此时的接口指的是:自己提供给使用者来调用自己功能的方式方法入口,java中的interface使用如下

  java中的interface:

=================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java
/*
* Java的Interface接口的特征:
* 1)是一组功能的集合,而不是一个功能
* 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作
* 3)接口只定义函数,但不涉及函数实现
* 4)这些功能是相关的,都是动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */

package com.oo.demo;
public interface IAnimal {
    public void eat();
    public void run(); 
    public void sleep(); 
    public void speak();
}

=================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口 
package com.oo.demo;
public class Pig implements IAnimal{ //如下每个函数都需要详细实现
    public void eat(){
        System.out.println("Pig like to eat grass");
    }

    public void run(){
        System.out.println("Pig run: front legs, back legs");
    }

    public void sleep(){
        System.out.println("Pig sleep 16 hours every day");
    }

    public void speak(){
        System.out.println("Pig can not speak"); }
}

=================第三部分:Person2.java
/*
*实现了IAnimal的“人”,有几点说明一下: 
* 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,
但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样 * 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,
这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */ package com.oo.demo; public class Person2 implements IAnimal { public void eat(){ System.out.println("Person like to eat meat"); } public void run(){ System.out.println("Person run: left leg, right leg"); } public void sleep(){ System.out.println("Person sleep 8 hours every dat"); } public void speak(){ System.out.println("Hellow world, I am a person"); } } =================第四部分:Tester03.java package com.oo.demo; public class Tester03 { public static void main(String[] args) { System.out.println("===This is a person==="); IAnimal person = new Person2(); person.eat(); person.run(); person.sleep(); person.speak(); System.out.println(" ===This is a pig==="); IAnimal pig = new Pig(); pig.eat(); pig.run(); pig.sleep(); pig.speak(); } } java中的interface

2,为何要用接口

接口提取了一群类共同的函数,可以把接口当做一个函数的集合。

然后让子类去实现接口中的函数。

这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。

归一化的好处在于:

  1,归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

  2,归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合

    ①就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。

    ②再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样

3,模仿interface

  在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念

  可以借助第三方模块:http://pypi.python.org/pypi/zope.interface

  也可以使用继承,其实继承有两种用途

    一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。

    二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能

class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
    def read(self): #定接口函数read
        pass

    def write(self): #定义接口函数write
        pass


class Txt(Interface): #文本,具体实现read和write
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的读取方法')

class Sata(Interface): #磁盘,具体实现read和write
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的读取方法')

class Process(Interface):
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的读取方法')

  上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,这就用到了抽象类

4,抽象类

 1,什么是抽象类

  与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化

 2,为什么要有抽象类

  如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。

     比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。

从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

     从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案 

 3,在python中实现抽象类

#一切皆文件
import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定义抽象方法,无需实现功能
    def read(self):
        '子类必须定义读功能'
        pass

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def write(self):
        '子类必须定义写功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #报错,子类没有定义抽象方法

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的读取方法')

class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的读取方法')

class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的读取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)  

  再来通过另外一个例子看看其中的差别和演变

  先来看看原代码

class People:
    def walk(self):
        print('is walking')

class Pig:
    def run(self):
        print('is running')

class Dog:
    def jump(self):
        print('is jumping')

people1 = People()
pig1 = Pig()
dog1 = Dog()

# 下面每个对象都有相应的走的功能,但是需要调用不用的方法。需要找个类把所有的方法统一起来。
people1.walk()
pig1.run()
dog1.jump()

  但是,这样会造成的困扰就是会增加后面使用者的使用难度,同样是走的动作,但是却有不同的表达

  这时,我们可以使用抽象类,达到归一化

# 通过调用模块,装饰器等,可以实现后面的调用一定要调用Animal里面的方法,否则会报错
import abc
class Animal(metaclass=abc.ABCMeta):  # 抽象类只能被继承,不能被实例化,功能是指规范子类
    all_type = 'animal'
    @abc.abstractmethod
    def run(self):
        pass
    @abc.abstractmethod
    def eat(self):
        pass

# 抽象类不能被实例化,否则会报错
# animal = Animal()  # Can't instantiate abstract class Animal with abstract methods eat, run

class People(Animal):
    def run(self):
        print('people is running')
    def eat(self):
        print('people is eating')

class Pig(Animal):
    def run(self):
        print('pig is running')
    def eat(self):
        print('pig is eating')

class Dog(Animal):

    def run(self):
        print('dog is running')
    def eat(self):
        print('dog is eating')

people1 = People()
pig1 = Pig()
dog1 = Dog()

people1.run()
pig1.run()
dog1.run()
print(people1.all_type)  # 虽然Animal是抽象类,但本质上还是一个类。

  

 4. 抽象类与接口

  抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。

  抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计

8,多态

  多态指的是一类事物有多种形态,比如动物有多种形态:人,狗,猪

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

1,多态性

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

  多态性是指在不考虑实例类型的情况下使用实例,多态性分为静态多态性和动态多态性

  静态多态性:如任何类型都可以用运算符 + 进行运算,1+2=3。

  动态多态性:如下

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()

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

  其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?

  1.增加了程序的灵活性

    以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

  2.增加了程序额可扩展性

       通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用  

>>> class Cat(Animal): #属于动物的另外一种形态:猫
...     def talk(self):
...         print('say miao')
... 
>>> def func(animal): #对于使用者来说,自己的代码根本无需改动
...     animal.talk()
... 
>>> cat1=Cat() #实例出一只猫
>>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
say miao

'''
这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
'''

2,鸭子类型(理论理解)

  逗比时刻:

  Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’

  python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象

也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

  例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法

#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
    def read(self):
        pass

    def write(self):
        pass

class DiskFile:
    def read(self):
        pass
    def write(self):
        pass

  例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系

#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))

#我们可以在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()

len(s)
len(l)
len(t)

  

9,封装

引子

封装有点像拿一个麻袋,把一个东西给装起来,然后封上口子,照这种逻辑来看,封装=‘隐藏’,这种理解还是有点片面

1,先看如何隐藏

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)

# 其实这仅仅这是一种变形操作
# 类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class A:

    __x = 1  # _A__x = 1 # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N

    def __init__(self, name):
        self.__X = 10  # 变形为self._A__X
        self.name = name    # self._A__name = name

    def __foo(self):  # def _A__foo(self):
        print('from _foo')

    def bar(self):
        self.__foo()  # self._A__foo()  # 只有在类内部才可以通过__foo的形式访问到,在类定义的时候,就已经形成了这种格式了
        print('from bar')

a = A('egon')

print(a.__dict__)
print(A.__dict__)
print(a.bar())
# A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形

"""
执行结果
{'_A__X': 10, 'name': 'egon'}
{'__module__': '__main__', '_A__x': 1, '__init__': <function A.__init__ at 0x02C1A390>, 
'_A__foo': <function A.__foo at 0x02C1A3D8>, 'bar': <function A.bar at 0x02C1A420>,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} from _foo from bar None """

 1,这种自动变形的特点:

  1,在类外部无法直接 obj.__AttrName
  2,在类内部是可以直接使用: obj.__AttrName
  3,子类无法覆盖父类__开头的属性

# 子类无法覆盖父类__开头的属性。
class Foo:
    def __func(self):  # _Foo__func
        print('from foo')
    
class Bar(Foo):
    def __func(self):  # _Bar__func  # 子类定义的和父类定义的表面上看起来一样,但是却实质上不一样,根本就不是一个名字
       print('from bar')

  2,这种变形需要注意的问题:

1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N

2、变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形

class B:
    def __init__(self, name):
        self.__name = name
b = B('egon')
print(b.__dict__)
b.__age = 18
print(b.__dict__)
print(b.__age) ### {'_B__name': 'qiuma'} {'_B__name': 'qiuma', '__age': 18}
18

3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

# 正常情况
class A:
    def foo(self):
        print('A.foo')
    def bar(self):
        print('A.bar')
        self.foo()  # b.foo(),访问顺序,先访问自己,然后类,再到父类。self.foo(),父类中存在,先访问父类里面的

class B(A):
    def foo(self):
        print('B.foo')

b = B()
b.bar()

###
A.bar
B.foo

# 定义私有,通过这种方法,调用只能访问类本身里面的变量
class A:
    def __foo(self):  # _A__foo,看上去和子类的__foo长得一样,实则在定义的时候,名称就已经发生了改变了
        print('A.foo')
    def bar(self):
        print('A.bar')
        self.__foo()  # self._A__foo()

class B(A):
    def __foo(self):  # _B__foo 
        print('B.foo')

b = B()
b.bar()

###
A.bar
A.foo

2,封装的意义

1,封装数据属性:

# 封装数据属性:明确的区分内外,控制外部对隐藏属性的操作。
class People:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
    def tell_info(self):
        print('Name:%s Age:%s' % (self.__name, self.__age))
    def set_info(self, name, age):
        if not isinstance(name, str):  # isinstance 什么必须是什么的实例,这里name必须是str
            print('名字必须是字符串类型')
            return
        if not isinstance(age, int):
            print('年龄必须是整数类型')
            return
        self.__name = name
        self.__age = age


p = People('qiuma', 18)
print(p._People__name)  # 硬要访问
p.tell_info()
p.set_info('qiuma', 17)
p.tell_info()

###
qiuma
Name:qiuma Age:18
Name:qiuma Age:17

2,封装方法:隔离复杂度

# 封装方法:隔离复杂度
# 取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
# 对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
# 隔离了复杂度,同时也提升了安全性

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()
              
a = ATM()
a.withdraw()

封装方法的其他举例:

  1. 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
  2. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
  3. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了

提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。

3,封装与可拓展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

class Room:
    def __init__(self, name, owner, weight, length):
        self.name = name
        self.owner = owner

        self.__length = length
        self.__weight = weight
    def tell_area(self):
        return self.__weight * self.__length
    def room_type(self):

        print("%s lives in the %s, the squre is %s" % (self.owner, self.name, self.tell_area()))

r = Room('豪华公寓', 'qiuma', 50, 50)
print(r.tell_area())
print(r.room_type())

4,property的使用

  什么是特性property

  property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

例一:

  BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)

  成人的BMI数值:

  过轻:低于18.5

  正常:18.5-23.9

  过重:24-27

  肥胖:28-32

  非常肥胖, 高于32

  体质指数(BMI)=体重(kg)÷身高^2(m)

  EX:70kg÷(1.75×1.75)=22.86

# 方式一:
class People:
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height

p = People('qiuma',75,1.8)
p.bmi = p.weight/(p.height ** 2)
print(p.bmi)

# 方式二:通过函数,有括号,容易给使用者造成误解
class People:
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height
    def bmi(self):
        return self.weight / (self.height**2)

p1 = People('qiuma',75,1.8)
print(p1.bmi())

###
23.148148148148145
23.148148148148145
方式一,方式二
# 方式三:使用property装饰器特性实现功能
# 实现统一访问,将方法伪装起来,bmi不能赋值(报错),且只能return返回。
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)

p1 = People('qiuma',75,1.8)
print(p1.bmi)
# p1.bmi = 1  # AttributeError: can't set attribute

例二:

将一个要通过计算的属性,封装成一个用户访问就像访问一个数据属性就可以的类型,伪装成一个更简单的方法,方便用户使用

class People:
    def __init__(self, name):
        self.__name = name
    def get_name(self):
        return self.__name

p = People('qiuma')
print(p.get_name())

###
qiuma
未进行改变的

通过property进行封装。

class People:
    def __init__(self, name):
        self.__name = name
    @property  # 访问,查看,将一个要通过计算的属性,封装成一个用户访问就像访问一个数据属性就可以了,伪装
    def name(self):
        return self.__name
    @name.setter  # 修改
    def name(self, val):
        if not isinstance(val, str):
            print('名字必须是字符串类型')
            return
        self.__name = val

    @name.deleter  # 删除
    def name(self):
        print("deleter")
        print('不允许删除')
p = People('qiuma')
print(p.name)  # 访问行为

p.name = 'Qiuma'  # 修改行为
print(p.name)

del p.name

###
qiuma
Qiuma
deleter
不允许删除

为什么要用property

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则,不需要添加多余的括号什么的,直接访问就可以了。

10,绑定方法与非绑定方法

1,类中定义的两大类函数

class Foo:
    def __init__(self, name):
        self.name = name
    def tell(self):  # 绑定对象的方法
        print('名字是 %s '% self.name)

    @classmethod  # 绑定到类的方法,绑定给谁,就应该由谁来调用,谁调用,就把谁当第一个参数传入
    def func(cls):  # cls = Foo
        print(cls)

    @staticmethod  # 非绑定方法,就是一个普通的函数,谁都能用,无自动传进
    def func1(x, y):
        print(x + y)

f = Foo('qiuma')
print(Foo.tell)
print(f.tell)

f.tell()

Foo.func()
print(Foo)

print(Foo.func1)  # 这里证明Foo.func1和f.func1都是普通函数
print(f.func1)
print(Foo.func1(3, 4))
print(f.func1(3, 4))

###
<function Foo.tell at 0x03679420>
<bound method Foo.tell of <__main__.Foo object at 0x036FC910>>
名字是 qiuma 
<class '__main__.Foo'>
<class '__main__.Foo'>
<function Foo.func1 at 0x036794B0>
<function Foo.func1 at 0x036794B0>
7
None
7
None

  

 一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):

  1,绑定到类的方法:用classmethod装饰器装饰的方法。

  为类量身定制
       类.boud_method(),自动将类当作第一个参数传入
     (其实对象也可调用,但仍将类当作第一个参数传入)

  2,绑定到对象的方法:没有被任何装饰器装饰的方法。

  为对象量身定制
      对象.boud_method(),自动将对象当作第一个参数传入
    (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)

 二:非绑定方法:用 staticmethod 装饰器装饰的方法,实际上就是一个普通函数

  不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已,对象和类都可以使用

  注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说

2,绑定方法与非绑定方法的使用

  1,绑定方法的使用

import hashlib
import time
import settings

class People:
    def __init__(self, name, age, sex):

        self.name = name
        self.age = age
        self.sex = sex

    def tell_info(self):  # 绑定到对象的方法
        print('Name:%s Age:%s Sex:%s' % (self.name, self.age, self.sex))

    @classmethod  # 绑定到类的方法
    def from_conf(cls):
        obj = cls(
            settings.name,
            settings.age,
            settings.sex
        )
        return obj

p = People('qiuma', 18, 'male')

# 绑定给对象,就应该由对象来调用,自动将对象本身当做第一个参数传入
# People.tell_info(p)
p.tell_info()

# 绑定到类,就应该有类来调用,自动将类本身当做第一个参数传入
p = People.from_conf()  # from_conf(People)  # 从配置文件里面读取配置进行实例化
# 等价于 p = People('qiumawu', 19, 'female')

p.tell_info()


# name = 'qiumawu'
# age = 19
# sex = 'female'

###
Name:qiuma Age:18 Sex:male
Name:qiumawu Age:19 Sex:female

 2,非绑定方法的使用

import hashlib
import time
import settings
 
class People:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
 
        self.id = self.create_id()
 
    def tell_info(self):    # 绑定到对象的方法
        print('Name:%s Age:%s Gender:%s' % (self.name, self.age, self.gender))
 
    @classmethod
    def from_conf(cls):
        obj = cls(
            settings.name,
            settings.age,
            settings.gender
        )
        return obj
 
    @staticmethod
    def create_id():
        m = hashlib.md5()
        m.update(str(time.time()).encode('utf-8'))
        return m.hexdigest()
 
 
p = People('qiuma', 18, '男')
p1 = People('qiuma1', 19, '男')
p2 = People('qiuma2', 20, '男')
 
# 非绑定方法,不与类或对象绑定,谁都可以调用,没有自动传值一说
print(p.id,'111')
print(p1.id,'2221')
print(p2.id,'331')

###
7871d235ee8ea3e25f16178004e00eb3 111
7871d235ee8ea3e25f16178004e00eb3 2221
7871d235ee8ea3e25f16178004e00eb3 331
7871d235ee8ea3e25f16178004e00eb3 111
4e16523b5ec7f189d45d1b9cda5da01e 2221
4e16523b5ec7f189d45d1b9cda5da01e 331

11,反射

1,引出反射

  python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

  以下代码可以说明没有反射不具备的功能。

class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print('%s is talking' % self.name)

obj = People('qiuma', 18)

print(obj.name)  # 本质obj.__dict__['name']  # 访问数据属性,点后面是一个属性,而不是字符串
print(obj.talk)  # 访问绑定方法

###
qiuma
<bound method People.talk of <__main__.People object at 0x0117EE70>>

  注意:

不能直接调用input里面的内容,它仅仅只是一个字符串
choice = input('>>>: ') # choice = 'name' # 这个name仅仅只是一个字符串
print(obj.choice) # print(obj.'name') # 后面要是一个属性名,不能为字符串

 需要解决的问题:
 能够让用户通过字符串去映射到对象的一个属性身上,所以我们学习python的解决方式

2,通过字符串映射到对象

四个可以实现自省的函数 下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

 1,hasattr(object,name)   

    第一个参数为对象,第二个参数是字符串hasattr(o, name:str)

  判断object中有没有一个name字符串对应的方法或属性

 2,getattr(object, name, default=None)

  得到对象的属性

 3,setattr(x, y, v)

  修改对象的属性

 4,delattr(x, y)

  删除对象的属性

class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print('%s is talking' % self.name)

obj = People('qiuma', 18)

print(obj.name)  # 本质obj.__dict__['name']  # 访问数据属性,点后面是一个属性,而不是字符串
print(obj.talk)  # 访问绑定方法


# 查
print(hasattr(obj, 'name'))  # obj.name  # 实质 obj.__dict__ ['name']
print(hasattr(obj, 'talk'))

# 取得
print(getattr(obj, 'name', None))   # 得到对象的属性
print(getattr(obj, 'talk', None))   # 得到方法的属性

# 修改
setattr(obj, 'sex', 'male')  # 本质:obj.sex = 'male'
print(obj.sex)

# 删除
delattr(obj, 'age')  # del obj.age
print(obj.__dict__)

###
qiuma
<bound method People.talk of <__main__.People object at 0x00F2DD30>>
True
True
qiuma
<bound method People.talk of <__main__.People object at 0x00F2DD30>>
male
{'name': 'qiuma', 'sex': 'male'}

  

3,反射的应用

# 反射的应用
class Service:
    def run(self):
        while True:
            inp = input('>>>: ').strip()  # get a.txt
            cmds = inp.split()  # cmds = ['get', 'a.txt']

            # print(cmds)
            if hasattr(self, cmds[0]):
                func = getattr(self, cmds[0])
                func(cmds)

    def get(self, cmds):
        print('get..............', cmds)

    def put(self, cmds):
        print('put..............', cmds)


obj = Service()
obj.run()

###输入get a.txt或者put a.txt
>>>: get a.txt
get.............. ['get', 'a.txt']
>>>: put a.txt
put.............. ['put', 'a.txt']

  

12,内置方法介

1,isinstance(obj,cls)

isinstance(obj,cls)检查是否obj是否是类 cls 的对象或实例

# isinstance
class Foo(object):
    pass
obj = Foo()
print(isinstance(obj, Foo))

###
True

2,issubclass(sub,super)

 检查sub类是否是 super 类的派生类(子类是不是父类的儿子)

# issubclass
class Foo(object):
    pass
class Bar(Foo):
    pass
print(issubclass(Bar, Foo))

###
True

3,item系列

item系列,把对象做成像字典一样的东西,然后像字典一样去操作

# item系列,把对象做成像字典一样的东西,然后像字典一样去操作
class Foo:  # 将Foo模拟成Dict系列
    def __init__(self, name):
        self.name = name
    def __getitem__(self, item):  # item = 'name'
        return self.__dict__.get(item)  # 这里使用self.__dict__[item],如果找不到这个key,就会报错。

    def __setitem__(self, key, value):
        print('setitem...')
        print(key, value)
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('delitem...')
        print(key)
        del self.__dict__[key]

obj = Foo('qiuma')

# 查看属性
# obj.属性名 # 一般或许对象属性的方法
print(obj['name'])  # 完成obj.name的取值效果

# 设置属性
# obj.sex = 'male'
obj['sex'] = 'male'
# print(obj.__dict__)
# print(obj.sex)

# 删除属性
# del obj.name
del obj['name']
print(obj.__dict__)

###
qiuma
setitem...
sex male
delitem...
name
{'sex': 'male'}

4,__str__

改变对象的字符串显示__str__,__repr__

自定制格式化字符串__format__

__str__方法定义完以后,会在打印对象的时候触发对象下面的__str__方法,将返回的结果(字符串)作为打印的结果,达到返回的不是说明obj是一个对象,而是得到更多有用的信息。

d = dict({'name': 'qiuma'})  # 本质是调dict这个类,然后将参数传进来进行实例化
print(isinstance(d, dict))  # dict就是d的一个对象
# 数据类型就是类
print(d)

class People():
    def __init__(self, name, age):
        self.name = name
        self.age = age
obj = People
print(obj)
# 上面两个print,python自带的类专门定制了打印的效果,打印出来的更加有用
# 为People类定制一个方法,在print对象的时候自动触发对象的一个绑定方法,来返回有用的信息。

class People(): def __init__(self, name, age): self.name = name self.age = age def __str__(self): print('===>str') return '<name:%s,age:%s>' % (self.name, self.age) # 打印obj直接触发__str__,并且打印返回值 obj = People('qiuma', 18) print(obj) # 触发并打印 obj.__str__() ### True {'name': 'qiuma'} <class '__main__.People'> ===>str <name:qiuma,age:18>

5,__del__

析构方法,当对象在内存中被释放时,自动触发执行。

注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__

来比较下下面两个程序的运行顺序

# 程序1
class Foo:

    def __del__(self):
        print('执行我啦')

f1=Foo()
del f1
print('------->')

#输出结果
执行我啦
------->

# 程序2
class Foo:

    def __del__(self):
        print('执行我啦')

f1=Foo()
# del f1
print('------->')

#输出结果
------->
执行我啦


# 对比这两个程序的执行顺序,可以发现如果未执行程序del f1,则对象中的__del__是在最后执行的,这就是因为若没有手动执行del f1,方法__del__会自动帮你在程序结束的时候自动回收空间,而del f1则是自己手动执行空间的回收

典型的应用场景:

创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中

当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源

这与文件处理是一个道理:

# __del__
f = open('settings.py')  # 做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件
del f  # 只回收用户空间的f,操作系统的文件还处于打开状态

# 所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是
f = open('settings.py')
# 读写...
f.close()  # 回收操作系统的资源
# 很多情况下大家都容易忽略f.close,这就用到了with上下文管理


# 模拟打开文件操作过程
class Open:
    def __init__(self, filename):
        print('open file...')
        self.filename = filename

    def __del__(self):  # 在对象被删除的时候会先自动触发这个方法的执行,再把对象删掉
        # 这里还可以写跟资源回收相关的操作
        print('回收操作系统的资源,类似于self.close()操作')

f = Open('settings.py')
# del f  # 手动回收
print('-------end--------')  # 触发了 del f # f.__del__()

  

13,元

1,元类介绍

 1,exec用法

# 储备知识 exec
# 参数1:字符串形式的命令
# 参数2:全局作用域(字典形式),如果不指定默认就使用globals()
# 参数3:局部作用域(字典形式),如果不指定默认就使用locals()
# 可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中

g = {
    'x': 1,
    'y': 2
}
l = {}
exec("""
global x,m
x = 10
m = 100

z = 3
""", g, l)
print(g)
print(l)

 2,何为元类

python中一切皆对象,对象可以怎么用?
1,都可以被引用,赋值给一个变量,x = obj
2,都可以当作函数的参数传入
3,都可以当作函数的返回值
4,都可以当作容器类的元素,l = [func, time, obj, l]

# 类也是对象

class Foo:
pass
obj = Foo()
print(type(obj))
print(type(Foo))

###
<class '__main__.Foo'>
<class 'type'>

  所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化),比如对象obj是调用类Foo得到的,如果一切皆为对象,那么类Foo本质也是一个对象,既然所有的对象都是调用类得到的,那么Foo必然也是调用了一个类得到的,这个类称为元类。
  产生类的类称之为元类,默认所有用class定义的类,他们的元类就是type。
元类type ===实例化===> 类Foo ===实例化===> 对象obj

2,创建类的两种方式

方式一:使用class关键字

# 方式一:class关键字
class Chinese:  # Cinese = type(...)
    country = 'China'
    def __init__(self,name, age):
        self.name = name
        self.age = age

    def talk(self):
        print('%s is talking ' % self.name)
# print(Chinese)
obj = Chinese('qiuma',18)
print(obj, obj.name, obj.age)

###
<__main__.Chinese object at 0x0150C490> qiuma 18

方式二:type元类产生

就是手动模拟class创建类的过程:将创建类的步骤拆分开,手动去创建

定义类的三要素
类名 class_name = 'Chinese'
基类们class_bases = (object, )
类的名称空间class_dic,类的名称空间是执行类体代码而得到的
调用type时会依次传入以上三个参数

#准备工作:

#创建类主要分为三部分
  1 类名
  2 类的父类
  3 类体

#类名
class_name='Chinese'

#类的父类
class_bases=(object,)

#类体
class_body="""
country='China'
def __init__(self,name,age):
    self.name=name
    self.age=age
def talk(self):
    print('%s is talking' %self.name)
"""

  步骤一(先处理类体->名称空间):

类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典

class_dic={}
exec(class_body,globals(),class_dic)

print(class_dic)
#{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}

  步骤二:调用元类type(也可以自定义)来产生类Chinese1

Chinese1=type(class_name,class_bases,class_dic) #实例化type得到对象Chinese1,即我们用class定义的类Chinese1
obj1 = Chinese1('qiuma', 18)

print(obj1, obj1.name, obj1.age) print(Chinese1) print(type(Chinese1)) print(isinstance(Chinese1,type)) '''
<__main__.Chinese object at 0x0150C490> qiuma 18 <class '__main__.Chinese'> <class 'type'> True '''

  我们看到,type 接收三个参数:

  • 第 1 个参数是字符串 ‘Chinese1’,表示类名
  • 第 2 个参数是元组 (object, ),表示所有的父类
  • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

补充:若Chinese1类有继承,即class Chinese1(Bar):.... 则等同于type('Chinese1',(Bar,),{})

 3,自定义元类控制类的行为

一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 知识储备:
# 产生的新对象 = object.__new__(继承object类的子类)

# 步骤一:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建

class Mymeta(type):  # 继承默认元类的一堆属性
    def __init__(self, class_name, class_bases, class_dic):
        if '__doc__' not in class_dic or not class_dic.get('__doc__').strip():
            raise TypeError('必须为类指定文档注释')

        if not class_name.istitle():
            raise TypeError('类名首字母必须大写')

        super(Mymeta, self).__init__(class_name, class_bases, class_dic)


class People(object, metaclass=Mymeta):
    country = 'China'

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

    def talk(self):
        print('%s is talking' % self.name)

# 步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用

class People(object,metaclass=type):
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def __call__(self, *args, **kwargs):
        print(self,args,kwargs)


# 调用类People,并不会出发__call__
obj = People('egon', 18)

# 调用对象obj(1,2,3,a=1,b=2,c=3),才会出发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3)
obj(1, 2, 3, a=1, b=2, c=3)  # 打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}

# 总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj

# 步骤三:自定义元类,控制类的调用(即实例化)的过程
class Mymeta(type):  # 继承默认元类的一堆属性
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise TypeError('类名首字母必须大写')

        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

    def __call__(self, *args, **kwargs):
        # self=People
        print(self, args, kwargs)  # <class '__main__.People'> ('egon', 18) {}

        # 1、实例化People,产生空对象obj
        obj=object.__new__(self)


        # 2、调用People下的函数__init__,初始化obj
        self.__init__(obj, *args, **kwargs)


        # 3、返回初始化好了的obj
        return obj

class People(object,metaclass=Mymeta):
    country='China'

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

    def talk(self):
        print('%s is talking' %self.name)

obj=People('egon',18)
print(obj.__dict__)  # {'name': 'egon', 'age': 18}

# 步骤四:
class Mymeta(type):  # 继承默认元类的一堆属性
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise TypeError('类名首字母必须大写')

        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

    def __call__(self, *args, **kwargs):
        # self=People
        print(self, args, kwargs)  # <class '__main__.People'> ('egon', 18) {}

        # 1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj
        obj = self.__new__(self, *args, **kwargs)

        # 2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值
        return obj

class People(object,metaclass=Mymeta):
    country = 'China'

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

    def talk(self):
        print('%s is talking' % self.name)

    def __new__(cls, *args, **kwargs):
        obj=object.__new__(cls)
        cls.__init__(obj, *args, **kwargs)
        return obj

obj=People('egon',18)
print(obj.__dict__)  # {'name': 'egon', 'age': 18}

# 步骤五:基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存
class Mysql:
    __instance = None
    def __init__(self,host='127.0.0.1',port='3306'):
        self.host=host
        self.port=port

    @classmethod
    def singleton(cls,*args,**kwargs):
        if not cls.__instance:
            cls.__instance=cls(*args,**kwargs)
        return cls.__instance


obj1 = Mysql()
obj2 = Mysql()
print(obj1 is obj2)  # False

obj3 = Mysql.singleton()
obj4 = Mysql.singleton()
print(obj3 is obj4)  # True

# 应用:定制元类实现单例模式
class Mymeta(type):
    def __init__(self, name, bases, dic):  # 定义类Mysql时就触发
        self.__instance = None
        super().__init__(name, bases, dic)

    def __call__(self, *args, **kwargs):  # Mysql(...)时触发

        if not self.__instance:
            self.__instance = object.__new__(self)  # 产生对象
            self.__init__(self.__instance, *args, **kwargs)  # 初始化对象
            # 上述两步可以合成下面一步
            # self.__instance=super().__call__(*args,**kwargs)

        return self.__instance
class Mysql(metaclass=Mymeta):
    def __init__(self, host='127.0.0.1', port='3306'):
        self.host = host
        self.port = port


obj1 = Mysql()
obj2 = Mysql()

print(obj1 is obj2)
View Code

14,面向对象的软件

15,异常处理

1,何为异常

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

  错误分为两种:

    1,语法错误:这种错误过不了python解释器的语法检测,在程序执行前就要改正过来

#语法错误示范一
if
#语法错误示范二
def test:
    pass
#语法错误示范三
class Foo
    pass
#语法错误示范四
print(haha)

    2,逻辑错误:

#TypeError:int类型不可迭代
for i in 3:
    pass
#ValueError
num=input(">>: ") #输入hello
int(num)

#NameError
aaa

#IndexError
l=['qiuma','aa']
l[3]

#KeyError
dic={'name':'egon'}
dic['age']

#AttributeError
class Foo:pass
Foo.x

#ZeroDivisionError:无法完成计算
res1=1/0
res2=1+'str'

2,异常的种类

在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类)去标识,一个异常标识一种错误

常见异常

AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的

  

ArithmeticError
AssertionError
AttributeError
BaseException
BufferError
BytesWarning
DeprecationWarning
EnvironmentError
EOFError
Exception
FloatingPointError
FutureWarning
GeneratorExit
ImportError
ImportWarning
IndentationError
IndexError
IOError
KeyboardInterrupt
KeyError
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeDecodeError
UnicodeEncodeError
UnicodeError
UnicodeTranslateError
UnicodeWarning
UserWarning
ValueError
Warning
ZeroDivisionError
更多异常

3,异常处理

为了保证程序的健壮性与容错性,即在遇到错误时程序不会崩溃,我们需要对异常进行处理,

  1,如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防

AGE=10
while True:
    age=input('>>: ').strip()
    if age.isdigit(): #只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的
        age=int(age)
        if age == AGE:
            print('you got it')
            break

  2,如果错误发生的条件是不可预知的,则需要用到try...except:在错误发生之后进行处理

#基本语法为
try:
    被检测的代码块
except 异常类型:
    try中一旦检测到异常,就执行这个位置的逻辑
#举例
try:
    f=open('a.txt')
    g=(line.strip() for line in f)
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
except StopIteration:
    f.close()

4,try...except...详细用法

1.异常类只能用来处理指定的异常情况,如果非指定异常则无法处理

s1 = 'hello'
try:
    int(s1)
except IndexError as e: # 未捕获到异常,程序直接报错
    print e

2.多分支,被检测的代码块抛出的异常有多种可能性,并且我们需要针对每一种都定制专门的处理逻辑

s1 = 'hello'
try:
    int(s1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)

3.万能异常Exception,被检测的代码块抛出的异常有多种可能性,并且我们针对所有的异常类型都只用一种处理逻辑就可以了。

s1 = 'hello'
try:
    int(s1)
except Exception as e:
    print(e)

4.也可以在多分支后来一个Exception

s1 = 'hello'
try:
    int(s1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)
except Exception as e:
    print(e)

5.异常的其他结构:finally,回收机制使用

s1 = 'hello'
try:
    int(s1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)
#except Exception as e:
#    print(e)
else:
    print('try内代码块没有异常则执行我')
finally:
    print('无论异常与否,都会执行该模块,通常是进行清理工作')

6.主动触发异常:raise  异常类型(值)

try:
    raise TypeError('类型错误')
except Exception as e:
    print(e)


#或者下面程序你不想让用户传入数字当作名字
class People:
  def __init__(self, name, age):
    if not isinstance(name, str):
      raise TypeError('名字必须传入str类型')
    if not isinstance(age, int):
      raise TypeError('年龄必须传入int类型')
  self.name = name
  self.age = age

p = People(2222,18)

###
这时如果用户输入数字当做用户名,就会直接报错了

7.自定义异常

class MynException(BaseException):
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg

try:
    raise MyException('类型错误')
except MyException as e:
    print(e)

8.断言:assert 条件

info = {}
info['name'] = 'qiuma'
info['age'] = 18
assert('name' in info) and ('age' in info)  # 判断上面是否name and age in info.否则报错

9.总结try..except

1:把错误处理和真正的工作分开来

2:代码更易组织,更清晰,复杂的工作任务更容易实现;

3:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;

原文地址:https://www.cnblogs.com/wuqiuming/p/9418262.html