Python基础语法03:类

学习目标:

  • 了解面向对象编程思想的由来
  • 掌握类的声明和创建方法
  • 掌握对象的创建和使用方法
  • 掌握类的属性和方法定义方法
  • 了解内部类和魔术方法的基本概念
  • 掌握常见的三种类间关系

思维导图
在这里插入图片描述

3.1 面向对象编程概述

根据代码组织方式的不同,编程语言可以分为以下两种。

  • 面向过程语言:面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再依次调用,类似流水线的工作原理。
  • 面向对象语言:面向对象是把构成问题事务分解成各个对象,依靠各个对象之间的交互推动程序执行,进而实现问题的解决。建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在完整解决问题步骤中的行为。

3.1.1 OOP的产生

面向对象编程的由来如下所述:
使用传统的面向过程编程机制构造系统时,在重用、维护、扩展等方面会出现诸多问题,且逻辑过于复杂,代码易读性差。人们开始思考能不能模拟现实环境,以人类解决问题的方法、思路、习惯和步骤来设计相应的应用程序。面向对象的编程思想(Object-Oriented ProgrammingOOP)就产生了。

面向对象其产生的原因主要有以下两点:

  1. 面向过程的编程机制无法解决复杂程序的可维护性和可扩展性问题。
  2. 面向过程的编程机制背离了人们观察和解决问题的基本思路。

简言之,面向过程的编程是根据业务逻辑上到下写代码,典型如C语言等;

面向对象的编程是对变量和函数进行分类和封装,让开发“更快更好更强”,典型如C++JavaC#等。对这两种编程机制,Python均提供了良好支持。
总体而言,Python更偏向于成为面向对象编程的编程语言。

3.1.2 OOP核心思想

面向对象编程的核心思想是把构成问题的各个事物分解成能够完整描述该实体功能的封装类,并通过对象的交互完成问题的解决。

  • 对象作为程序的基本单位,将程序和数据封装于其中,以提高程序的重用性、灵活性和可扩展性。

  • 创建对象模板,而对象是类的实例化。一个类可以创建多个对象。
    在这里插入图片描述

  • 类 :是对实体的抽象,是泛指,比如:动物、植物等。

  • 对象:是类的一个实例,是特例,比如:猫、狗等。
    例如:动物可以对猫的特征和行为进行抽象,然后可以实例化为真实的动物实体。
    在这里插入图片描述
    在采用面向对象思想编程时,可依次采用以下步骤:

    (1)分析哪些动作是由哪些实体发出的;
    (2)定义这些实体,为其增加相应的属性和功能;
    (3)让实体去执行相应的功能或动作。

3.1.3 OOP特征

面向对象的编程机制有以下三大特征:
封装:找到变化并且把它封装起来,就可以在不影响其它部分的情况下修改或扩展被封装的变化部分,这是所有设计模式的基础。封装解决了程序的可扩展性。
继承:子类继承父类,可以继承父类的方法及属性,实现了多态以及代码的重用,解决了系统的重用性和扩展性。
多态:接口的多种不同的实现方式即为多态。接口的主要目的是为不相关的类提供通用处理服务。这实现了系统的可维护性和可扩展性。

3.2 类和对象

3.2.1 类的创建

Python语言中,使用class关键字来创建类,其创建方式如下:

class ClassName(bases):
    # class documentation string 类文档字符串,对类进行解释说明
    class_suite

class是关键字,bases是要继承的父类,默认继承object类。
class documentation string是类文档字符串,一般用于类的注释说明。class_suite是类体,主要包含属性方法

类、属性和方法的命名约定惯例如下:

  • 类名表示实例的抽象,命名时首字母大写
  • 属性使用名词作为名字,比如name、age、weight等;
  • 方法名一般指对属性的操作,其命名规则一般采用动词加属性名称形式,如updataName、updataAge、updataWeight等。 举例如下图:
# 类定义
class People:  # 类名
    name = "张三"  # 属性名

    def undate_name(self, name):# 方法名
        self.name = name  

Python的类分为以下两种:
经典类:Python2.x中类定义的默认方式,不继承object类,其内部由属性和方法组成。经典类定义的典型方式如下图:

# 经典类是指没有继承object类
class A:
    pass

新式类:Python3.x中类定义的默认方式,必须继承object方法,其典型定义方式如下图:

# 新式类是指继承object的类
class A(object):
    pass

新式类修复了经典类的一些bug(如多重继承时的歧义等),并且提供了对类方法静态方法的支持。

在Python3.x中,如果没有显示指明要继承的父类,则默认继承object类。

class A:
    pass


class A():
    pass


class A(object):
    pass

Python3.x中,无需继承时,例子中三种类的创建效果一样。自动继承object类。
新式类添加了一些内置属性和方法,如下所示:

__name__ :属性的名字
__doc__ :属性的文档字符串
__get__(object) :获取对象属性值的方法
__set__(object, value) :设置对象属性值的方法
__delete__(object, value) :删除对象属性的方法 

3.2.2 对象的创建

类创建完之后,就应该创建该类的实例或对象了,该过程称之为实例化。当一个对象被创建后,就包含标识、属性和方法这三个方面的对象特性了。其中,对象标识用于区分不同的对象,属性和方法与类中的成员变量和成员函数相对应

people = People("李四", 20, "50kg")  # 实例化一个对象

如例子所示,对象标识符为people,属性为括号中内容,方法为类中方法

3.2.3 类的属性

Python语言中,属性分为类级别和实例级别两种。实例级别的属性值默认共享类级别的属性值。除非显式进行赋值操作。下面举一个例子来说明。

class A():
    age = 10


obj2 = A()
obj3 = A()

如例子所示,存在三个实例,分别是类实例A和对象实例obj2、obj3。
在情形1中,obj2和obj3这两个对象实例共享类实例A的属性age;

# 情形1
print(obj2.age, obj3.age, A.age)

在情形2中,显示修改了对象实例obj1的属性aaa;

# 情形2
obj2.age+=2
print(obj2.age, obj3.age, A.age)

在情形3中,修改了类实例A的属性aaa。

# 情形3
A.age+=3
print(obj2.age, obj3.age, A.age)

结果如图所示:

情景1:
10 10 10
情景2:
12 10 10 
情景3:
12 13 13

在情形2中已经修改了对象实例obj2的属性值age,其属性值和类实例A的属性值已经独立。而对象实例obj3的属性从来没有修改过,所以它还是和类实例A的属性值保持一致。

Python语言对于属性的设置采用“类.属性 = 值”或“实例.属性 = 值”的形式。如上例中obj2.age += 2等价于obj2.age = obj2.age + 2,该语句包含了属性获取及属性设置两个操作。

Python语言中的属性操作遵循三个规则:
(1)属性的获取是按照从下到上的顺序来查找属性;
(2)类和实例是两个完全独立的对象;
(3)属性设置是针对实例本身进行的。

类的定义由属性和方法组成,属性是对数据的封装,方法则是对类行为的封装。属性按使用范围分为公有属性私有属性,使用范围取决于属性名称。类的属性如下表所示。
在这里插入图片描述
内置属性如下表所示:
在这里插入图片描述
在这里插入图片描述

3.2.4 类的方法

类方法也包括公有方法、私有方法、类方法和静态方法。如下表介绍:
在这里插入图片描述
类的方法举例如图所示:
在这里插入图片描述
类方法和静态方法原理上有以下区别:
(1)静态方法不能使用self的方式调用。
(2)静态方法调用时会预先将类中用到的属性和方法进行加载,而类方法则是随调随用。因此,类方法相比静态方法具有不占资源的优势,但是速度不及静态方法。
(3)静态方法调用类中的属性时需要使用“类名.属性”的格式。

3.2.5 内部类

内部类:类的内部定义的类,主要目的是为了更好抽象现实世界。
一般情况下不使用内部类,这样会使程序结构复杂,但是理解内部类有助于理解模块的调用。
在这里插入图片描述
下面例子中,People类中又定义了Father类和Mother类两个内部类。创建内部类的实例化对象,可以通过外部类的实例化对象调用内部类完成,如Lisi = Zhangsan.Father();也可以直接使用外部类名调用内部类,如Liming = People.Mother()。

class People():
    code = 0

    class Father():
        code = 1

    class Mother():
        code = 2


zhangsan = People()
lisi = zhangsan.Father()  # 第一种实例化方法
print(lisi.code)  # 输出结果:1
liming = People.Mother()  # 第二种实例化方法
print(liming.code)  # 输出结果:2

从例子可以看出,内部类调用有两种方式。
(1)直接使用外部类调用内部类;
(2)先对外部类进行实例化,然后再实例化内部类。

3.2.6 魔术方法

在Python语言中,所有以双下划线“__”包起来的方法,都统称为“魔术方法”。
这些方法在实例化时会自动调用,
比如“_str__()”、“__init__()”、“__del__()”等。
使用魔术方法可以构造出非常优美的代码,比如将复杂的逻辑封装成简单的API等。
魔术方法中的“__init__()”方法一般叫做构造函数,用于初始化类的内部状态和参数。
如果不提供,Python语言会给出一个默认的“__init__()”方法

魔术方法中的“__ del __()”函数叫做析构函数,用于释放对象占用的资源。“__del__()”函数是可选的,
如果不提供,Python语言会在后台提供默认析构函数。

魔术方法中,有些可以实现属性访问控制的功能,如“__getattr__(self,name),
“__setattr__(self,name,value)”方法等。

魔术方法举例:

class People():
    name = "人"

    def __init__(self, n="非洲人"):
        self.name = n
        print("我是构造函数", self.name)  # 重写构造函数

    def __del__(self):
        print("我是析构函数", self.name)  # 重写析构函数


zhangsan = People()
lisi = People("欧美人")
zhangsan.__del__()  # 调用析构函数
print(zhangsan)
del zhangsan
# print(zhangsan) 出现错误,因为del后,对象就不存在了

结果如下:

我是构造函数 非洲人
我是构造函数 欧美人
我是析构函数 非洲人
<__main__.People object at 0x000001EAF4D09358>
我是析构函数 非洲人
我是析构函数 欧美人

如例子所示,对于这些魔术方法,在创建对象时可以自动执行。当对象自动执行析构函数“A.del()”后,对象仍然存在,但是在调用“del A”,后,对象就已经被回收删除,无法再次使用。

3.3 类间关系

3.3.1 依赖关系

用实例方法执行某个功能时,如果需要使用另一个类的实例的方法来完成,则称这两个类之间存在关联关系。下面是个例子

class Person():
    def play(self, tools):
        tools.run()
        print("很开心,能玩游戏了")


class Computer():
    def run(self):
        print("电脑开机,可以运行")


class Phone():
    def run(self):
        print("手机开机,可以运行")


c = Computer()
phone = Phone()
p = Person()
p.play(phone)

结果:

手机开机,可以运行
很开心,能玩游戏了

例子定义了Person、Computer、Phone三个类,在Person类的play()方法中,可以使用Computer类或Phone类的实例作为参数。因为在Person类方法中实现时需要Phone类实例调用方法完成,所以这两个类之间存在依赖关系。

3.3.2 关联关系

一个类的属性类型是另外一个类的类型,则称这两个类之间存在关联关系。根据属性是单值或多值,关联关系又分为一对一关联、一对多关联等。一对一关联举例如下。
在这里插入图片描述
例子中表示的是一对一关联,Boy类中的属性girlFriend是Girl类的实例,这两个类之间存在一对一关联关系。
一对多关联:
在这里插入图片描述
例子中表示的是一对多关联,School类中的属性teach_list是Teacher类的实例集合,这两个类之间存在一对多关联关系。

3.3.3 继承关系

类继承是在已有类基础上构建新类的机制,该新建类也成为子类。子类可以增加新的属性或功能,也可以继承父类的功能。继承所描述的是“is-a”的关系。
通过继承机制,可以复用以前代码,大大提高开发效率。
在这里插入图片描述
如图所示,可以说人是动物,也可以说人继承动物。人、猪、狗是继承者称之为子类或者派生类。动物是被继承者称之为父类或者超类。
在这里插入图片描述
例子中,首先定义了一个People类,接着从该类派生出两个子类Japan和China,然后同时以这两个类为父类,派生出类Ren。一般来说,为防止出现歧义,尽量在类定义时候避免多继承。

在Python语言中使用继承机制时,有以下几点需要注意:

1)子类拥有父类的属性和方法。
(2)子类可以创建自己属性和方法。
(3)子类可以对父类的方法进行覆盖实现。
(4)子类可重新定义父类中的属性。
(5)一个父类可由多个子类继承,一个子类也可继承多个父类。
(6)如果父类定义了__init__()方法,子类也定义了自己的__init__()方法并且还要使用父类的__init__()方法,
子类需要显式调用父类的__init__()方法。如果子类需要扩展父类的初始化行为,可以添加__init__()方法参数。
(7)当继承的多个父类中有相同的属性或方法时,会使用最后一个继承父类的属性或方法。

继承机制的出现,导致父类和子类相同行为出现的可能,以及在实际运行中的动态结果,此为多态。
多态(是从定义角度出发):同一类事物的多种形态,如动物的多种形态:猫、狗、猪等。
多态性(是从使用角度出发):同一种调用方式,不同的执行效果。具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。
多态性依赖于继承机制,并且要定义统一的接口。

在程序语言中,多态实质就是定义了一个函数接口,在这个函数中定义了所有类中通用的功能。根据不同对象的相同调用接口,就得到不同的结果。下面例子是多态的实现。

class Animal():
    def run(self):
        raise AttributeError("子类必须实现这个方法")


class Cat(Animal):
    def run(self):
        print("猫在跑")


class Pig(Animal):
    def run(self):
        print("猪在跑")


class Dog(Animal):
    def run(self):
        print("狗在跑")
a=Cat()
b=Pig()
c=Dog()
a.run()
b.run()
c.run()

输出结果:

猫在跑
猪在跑
狗在跑

这种写法最大的问题就是接口不统一,有没有一种统一接口的写法呢?如下所示:

def func(n):
    n.run()


a = Cat()
b = Pig()
c = Dog()
func(a)
func(b)
func(c)

例子中演示了多态性定义和调用的两种方式。也体现了多态依赖的两个步骤。首先,多态来自同一个父类的继承;其次,为这些不同的子类定义相同的调用接口。
使用多态性有如下优点:
(1)以不变应万变,增加了程序的灵活性。不论对象千变万化,调用方式都相同。
(2)增加了程序的可扩展性。对于子类来说,使用者无需更改调用代码。

3.4 总结

  • 通过介绍面向对象的产生由来和核心思想,使初学者认识面向对象编程的基本概念。
  • 通过介绍Python语言中类和对象定义和使用的基本概念、流程,使得开发者了解Python语言中面向对象编程的基本流程,并初步掌握面向对象编程的方法。
  • 介绍了Python语言面向对象编程时类之间关系的相关知识,包括依赖关系、关联关系、继承关系等。

猜你喜欢

转载自blog.csdn.net/PoGeN1/article/details/124781118