Python基础入门(6)- 面向对象编程

1.初识面向对象

Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本篇随笔将详细介绍Python的面向对象编程。

如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。

1.1.什么是面向对象编程(类)

  • 利用(面向)对象(属性与方法)去进行编码的过程
  • 自定义对象数据类型就是面向对象中的类(class)的概念

刚开始接触编程的朋友可能对面向对象上述两个点可能不是很理解,我们来引入一下面向过程,通过这两者的对比,以大白话的形式阐述来加深理解

同时面向对象编程和面向过程编程也是我们学好一门语言必须要理解透彻的基础

面向过程(Procedure Oriented 简称PO :如C语言):

从名字可以看出它是注重过程的。当解决一个问题的时候,面向过程会把事情拆分成: 一个个函数和数据(用于方法的参数) 。然后按照一定的顺序,执行完这些方法(每个方法看作一个过程),等方法执行完了,事情就搞定了。

面向对象(Object Oriented简称OO :如C++,JAVA,Python等语言):

看名字它是注重对象的。当解决一个问题的时候,面向对象会把事物抽象成对象的概念,就是说这个问题里面有哪些对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法,问题得到解决。

例子讲解

背景:洗衣机洗衣服
面向过程的解决方法 面向对象的解决方法

①执行加洗衣粉方法

①先创建两个对象:“洗衣机”、“人”

②执行加水方法

②针对对象“洗衣机”加入一些属性和方法:

    方法:“洗衣服方法”、“漂洗方法”、“烘干方法”

    属性:“羽绒服清洗”、“漂洗2次”、“烘干时间设置120分钟”

③执行洗衣服方法

③针对对象“人”加入属性和方法:“加洗衣粉方法”、“加水方法”

    方法:“加洗衣粉方法”、“加水方法”

    属性:“洗衣粉+消毒液+柔顺剂”、“加热水”

④执行漂洗方法

④然后执行

  对象人:加洗衣粉方法

  对象人:加水方法

  对象洗衣机:洗衣服方法

  对象洗衣机:漂洗方法

  对象洗衣机.:烘干方法

⑤ 执行烘干方法 解决同一个问题 ,面向对象编程就是先抽象出对象,然后用对象执行方法的方式解决问题

以上就是将解决这个问题的过程拆成一个个方法(是没有对象去调用的),通过一个个方法的执行来解决问题。

面向过程与面向对象的优缺点

面向过程

优点:性能比面向对象好,因为类调用时需要实例化,面向对象频繁调用,性能开销比较大

缺点:没有面向对象易维护、易复用、易扩展

面向对象

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护

缺点:性能比面向过程低



1.2.类的定义与调用

通过关键字 class 来定义一个类;class来声明类,类的名称首字母大写,多单词的情况下,每个单词的首字母大写

通过实例化类,进行类的调用

 1 # coding:utf-8
 2 
 3 class Person(object):
 4     # 类属性
 5     name="student"
 6 
 7     def dump(self):
 8         # 要在函数中调用类属性,就要在属性前添加self进行调用
 9         print(f"{self.name} is dumping")
10 
11 student=Person()        #类的实例化
12 print(student.name)     #通过实例化进行属性的调用
13 student.dump()          #通过实例化进行函数调用

1.3.self

  • self是类函数中的必传参数,且必须放在第一个参数位置
  • self代表类的实例,而非类
  • self是一个对象,代表实例化的变量自身
  • self可以直接通过点来定义一个变量
  • self中的变量与含有self参数的函数可以在类中的任何一个函数内随意调用
  • 非函数中定义的变量在定义的时候不用self
  • 类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self

self的解析与总结

  1. python中的self代表类的实例。

  2. python中只有针对类来说self才有意义。

  3. self只能用在python类的方法中。

  4. 属性:

    (1)如果变量定义在类下面而不是类的方法下面,那这个变量即是类的属性也是类实例的属性。

    (2)如果变量定义在类的方法下面,如果加了self,那这个变量就是类实例的属性,不是类的属性;如果没加self,这个变量只是这个方法的局部变量,既不是类的属性也不是类实例的属性。

  5. 方法

    (1)如果在类中定义函数时加了self,那这个函数就是类实例的方法,而不是类的方法。

    (2)如果在类中定义函数时没有加self,那这个函数就只是类的方法,而不是类实例的方法。

  6. 函数中要调用类属性,需要使用 self.类属性 进行调用

是不是还有点懵,不清楚什么意思?接着往下看,先了解一下已经有哪些名词了,然后通过大量代码加深映像去理解!


1.4.面向对象中常用术语

  • 类(Class)用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例;可以理解是一个模版,通过它可以创建出无数个具体实例
  • 方法:类中的所有函数通常称为方法。不过,和函数所有不同的是,类方法至少要包含一个self参数,类方法无法单独使用,只能和类的对象一起使用
  • 类变量(属性):类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量(属性)定义在方法中的变量,只作用于当前实例的类。
  • 实例变量(属性)在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化:创建一个类的实例,类的具体对象。
  • 对象:类并不能直接使用,通过类创建出的实例(又称对象)才能使用。

面向对象最重要的概念就是类和实例,要牢记类是抽象的模版,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法。

上面标红的常用术语,是截止此处已经看到或者随笔中已经记录的知识点,下面通过大量代码加深大家映像:

 1 # 1.实例化后的对象对类属性进行修改,不会影响模板类中属性的值 
 2 class Person(object):
 3     # 类属性
 4     name="student"
 5 
 6     def dump(self):
 7         # 要在函数中调用类属性,就要在属性前添加self进行调用
 8         print(f"{self.name} is dumping")
 9 
10 student=Person()
11 student.name="teacher"
12 print(student.name)     #teacher
13 print(Person.name)      #student
 1 # 2.类方法无法单独使用,即使是模板类调用,必须也只能和实例化后的对象一起使用
 2 class Person(object):
 3     # 类属性
 4     name="student"
 5 
 6     def dump(self):
 7         # 要在函数中调用类属性,就要在属性前添加self进行调用
 8         print(f"{self.name} is dumping")
 9 
10 student=Person()
11 student.dump()      #student is dumping
12 Person.dump()       #报错
13 '''
14 Traceback (most recent call last):
15   File "D:/WorkSpace/Python_Study/test03.py", line 12, in <module>
16     Person.dump()
17 TypeError: dump() missing 1 required positional argument: 'self'
18 '''
1 # 3.类属性,方法中想要调用必须使用self,否则无法使用;不在方法中,可以直接调用
2 class Person(object):
3     # 类属性
4     name="student"
5     print(name)
6     
7     def dump(self):
8         print(name)
9         print(self.name)

 1 # 4.类中的方法可以相互调用,但是得加self
 2 class Person(object):
 3     name="student"
 4 
 5     def dump(self):
 6         print(f"{self.name} is dumping")
 7 
 8     def run(self):
 9         # 之前有个知识点讲过,类中得函数方法,需要和实例一起使用,self本质就是实例
10         self.dump()
11         # 直接调用报错
12         dump()
13 
14 student=Person()
15 student.run()

 1 # 5.方法中可以调用其他方法的实例属性,前提是实例属性所在的方法要被执行过,才能找到实例属性
 2 #   因此我们一般将实例属性放在构造器__init__中,后面会讲到
 3 class Person(object):
 4     name="student"
 5 
 6     def dump(self):
 7         #局部变量,适用方位只有当前这个方法
 8         age=20
 9         #实例变量
10         self.top=180
11         print(f"{self.name} is dumping")
12 
13     def run(self):
14         self.dump()
15         print(self.top)
16 
17 student=Person()
18 student.run()
19 '''
20 student is dumping
21 180
22 '''
 1 # coding:utf-8
 2 
 3 # 6.实例属性,类模板无法调用访问;实例化后的对象可以调用
 4 #   实例化后的对象,可以通过“对象名.新增变量=新增变量值”的方式来新增变量,一般用的很少,知道即可;也可以通过“类名.新增变量名=新增变量值”的方式来新增变量
 5 class Person(object):
 6     name="student"
 7 
 8     def dump(self):
 9         self.top=180
10         print(f"{self.name} is dumping")
11 
12 student=Person()
13 student.dump()  #实例变量在该方法中,运行该方法,使实例变量生效
14 
15 student.abc=123
16 print(student.abc,student.top)  # 123 180
17 print(Person.top)       # 报错
18 '''
19 Traceback (most recent call last):
20   File "D:/WorkSpace/Python_Study/test03.py", line 17, in <module>
21     print(Person.top)
22 AttributeError: type object 'Person' has no attribute 'top'
23 '''

上面标红术语的基本使用上述代码已经解释了各自概念中的含义,下面针对实例变量和类变量单独细致再区分一下:

  • 类变量:定义在类里面,通过类名或对象名引用,如果是通过对象名引用,会先找有没有这个同名的实例变量,如果没有,引用到的才是类变量,类变量的更新,只能通过类名,比如 Person.name =“李四” ;通过对象来更新类属性只对当前对象生效,前面有相关代码说明
  • 实例变量: 定义在方法里面的变量,一般在构造器__init__里面,只能通过对象名进行引用;实例变量的增加、更新形式,比如self.top = 180

实例变量(相当于Java中的静态属性)

创建方式:self.实例化变量名=变量值
使用方式:实例化后的对象.实例变量名
创建+使用方式
  • 实例变量是构造函数下(一般放在构造函数__init__)的变量带self.变量
  • 实例变量为每个实例本身独有,不可相互调用、新增、修改、删除,不可被类调用、新增、修改、删除
  • 可以访问类变量
  • 如果同时有类变量和实例变量,程序执行时,先访问实例变量,实例变量存在,会使用实例变量,实例变量不存在,会使用类变量
  • 实例改类变量,不可修改,实际是在实例内存里创建了实例变量
  • 新增、修改、删除实例变量n,不会影响到类变量n
  • a实例不能调用b实例的变量;相当于class A ,实例化对象a对self.name赋值为“张三”,然后实例化对象b对self.name赋值为“李四”。a对象中还是张三,是独立的实例变量
  • 实例变量可修改、新增、删除
  • 当实例变量与类变量重名时,优先调用实例变量

类变量:

创建方式:类变量名=变量值
使用方式:
①类.类变量名
②实例化后的对象.类变量名
注意:如果重新赋值等操作,实例化后的对象只对当前对象生效;而类的方式调用修改直接将模板属性改了,其他对象跟着变
创建+使用方式
  • 类变量在class内,但不在class的方法内,存在类的内存里
  • 类变量是该类所有实例共享的变量,但是实例对象只能访问,不可修改,每个实例对象去访问同一个类变量都将得到相同结果【实例名.类变量名】;如果有对象将类属性值改了,再次访问,值变了,而其他对象依旧是原先的值,实际是在当前对象在内存中创建了自己的实例变量,本质上访问的是实例变量,而并非类变量
  • 新增、修改、删除类变量n,不会影响到实例变量n
  • 类无权访问实例名
  • 类变量可修改、新增、删除

代码检验:

 1 # 实验证明
 2 # 1、实例变量为每个实例独有,不可相互调用、新增、修改、删除,不可被类调用、新增、修改、删除
 3 # 2、如果同时有类变量和实例变量,程序执行时,先访问实例变量,实例变量存在,会使用实例变量,实例变量不存在,会使用类变量
 4 # 3、类无法访问实例变量
 5 class Test(object):
 6     name = '类的姓名'  # 类变量
 7     address = '类的地址'
 8 
 9     def __init__(self, name, age, sex):
10         self.name = name  # 实例变量
11         self.age = age
12         self.sex = sex
13 
14     def test1(self):
15         print(self.name, Test.address)
16 
17     def test2(self):
18         pass
19 
20 
21 Test1 = Test('test1实例的姓名', 22, '')
22 Test2 = Test('test2实例的姓名', 33, '')
23 print(Test1.name, Test1.address)#test1实例的姓名 类的地址    当实例属性name和类属性name重名时,优先调用实例属性
24 print(Test2.name, Test2.address)#test2实例的姓名 类的地址    当实例属性name和类属性name重名时,优先调用实例属性
25 print(Test.name)     #类的姓名      类调用的name是类属性,而并非实例属性
26 print(Test.age)      #报错        类调用实例属性报错,实例属性需要和实例对象在一起使用
27 '''
28 Traceback (most recent call last):
29   File "D:/WorkSpace/Python_Study/test03.py", line 26, in <module>
30     print(Test.age)
31 AttributeError: type object 'Test' has no attribute 'age'
32 '''
 1 # 实验证明
 2 # 1、实例变量可修改、新增、删除
 3 # 2、类变量可修改、新增、删除
 4 # 3、新增、修改、删除实例变量n,不会影响到类变量n
 5 # 4、新增、修改、删除类变量n,不会影响到实例变量n
 6 class Test(object):
 7     name = '类的姓名'  # 类变量
 8     address = '类的地址'
 9 
10     def __init__(self, name, age):
11         self.name = name  # 实例变量
12         self.age = age
13 
14     def test1(self):
15         print(self.name, self.address)
16 
17     def test2(self):
18         pass
19 
20 Test1 = Test('test1实例的姓名', 22)
21 Test1.address = 'test1实例的地址'  # 看上去是访问类属性,实际是新增实例变量
22 print(Test1.address)    #test1实例的地址
23 print(Test.address)     #类的地址
24 print(Test1.age)        #22
25 Test1.age = 11
26 print(Test1.age)        #11
27 Test.age = 30  # 新增类变量
28 print(Test1.age)        #11
29 print(Test.age)         #30
30 print(Test.address)     #类的地址
31 Test.address = '中国'
32 print(Test.address)     #中国

1.5.类的构造函数

类的构造函数(构造器):类中的一种默认函数,用来将类实例化的同时,将参数传入类中;相当于起到这个类初始化的作用

 1 # coding:utf-8
 2 
 3 class Test(object):
 4 
 5     def __init__(self,a,b): #注意:这边除了self默认的,你定义了几个,实例化该类的时候,你就要传递几个参数
 6         a = a
 7         self.b=b
 8         print(f"构造函数运行了,需要传递两个参数,{a}是局部变量,适用范围是构造函数内;{self.b}是实例变量,适用于整个类")
 9 
10 test01=Test(a="A",b="B")    #构造函数运行了,需要传递两个参数,A是局部变量,适用范围是构造函数内;B是实例变量,适用于整个类
11 test02=Test("C","D")        #构造函数运行了,需要传递两个参数,C是局部变量,适用范围是构造函数内;D是实例变量,适用于整个类

1.6.对象的生命周期

简单了解下,后面进阶篇章会有详细讲解,此处,只需要了解图示即可,实例化一个类的时候,对象的生命就开始诞生,代码执行完或者对象不在用时,python的__del__会自动对对象进行回收

 

2.类中的私有函数和私有变量

什么是私有函数私有变量:

  • 无法被实例化后的对象调用的类中的函数和变量
  • 只在类的内部可以调用私有函数和变量
  • 只希望类内部业务调用使用,不希望被使用者调用的场景

私有函数与私有变量的定义方法:

定义方法:在变量或函数前添加__(两个下横线),即为私有变量或函数

 1 # coding:utf-8
 2 
 3 class Person(object):
 4     def __init__(self,name):
 5         self.name=name
 6         self.__age=20
 7     def dump(self):
 8         print(self.name,self.__age)
 9     def __cry(self):
10         print(self.name+"is crying")
11 
12 student=Person("张三")
13 student.dump()        #张三 20
14 print(student.__age)  #报错:AttributeError: 'Person' object has no attribute '__age'
15 student.__cry()     #报错:AttributeError: 'Person' object has no attribute '__cry'

发现实例化后的对象无法调用私有函数与私有变量,但是我就是想调用怎么办?格式: 对象._类名__方法()  对象_类名__属性

 1 # coding:utf-8
 2 
 3 class Person(object):
 4     def __init__(self,name):
 5         self.name=name
 6         self.__age=20
 7     def dump(self):
 8         print(self.name,self.__age)
 9     def __cry(self):
10         print(self.name+"is crying")
11 
12 student=Person("张三")
13 student.dump()        #张三 20
14 print(dir(student))     #打印student对象,可以操作哪些变量和方法  '_Person__age', '_Person__cry', '__class__', '__delattr__',
15 student._Person__cry()  #调用私有方法
16 print(student._Person__age)     #访问私有属性

私有函数和私有属性的应用:Python中的封装

  • python 中的封装:将不对外的私有属性或方法通过可对外使用的函数而使用(类中定义私有的,只有类内部使用,外部无法访问)
  • 这样做的主要原因:保护私隐,明确区分内外
 1 # coding:utf-8
 2 
 3 class Parent(object):
 4     def __hello(self,data):
 5         print("hello %s" % data)
 6 
 7     def helloworld(self):
 8         self.__hello("world")
 9 
10 if __name__=="__main__":
11     p=Parent()
12     p.helloworld()      #hello world

 

3.装饰器与类的装饰器

装饰器的作用:可以使我们更加灵活的使用函数,应用场景中最常见的记录日志,就会经常使用装饰器功能;还有屏蔽一些不合法的程序运行,保证代码的安全等等

3.1.什么是装饰器

装饰器本身就是一个函数,它只不过是对另外一个函数进行了一次封装,它能够把那个函数的功能进行一个强化

  • 也是一种函数

  • 可以接收函数作为参数

  • 可以返回函数

  • 接收一个函数,内部对其处理,然后返回一个新的函数,动态的增强函数功能

  • 将C函数在a函数中执行,在a函数中可以选择执行或不执行c函数,也可以对c函数的结果进行二次加工处理

    # coding:utf-8
    
    def a():
        def c():
            print("Hello World")
        return c()
    
    a() #Hello World
    c() #在外部无法调用,报错
    View Code

3.2.装饰器的定义

1     def out(func_args):      #外围函数
2 
3             def inter(*args, **kwargs):   #内嵌函数
4 
5                     return func_args(*args, **kwargs)
6 
7               return inter    #外围函数返回内嵌函数,注意这里的inter函数是不执行的,因为没有()

讲解:

  • 有一个函数func_args和一个装饰器(简单理解为加工作坊)
  • 装饰器分为外围函数和内嵌函数,内嵌函数一般负责加工,加工好后将加工结果返回,注意返回是在与内嵌函数同级返回,内嵌函数里面那个return只是加工过程,我换成print什么的都是可以的;加工结果返回内嵌函数,不需要加(),不用再执行一遍inter再返回,因为已经执行过了,直接返回即可;
  • 将func_args函数(方法)给out装饰器加工,out扔给实际加工的内嵌函数执行加工过程
  • 因为,任何函数和方法都可以给装饰器加工,所以,装饰器不知道你让我加工的函数方法有几个参数,因此,内嵌函数定义了不确定参数,一个为数组一个为字典用户接收需要加工的函数参数

3.3.装饰器的用法

  • 将被调用的函数直接作为参数传入装饰器的外围函数括弧

    # 装饰器
    def a(func):
        def b(*args,**kwargs):
            print(f"需要加工的是:{func(*args,**kwargs)}")
        return b
    
    # 需要加工的函数
    def c (name):
        return name
    
    a(c)("zhangsan")  #需要加工的是:zhangsan
    View Code
  • 将装饰器与被调用的函数绑定在一起

  • @符号+装饰器函数放在被调用函数的上一行,被调用的函数正常定义,只需要直接调用被执行函数即可

     1 # 装饰器
     2 def a(func):
     3     def b(*args,**kwargs):
     4         print(f"需要加工的是:{func(*args,**kwargs)}")
     5     return b
     6 
     7 # 需要加工的函数,@装饰器名,不用加(),这个是常用的,掌握
     8 @a
     9 def c (name):
    10     return name
    11 
    12 c("zhangsan")   #需要加工的是:zhangsan
    View Code
 1 # coding:utf-8
 2 
 3 def check_str(func):
 4     def inner(*args,**kwargs):
 5         print("args:",args,kwargs)
 6         result=func(*args,**kwargs)
 7         if result=='ok':
 8             return 'result is %s' % result
 9         else:
10             return 'result is %s' % result
11     return inner
12 # 装饰器位置要放在需要调用装饰器函数前面,因为python是自上而下执行的
13 @check_str
14 def test(data):
15     return data
16 
17 print(test("ok"))
18 # 参数以元组传给装饰器
19 # args: ('ok',) {}
20 # result is ok
21 print(test(data="no"))
22 # 参数以字典传给装饰器
23 # args: () {'data': 'no'}
24 # result is no

3.4.类的常用装饰器

@classmethod:使类函数可不经实例化而直接被调用

 1 # coding:utf-8
 2 
 3 class Test(object):
 4     def run(self):
 5         print("Test is runing")
 6 
 7     @classmethod
 8     def jump(cls):
 9         print("Test is junming")
10 
11 Test.jump()     #Test is junming,使用了装饰器@classmethod,类可以调用类方法
12 Test.run()      #报错,类无法调用self实例化函数
13 '''
14 Traceback (most recent call last):
15   File "D:/WorkSpace/Python_Study/test01.py", line 12, in <module>
16     Test.run()
17 TypeError: run() missing 1 required positional argument: 'self'
18 
19 '''
 1 # coding:utf-8
 2 
 3 # 1:类的实例化函数中可以调用@classmethod装饰器修饰的类函数;类的实例化对象也是可以调用@classmethod修饰的方法,下面代码未展示,大家可以自己试一下。test.jump()
 4 class Test(object):
 5     name="张三"
 6 
 7     def run(self):
 8         print(f"{self.name} is runing")
 9         self.jump()
10 
11     @classmethod
12     def jump(cls):
13         print(f"{cls.name} is junming")
14 
15 test=Test()
16 test.run()
17 '''
18 执行结果:
19 张三 is runing
20 张三 is junming
21 '''
 1 # coding:utf-8
 2 
 3 # 2:@classmethod装饰器修饰的类函数中无法使用类的实例化函数
 4 class Test(object):
 5     name="张三"
 6 
 7     def run(self):
 8         print("Test is runing")
 9 
10     @classmethod
11     def jump(cls):
12         print(f"{cls.name} is junming")
13         cls.run()
14 
15 Test.jump()
16 '''
17 执行结果:
18 张三 is junming
19 Traceback (most recent call last):
20   File "D:/WorkSpace/Python_Study/test01.py", line 14, in <module>
21     Test.jump()
22   File "D:/WorkSpace/Python_Study/test01.py", line 11, in jump
23     cls.run()
24 TypeError: run() missing 1 required positional argument: 'self'
25 '''

@staticmethod:使类函数可以不经过实例化而直接被调用,调用该装饰器的函数无需传递self或cls参数,且无法在函数内调用其他类函数或类变量(因为没有self或者cls代表当前类)

 1 # coding:utf-8
 2 
 3 '''
 4 1.实例化(self)函数和用@classmethod装饰的函数中可以调用@staticmethod修饰的函数
 5 2.@staticmethod修饰的函数中无法调用类中的其他函数以及类属性
 6 3.@staticmethod修饰的函数和@classmethod装饰的函数一样可以通过(类.方法名)或者(实例化对象.方法名)执行
 7 '''
 8 class Test(object):
 9     name="张三"
10 
11     def run(self):
12         print(f"{self.name} is runing")
13         self.jump()
14         self.run()
15 
16     @classmethod
17     def jump(cls):
18         print(f"{cls.name} is junming")
19         cls.cry()
20 
21     @staticmethod
22     def cry():
23         # print(f"{name} is crying") 无法调用类属性
24         print("Test is crying")
25 
26         # 无法调用其他类函数
27         # jump()
28         # self.run()
29 
30 Test.jump()
31 '''
32 张三 is junming
33 Test is crying
34 '''
35 Test.cry()      #Test is crying
36 
37 test=Test()
38 test.jump()
39 '''
40 张三 is junming
41 Test is crying
42 '''
43 test.cry()      #Test is crying

@property:将类函数的执行免去括弧,类似于调用属性(变量)

 没有了括弧,那我想给@property修饰的函数传参怎么办???再新增一个同名不同参方法函数,使用@方法名.setter进行装饰

# coding:utf-8

#@property修饰的还是self实例函数,因此具有的功能同实例函数,只是对象调用的时候不用加(),对其传参改值的时候要做特殊处理
class Test(object):

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

    @property
    def name(self):
        print (self.__name)

    @name.setter
    def name(self,newname):
        self.__name=newname
        print ("newname: %s" % self.__name)

t1=Test(name="张三")
t1.name     #张三

# 如果我想要修改name的值怎么办,采取类似java方法重载的方式
# 简单讲下方法重载,我的java随笔里面有,大家可以去详细了解下:
#   就是方法名相同的时候,根据参数的个数来调用方法
t1.name="李四"    #newname: 李四
t1.name          #李四

@property修饰的方法函数要传递多个参数的时候怎么办,可以传tuple,list等,@property方法内部去处理

 1 # coding:utf-8
 2 
 3 #@property修饰的方法传递设定多个参数
 4 class Test(object):
 5 
 6     def __init__(self,name):
 7         self.__name=name
 8 
 9     @property
10     def name(self):
11         print (self.__name)
12 
13     @name.setter
14     def name(self,data):
15 
16         # 根据实际需要将传进来的参数进行处理
17         if str(type(data))=="<class 'list'>":
18             for i in range(len(data)):
19                 print(data[i])
20         elif str(type(data))=="<class 'tuple'>":
21             for i in range(len(data)):
22                 print(data[i])
23 
24 
25 t1=Test(name="张三")
26 # 可以传tuple、list等类型数据
27 t1.name=["李四","lisi"]
28 '''
29 输出结果:
30 李四
31 lisi
32 '''
33 t1.name=(6,4,2,3,223,4)
34 '''
35 输出结果:
36 6
37 4
38 2
39 3
40 223
41 4
42 '''

 

4.类的继承与多态

4.1.什么是继承

  • 通过继承基类来得到基类的功能
  • 所以我们把继承的类称为父类或者基类,继承者被称作子类
  • 代码的重用

4.2.父类与子类的关系

  • 子类拥有父类的所有属性和方法
  • 父类不具备子类自有的属性和方法

4.3.python中类的继承

  • 定义子类时,将父类传入子类参数内
  • 子类实例化后可以调用自己与父类的函数与变量
  • 父类无法调用子类的函数与变量

 1 # coding:utf-8
 2 
 3 class Parent(object):
 4     def __init__(self,name,sex):
 5         self.name=name
 6         self.sex=sex
 7 
 8     def talk(self):
 9         return f'{self.name} are walking'
10 
11     def is_sex(self):
12         if self.sex=='boy':
13             return f'{self.name} is a boy'
14         else:
15             return f'{self.name} is a girl'
16 
17 class ChildOne(Parent):
18     def play_football(self):
19         return f'{self.name} are playing football'
20 
21 class ChildTwo(Parent):
22     def play_pingpong(self):
23         return f'{self.name} are playing pingpong'
24 
25 c_one=ChildOne(name="张三",sex="boy")
26 print(c_one.talk())     #张三 are walking
27 print(c_one.play_football())    #张三 are playing football
28 
29 c_two=ChildTwo("王五","girl")
30 print(c_two.is_sex())           #王五 is a girl
31 print(c_two.play_pingpong())    #王五 are playing pingpong
32 
33 p=Parent("李四","boy")
34 print(p.is_sex())               #李四 is a boy
35 print(p.play_football())        #报错,父类无法调用子类的方法
36 '''
37 Traceback (most recent call last):
38   File "D:/WorkSpace/Python_Study/test01.py", line 35, in <module>
39     print(p.play_football())
40 AttributeError: 'Parent' object has no attribute 'play_football'
41 '''

4.4.类的多态

  • 什么是多态:同一个功能的多状态化
  • 多态的背景:为了保留子类中某个和父类名称一样的函数的功能,这时候,我们就用到了类的多态,可以帮助我们保留子类中的函数功能。
  • 多态的用法:子类中重写父类的方法
 1 # coding:utf-8
 2 
 3 #书写一个父类
 4 class XiaomingFather(object):
 5    def talk(self):
 6        print('小明爸爸说了一句话...')
 7 
 8 # 2 书写一个子类,并且继承一个父类
 9 class XiaomingBrother(XiaomingFather):
10    def run(self):
11        print('小明的哥哥在奔跑着..')
12 
13    def talk(self):
14        print('小明哥哥在说话...')
15 
16 class Xiaoming(XiaomingFather):
17    def talk(self):
18        print('哈哈  小明也可以说自己的观点...')
19 
20 #为什么要去多态
21 #为什么要去继承父类
22 #答案: 为了使用已经写好的类中的函数
23 # 为了保留子类中某个和父类名称一样的函数功能, 这时候, 我们就用到了类的多态.
24 # 可以帮助我们保留子类中的函数的功能
25 if __name__ == '__main__':
26    Xiaoming_borther = XiaomingBrother()
27    Xiaoming_borther.talk()      #小明哥哥在说话...
28    father = XiaomingFather()
29    father.talk()                #小明爸爸说了一句话...
30    Xiaoming = Xiaoming()
31    Xiaoming.talk()              #哈哈  小明也可以说自己的观点...

 


4.5.类的super函数

  • super是子类继承父类方法使用的关键字,当子类继承父类后,就可以使用super来调用父类的方法
  • super函数:在类中调用父类的方法,而不是重写,与多态区分开
  • super() 函数是用于调用父类(超类)的一个方法
  • Python3.x 和 Python2.x 的一个区别是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx 
 1 # coding: UTF-8
 2 
 3 class Parent(object):
 4     def __init__(self, p):
 5         print('Hello i am parent', p)
 6 
 7     def talk(self):
 8         print("Parent is talking")
 9 
10 
11 class Child(Parent):
12     def __init__(self, c):
13         print('Hello i am child', c)
14         super(Child,self).talk()
15         super().talk()
16         super(Child,self).__init__(c)
17         super().__init__(c)
18 
19     def talk(self):
20         print("Child is talking")
21 
22 if __name__ == '__main__':
23     child=Child("小明")
24 
25 '''
26 Hello i am child 小明
27 Parent is talking
28 Parent is talking
29 Hello i am parent 小明
30 Hello i am parent 小明
31 '''

 

5.类的多重继承

5.1.什么是多重继承

可以继承多个基(父)类


5.2.多重继承的方法

 1 # coding: UTF-8
 2 
 3 class Tool(object):
 4     def work(self):
 5         return 'tool work'
 6 
 7     def car(self):
 8         return 'car will run'
 9 
10 class Food(object):
11     def work(self):
12         return 'food work'
13 
14     def cake(self):
15         return 'i like cake'
16 
17 # 继承父类的子类
18 class Person(Tool,Food):
19     #继承多个父类时,存在相同方法,会先执行自己类的,如果没有,根据继承的顺序从左至右依次调用
20     #自己类中的>Tool>Food
21     #可以通过python内置函数__mro__查看这个类继承链的顺序
22     pass
23 
24 if __name__=='__main__':
25     p=Person()
26     print(p.car())          #car will run
27     print(p.cake())         #i like cake
28     print(p.work())         #Tool在Food左边先被继承,因此执行Tool类中同名的work方法
29     print(Person.__mro__)   #(<class '__main__.Person'>, <class '__main__.Tool'>, <class '__main__.Food'>, <class 'object'>)

 拓展: __mro__ 查看类继承链的属性

多继承中多个父类存在同名方法需要调用指定类的同名方法怎么办???

 1 # coding:utf-8
 2 
 3 class A(object):
 4     def working(self):
 5         print('Enter A')
 6 
 7 
 8 
 9 class B(object):
10     def working(self):
11         print('Enter B')
12 
13 
14 class C(A,B):
15     def working(self):
16         A.working(self)
17         B.working(self)
18         print('Enter C')
19 
20 
21 if __name__ == '__main__':
22     c = C()
23     c.working()
24 '''
25 Enter A
26 Enter B
27 Enter C
28 '''

5.3.类的多重继承中super的使用方法

super对象是可以作为一个类代理对象,在多重继承时,如何使用super来创建自己需要的父类代理对象呢?

super构造时有三种传参方式:

  • super() 是super(__class__,self)的懒人模式,实际上还是super(Type , obj)的形式

  • super(Type, obj) 

这个方式要传入两个常数,第一个参数type必须是一个类名,第二个参数是一个该类的实例化对象,不过可以不是直接的实例化对象,该类的子类的实例化对象也行。

super会按照某个类的__ mro__属性中的顺序去查找方法,super(Type, obj)两个参数中Type作用是定义在__ mro__数组中的那个位置开始找,obj定义的是用obj所属的类的__ mro__属性

 1 # coding:utf-8
 2 
 3 class A(object):
 4     def __init__(self):
 5         print('Enter A')
 6         print('Leave A')
 7 
 8 
 9 class B(A):
10     def __init__(self):
11         print('Enter B')
12         print('Leave B')
13 
14 
15 class C(A):
16     def __init__(self):
17         print('Enter C')
18         print('Leave C')
19 
20 
21 class D(B, C):
22     def __init__(self):
23         print('Enter D')
24         super(D, self).__init__()
25         super(B, self).__init__()
26         super(C, self).__init__()
27         print('Leave D')
28 
29 if __name__ == '__main__':
30     d = D()
31     print(D.mro())
32     print(C.mro())
33     print(B.mro())
34 
35 '''
36 Enter D
37 Enter B
38 Leave B
39 Enter C
40 Leave C
41 Enter A
42 Leave A
43 Leave D
44 [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
45 [<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
46 [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
47 '''

5.4.菱形继承(钻石继承)

在多层继承和多继承同时使用的情况下,就会出现复杂的继承关系,多重多继承。

其中,就会出现菱形继承。如下图所示:

在这种结构中,在调用顺序上就出现了疑惑,调用顺序究竟是以下哪一种顺序呢?

  • D->B->A->C(深度优先)
  • D->B->C->A(广度优先)

下面我们来解答下这个问题,举个例子来看下:

 1 class A():
 2  def __init__(self):
 3    print('init A...')
 4    print('end A...')
 5  
 6 class B(A):
 7  def __init__(self):
 8    print('init B...')
 9    A.__init__(self)
10    print('end B...')
11  
12 class C(A):
13  def __init__(self):
14    print('init C...')
15    A.__init__(self)
16    print('end C...')
17  
18 class D(B, C):
19  def __init__(self):
20    print('init D...')
21    B.__init__(self)
22    C.__init__(self)
23    print('end D...')
24  
25 if __name__ == '__main__':
26    D()

输出结果:

 1 init D...
 2 init B...
 3 init A...
 4 end A...
 5 end B...
 6 init C...
 7 init A...
 8 end A...
 9 end C...
10 end D...
  • 从输出结果中看,调用顺序为:D->B->A->C->A。可以看到,B、C共同继承于A,A被调用了两次。A没必要重复调用两次。
  • 其实,上面问题的根源都跟MRO有关,MRO(Method Resolution Order)也叫方法解析顺序,主要用于在多重继承时判断调的属性来自于哪个类,其使用了一种叫做C3的算法,其基本思想时在避免同一类被调用多次的前提下,使用广度优先和从左到右的原则去寻找需要的属性和方法。
  • 那么如何避免顶层父类中的某个方法被多次调用呢,此时就需要super()来发挥作用了,super本质上是一个类,内部记录着MRO信息,由于C3算法确保同一个类只会被搜寻一次,这样就避免了顶层父类中的方法被多次执行了,上面代码可以改为:
 1 class A():
 2  def __init__(self):
 3    print('init A...')
 4    print('end A...')
 5  
 6 class B(A):
 7  def __init__(self):
 8    print('init B...')
 9    super(B, self).__init__()
10    print('end B...')
11  
12 class C(A):
13  def __init__(self):
14    print('init C...')
15    super(C, self).__init__()
16    print('end C...')
17  
18 class D(B, C):
19  def __init__(self):
20    print('init D...')
21    super(D, self).__init__()
22    print('end D...')
23  
24 if __name__ == '__main__':
25    D()

输出结果:

1 init D...
2 init B...
3 init C...
4 init A...
5 end A...
6 end C...
7 end B...
8 end D...

可以看出,此时的调用顺序是D->B->C->A。即采用是广度优先的遍历方式。

补充内容

  • Python类分为两种,一种叫经典类,一种叫新式类。都支持多继承,但继承顺序不同。
  • 新式类:从object继承来的类。(如:class A(object)),采用广度优先搜索的方式继承(即先水平搜索,再向上搜索)。
  • 经典类:不从object继承来的类。(如:class A()),采用深度优先搜索的方式继承(即先深入继承树的左侧,再返回,再找右侧)。
  • Python2.x中类的是有经典类和新式类两种。Python3.x中都是新式类。

 

6.类的高级函数

6.1.__str__

若定义了该函数,当print当前实例化对象的时候,会返回该函数的return信息


6.2.__getattr__

当调用的属性或者方法不存在时,会返回该方法定义的信息


6.3.__setattr__

用于拦截当前类中不存在的属性与值


6.4.__call__

用于将一个实例化后的类变成一个函数使用

6.5.实例

 1 # coding:utf-8
 2 
 3 # t.a.b.c链式操作
 4 class Test(object):
 5     def __init__(self,attr=None):
 6         self.__attr=attr
 7 
 8     def __call__(self,name):
 9         return name
10 
11     def __getattr__(self, key):
12         if self.__attr:
13             key='{}.{}'.format(self.__attr,key)
14         else:
15             key=key
16         print(key)
17         return Test(key)
18 
19 t=Test()
20 t1=t.a.b.c.d("zhangsan")
21 print(t1)
22 '''
23 a
24 a.b
25 a.b.c
26 a.b.c.d
27 zhangsan
28 '''
posted @ 2021-12-20 10:42  葛老头  阅读(572)  评论(0编辑  收藏  举报