python之类和对象

面向对象概述

面向对象(Object Oriented)是一种设计思想,可以使软件设计更加灵活,并能很好的实现代码复用。

对象

对象是一个抽象的概念,表示任意存在的事物,世间万物皆对象,对象是真实存在的实体,例如一个人。通常将对象分为两个部分,静态部分和动态部分,静态部分被称为属性,任何对象都具备自身属性,这些属性是客观存在的,不能被忽视的,如人的性别,动态部分指的是对象的行为,如对象执行的动作,例如人可以行走

注意:python中,一切都是对象,不仅是具体的事物称为对象,字符串、函数等也是对象

类是封装对象的属性和行为的载体,具有相同属性和行为的一类实体被称为类,例如:把雁群比作大雁类,那么大雁类就具备了翅膀、爪等属性,飞行和睡觉等行为,而一只大雁则被视为大雁类的一个对象

在python 中,类是一个抽象概念,如果定义一个大雁类,那么可以定义每个对象共有的属性和方法,对象是类的实例,类是对象的抽象

面向对象的特点

1、封装:

封装是面向对象的核心思想,将对象的属性和行为封装起来,其载体就是类,类通常会对客户隐藏一些细节,这就是封装的思想,例如,用户使用计算机,只需要通过键盘就可以实现一些功能,至于内部如何实现的无需关心,采用封装思想保证了内部数据结构的完整性,使用该类的用户不能直接看到类中的数据结构,只能执行公开的数据,这样就避免了外部对内部数据的影响,从而提高程序的可维护性。

2、继承:

继承是实现重复利用的重要手段,子类通过继承复用了父类的属性和行为,同时又添加了子类特有的属性和行为

3、多态:

将父类对象应用于子类的特征就是多态,例如创建一个螺丝类,螺丝类有两个属性:粗细和螺纹密度,然后在创建两个类,一个是长螺丝类,一个是短螺丝类,并且他们都继承了螺丝类,这样长螺丝类和短螺丝类不仅具有相同的特征(粗细和螺纹密度相同),同时还具有不同的特征(一个长、一个短),综上所述,一个螺丝类可以衍生出不同的子类,子类继承父类的同时,也具备了自己的特征,并且能够实现不同的效果,这就是多态化的结构。也可以理解为,同样的行为,后代们有多种不同的形态

类的定义和使用

在python中,类表示具有相同属性和方法的对象的集合,在使用类的时候,需要先定义类,然后创建类的实例,通过实例既可以访问类中的属性和方法了

1、定义类:

在python中,类的定义使用class关键字来实现,语法如下:

class ClassName:
    '''类的帮助信息'''  #注释信息
    statement           #类体

说明:

  • ClassName: 类型,通常采用驼峰命名法,首字母大写
  • 类的帮助信息,类似于注释信息
  • statement:类体,主要由类变量、方法、属性等组成,如果定义时没有想好类的具体功能,可以先使用pass语句代替

2、创建类的实例:

通过类名和参数的方式来创建类的实例,语法如下:

ClassName(parameter)

其中,ClassName是必选参数,表示具体的类,parameter是可选参数,当创建一个类时,没有创建__init__()方法,或者当__init__()方法只有一个self参数时。parameter可以省略

例如:定义一个测试类,如图:

注意:上面定义的类名后的括号可写可不写,如果是继承父类,那么括号要写并写上父类的名字,如果不写默认继承object类,但是实例化类的时候后面需要加括号,如果不加括号不会调用__init__()方法

代码执行结果如下:

注意:在python中创建实例不使用new关键字,和其他面向对象语言有区别

3、魔术方法:__init__()

在创建类后,类通常会自动创建一个__init__()方法,该方法是一个特殊的方法,类似于构造方法,每当创建一个类的实例时,python都会自动执行它,__init__()方法必须包含一个self参数,并且必须是第一个参数,self指向实例本身的引用,用于访问类中的属性和方法,在方法调用时会自动传递实际参数self,因此当__init__()方法只有一个参数的时候,创建类的实例时,就不需要指定实际参数了(self指向的就是实例本身)

注意:在__init__()方法的名称中,开头和结尾是两个下划线(中间没有空格),这是一种默认的约定

例如:定义测试类,定义魔术方法,并创建类实例,如图:

代码执行结果如下:

注意:上面的__init__()方法中的第一个参数不一定非要是self,也可以自定义,但是默认习惯上为self

在__init__()方法中,除了self参数,还可以自定义多个参数,参数之间通过逗号分隔,如图:

代码执行结果如下:

创建类的成员并访问

类的成员主要由实例方法和数据成员组成,在类中创建了类的成员后,可以通过类的实例进行访问

1、创建实例方法并访问:

所谓实例方法是指在类中定义的函数,该函数是一种在类的实例上操作的函数,同__init__()方法一样,实例方法的第一参数必须是self,并且必须包含一个self参数,语法如下:

def functionName(self,parameter):
    block
  • functionName:用于指定方法名,一般使用小写字母开头
  • self,必须参数,不一定非要使用self,通常习惯使用self
  • parameter,可选参数,如果有多个用逗号分隔
  • block:方法体,用于实现具体的功能

注意:实例方法和python函数的主要区别是,函数实现的是某个独立的功能,而实例方法是实现类中的一个行为,是类的一部分

实例方法创建完成后,可以通过类的实例名称和点(.)操作符进行访问,如图:

代码执行结果如下:

2、创建数据成员并访问:

数据成员是指在类中定义的变量,即属性,根据定义位置,分为类属性和实例属性

类属性是指定义在类中,并且在函数体外的属性,类属性可以在类的所有实例之间共享值,也就是在所有实例化的对象中公用

类属性可以通过类名称或者实例名访问

(1)、下面的列子定义类属性,并通过实例名和类型访问,如图:

下面的例子表示修改类属性的值,如图:

代码执行结果如下:

从结果可以看出,修改了类属性值后,对其他的实例化后的属性值是有影响的

(2)、还可以在类的函数中定义类属性,如图:

运行结果如下:

实例属性是指定义在类的方法中的属性,只作用于当前实例

例如:下面定义实例属性和实例方法,并访问属性,如图:

代码执行结果如下:

注意:上图中的实例属性要用self.name=”张三”,而不是直接用name=”张三呢”,这是因为用了self后,其余的实例方法都可以进行访问,如果不用,只能在当前方法中使用,因此下面的linshi()方法可以调用上面的name,特别提醒:两个方法中的参数都不一定是self,习惯上写为self,如果第一个写成one,第二个写成two 也是可以的,一般建议写为self

注意:实例属性只能通过实例名访问,不能通过类名访问,否则会报错

对于实例属性也可以通过实例名称修改,与类属性不同,通过实例名修改实例属性后,并不影响该类中其他实例中相应的实例属性的值

例如:定义实例属性,实例化两个类,其中一个类修改实例属性,另一个类再次访问,如图:

从上图中可以看出,test实例修改完实例属性后,通过test1实例再次访问该属性,值并不受影响

访问限制

在类的内部可以定义属性和方法,在类的外部可以直接调用属性或方法来操作数据,从而隐藏了类内部复杂逻辑,但是python并没有对属性和方法的访问权限进行限制,为了保证类内部的某些属性和方法不被外部所访问,可以在属性或者方法名前添加单下划线(_foo)、双下划线(__foo)、或者首尾加双下划线(__foo__),从而限制访问权限,作用说明如下:

  • __foo__:首尾双下划线表示定义特殊方法,一般是系统定义,如__init__()
  • _foo:单下划线开头表示protected(保护)类型的成员,只允许类本身或者子类进行访问,但不能使用from module import * 语句导入
  • __foo:双下划线表示privite(私有)类型的成员,只允许定义该方法的类本身进行访问,子类也不能访问,而且不能通过类的实例进行访问 ,但是可以通过类的实例名.类名.__xxx的方式访问

例如:下面定义一个保护类型的成员属性,并进行访问,如图:

代码执行结果如下:

定义子类,在子类中访问父类的保护类成员属性,如图:

从上图中可以看出,字类可以访问父类中的保护类型成员变量

下面的例子表示定义私有类型成员属性,并访问,如图:

注意:上图中Gongguan前面还有一个下划线_

注意:私有属性可以通过类名.属性名以及实例名.类名__xxx访问,但是不能直接通过实例名.属性名进行访问,否则将报错

属性

本节介绍的属性和之前的类属性和实例属性不同,本节介绍的属性则是一种特殊的属性,访问它时将计算它的值,另外,该属性还可以为属性添加安全保护机制

1、创建用于计算的属性

在python中,可以通过@proprety(装饰器)将一个方法转换为属性,从而实现用于计算的属性,将方法转换为属性后,可以直接通过方法名来访问方法,而不需要在添加一对小括号,这样可以让代码更加简洁,语法格式如下:

@property
def methodname(self):
    block
  • methodname:用于指定方法名,一般使用小写字母开头,该名称最后将作为创建的属性名
  • self:必要参数,表示类的实例
  • block:方法体,实现具体的功能,通常以return语句结束,返回计算结果

例如:定义一个矩形类,在__init__()方法中定义两个实例属性,在定义一个计算矩形面积的方法,使用@property将其转换为属性,最后创建类实例,并访问转换后的属性,如图:

为属性添加安全保护机制

在python中,默认情况下,创建的类属性或者实例属性,是可以在类体外修改的,如果想要限制其不能修改,可将其设置为私有,但是设置为私有后,在类体外就无法获取它的值,如果想要创建一个可以读取,但是不能修改的属性,可以使用@property实现只读属性

例如:创建一个类,并定义一个私有属性,最后实例化并获取属性值,如图:

代码执行结果如下:

从上面可以看出,修改只读属性后,打印输出值报错

通过@property不仅可以将属性设置为只读属性,而且可以为属性设置拦截器,即允许对属性进行修改,但是要遵循一定的约束

继承

继承是面向对象编程最重要的特性之一,在程序设计中实现继承,表示这个类拥有它继承的类的所有公有成员或者受保护的成员,被继承的类称为父类或者基类,新的类称为子类或者派生类

通过继承不仅可以实现代码复用,还可以通过继承来理顺类与类之间的关系,在python中,可以在类定义语句中,类名右侧使用一对小括号将要继承的基类名称括起来,从而实现继承,语法格式如下:

class ClassName(baseclasslist):
    '''类的帮助信息'''
    statement
  • ClassName:表示类名
  • baseclasslist:表示要继承的基类,可以有多个,类名之间用逗号分隔,入股不指定将使用python对象的根类object
  • statement:类体,主要由类变量、方法、属性等定义语句组成,如果没想好可以使用pass代替

1、方法重写:

基类的成员都会被派生类继承,当基类中的某个方法不完全适用于派生类时,就需要在派生类中重写这个方法

例如:定义一个水果类,在该类中定义一个harvest()方法,如果想要针对不同水果给出不同的提示,可以在派生类中重写harvest()方法,如图:

2、派生类中调用基类的__init__()方法

如果你需要在子类的构造器中执行一些额外的初始化工作,或者父类的__init__方法有参数需要被传递,那么你应该在子类的__init__方法中显式地调用父类的__init__方法,在派生类中定义__init__()方法时,不会自动调用基类的__init__()方法

使用super()函数调用父类__init__()方法(推荐用法)

例如:定义一个Fruit类,在__init__()方法中创建类属性color。然后在Fruit类中定义一个harvest()方法,在该方法中输出类属性color的值,在创建继承自Fruit类的Apple类,最后创建Apple类的实例,并调用harvest()方法,如图:

代码执行结果如下:

字类如果要调用父类的构造方法__init__(),需要通过super()函数(推荐用法)实现,如果父类的__init__()中的参数有默认值,那么子类调用的时候可以不指定参数,上图中super().__init__()参数为空,如果color没有默认值,那么子类调用父类__init__的时候就需要指定参数了,否则报TypeError,如图:

注意:使用super().__init__()的时候,不需要显式的指定self参数,因为通过 super() 调用父类方法时,super() 会返回一个类型为 super 的对象,这个对象的调用会自动将当前的 self 参数传递给父类方法。这意味着 super() 抽象了 self 的传递过程,使得你不需要显式地写出 self

使用父类类名调用__init__()方法(不推荐)

修改上面例子,将super()换成类名,并添加self参数,如图:

注意:当使用类型调用__init__()的时候,必须显式的指定self参数

标签