面向对象基础篇1

1. 什么是面向对象?

对于面向对象编程,我们首先要知道的是什么是面向对象,然后才能运用面向对象去解决实际的问题。而面向对象是一种抽象化的编程思想,在很多编程语言中都有这个概念。在面向对象的思想中,我们强调万物皆对象,即现实世界中的所有事物都可以被抽象为程序中的对象,从而更好地实现程序的设计和开发。对于以往的函数编程,将一个功能设计为一个函数,需要使用该功能的时候调用函数就完成了,这种方法强调将计算看作是对数学函数的求值,注重函数的组合和复用,由于需要避免副作用和状态的变化,该方法倡导使用不可变数据和纯函数,这对于复杂的程序和功能来说较难实现。而面向对象编程以对象为核心,将数据和操作数据的方法封装在对象中,通过对象之间的交互来实现程序的功能,该方法强调数据和行为的封装,以及继承、多态等概念,且状态是可以改变的。

2. 类和对象

2.1 理解类和对象

了解了面向对象之后,我们要知道,面向对象有两个核心的概念,即类和对象,那它们究竟是什么。类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用。而对象是由类创建出来的一个具体的存在,可以直接使用。就概念而言类和对象都是比较抽象的,举例说明一下,你可能就会茅塞顿开,比如以“车”来说,我们所见到的实体的车在建造之间需要有一个图纸,而这个图纸就是一个模版,是负责创建对象的,它目前没有条件让车具备行驶的条件,而要想真正意义的开上一辆车,我们就得用这个模版造一辆车出来,而造出来的车就是一个实例对象。这样说来,就得先有图纸(类),再有车(对象),而类定义了对象的属性和方法。

2.2 类的设计

在使用面向对象进行开发时,应该提前分析具体的需求,确定程序中应该包含哪些类!比如在一个植物大战僵尸的demo中,我们会用到下列这些类。

向日葵 豌豆射手 冰冻射手 普通僵尸 铁通僵尸 跳跃僵尸
生命值 生命值 生命值 生命值 生命值/铁通 生命值/竹竿
生产阳光() 发射子弹() 发射冰子弹() 咬() 咬() 咬()

在上面的这些类中,为什么有些没有’()‘,而有些有’()'呢?这是因为对于每个类别,它们都具有生命值这个属性,而铁通僵尸和跳跃僵尸分别具有铁通和竹竿,这些都是实体对象的属性,而带有括号的都是它们的行为,也就是方法。而在程序开发中,要设计一个类,通常都需要满足三个要素:

    1. 类名:这类事物的名字,满足大驼峰命名法,即类名首字母大写,中间没有下划线
    1. 属性:这类事物具有什么样的特征
    1. 方法:这类事物具有什么样的行为

3. 面向对象基础语法

3.1 定义简单的类

面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法。首先我们先定义一个只包含方法的类。具体如下:

class 类名:
    def 方法1(self, 参数列表)pass
        
    def 方法2(self, 参数列表)pass

在该类中,方法的定义和函数几乎是一样的,区别在于类中的方法第一个参数必须是self

3.2 创建对象

当一个类定义完成之后,要使用类来创建对象,语法格式如下:

对象变量 = 类名()

这样就创建了一个实例对象并且用变量来接收。

3.3 案例一

3.3.1 创建单个对象

现在,我们已经知道了面向对象的基础语法以及用简单类来创建对象,接下来,我们通过实际的案例来熟练掌握。

需求:

  • 现在有一只猫,它喜欢吃鱼,并且小猫要喝水。请用类来实现该程序

分析:

  • 需要一个猫类Cat
  • 该类有两个方法:eat和drink
  • 没有属性
Cat
eat
drink

确定需求并且对功能分析完成之后我们可以进行程序的设计了,具体如下:

# 定义猫类
class Cat:

    def eat(self):
        print('小猫爱吃鱼')


    def drink(self):
        print('小猫喝水')

# 创建对象
tom = Cat()
tom.eat()
tom.drink()
print(tom)

在上述程序中,我们首先定义了一个猫类(Cat),然后实例化了一个对象tom,此处的tom其实是一个变量,它接收了Cat()对象的相关信息,程序运行结果如下:

小猫爱吃鱼
小猫喝水
<__main__.Cat object at 0x000001E9CFB02B60>

其中,__main__表示这个类 Cat 是在当前执行的主模块里定义的, Cat是类的名字, object at 0x000001E9CFB02B60表示这个对象在内存中的地址(十六进制),每个对象都有一个唯一的内存地址。

3.3.2 创建多个对象

现在,我们知道了创建单个对象的程序是怎么样设计的,但是实际情况当中我们需要创建多个对象,而创建对象是比较简单的操作,在上述程序中只需要再实例化一个猫对象即可:

...
tom = Cat()

# 再实例化一个对象tom2
tom2 = Cat()

这样通过同一个类创建出来的对象tom2tom具有相同的方法,但它们并不是同一只猫,它们的地址是不同的。我们打印引用的地址可以看到:

小猫爱吃鱼
小猫喝水
<__main__.Cat object at 0x0000020C166A2B30>
小猫爱吃鱼
小猫喝水
<__main__.Cat object at 0x0000020C166A2B60>

它们具有相同的方法,但是地址是不一样的。

3.3.3 设置属性

对于上述创建两个猫对象,我们在定义类的时候指定了它的两个方法,而基于该类创建的两个猫对象并没有属性,而实际上猫既有行为,也有属性,现在我们给这些对象添加属性。

要给python对象设置属性非常的容易,只需要在类的外部通过.的方式添加属性,但是这种方法不推荐,因为对象的属性应该在定义类的时候封装在类的内部。

tom.name = "汤姆"
...
tom2.name = "汤姆2"

若要利用self在类封装的方法中输出对象的属性,即使用self在方法内部输出每一只猫的名字,那么由哪一个对象调用的方法,方法内部的self就是哪一个对象的引用,在类封装的方法内部self就是当前调用方法的对象自己,在调用方法时,不需要传递self参数。在方法内部:

  • 1.可以通过self.访问对象的属性
  • 2.可以通过self.调用其他对象方法

在我们上面的Cat类可以进行如下改动:

class Cat:
    def eat(self):
        print("%s 爱吃鱼" % self.name)

tom = Cat()
tom.name = "tom"
tom.eat()
tom2= Cat()
tom2.name = "tom2"
tom2.eat()

当然,对于改造后的程序依然有很大的问题,我们可以从程序执行的顺序就可以看到给对象设置属性name是在调用方法之前,若在调用方法之后设置属性:

class Cat:
    def eat(self):
        print("%s 爱吃鱼" % self.name)

tom = Cat()
tom.eat()
tom.name = "tom"

那么在程序执行到eat方法时会抛出异常AttributeError: 'Cat' object has no attribute 'name'。这是因为我们的对象属性还没有设置就进行调用,这种类的外部设置属性是非常不推荐的,应该将属性和方法一起封装到类的内部。

3.4 初始化方法

在类的内部封装属性和方法,我们需要一个初始化方法来设置属性。当我们使用类名来创建对象时,会自动执行以下操作:

  • 1.为对象在内存中分配空间——>创建对象
  • 2.为对象的属性设置初始值——>初始化方法(init)

这个初始化方法就是对象的内置方法__init__,该方法是专门用来定义一个类具有哪些属性的方法!
现在我们重新定义一个猫类,查看一下该方法有何作用。

class Cat:

    def __init__(self):
        print('这是一个初始化方法')
tom = Cat()

创建对象后发现程序有如下输出结果:

这是一个初始化方法

这说明初始化方法在创建对象时会被自动调用。既然在创建对象时会自动1调用类里面的初始化方法,那我们就可以将对象的属性封装在初始化方法中,这样就避免了我们在类的外部进行封装而存在的程序隐患。

在初始化方法中设置属性通过self.属性名 = 属性初始值即可定义属性,在定义属性之后,在使用类创建的对象就会拥有该属性。在我们的猫类中设置名字属性可以进行如下操作:

class Cat:

    def __init__(self):
        self.name = 'Tom'

    def eat(self):
        print(f'{self.name}爱吃鱼')

tom = Cat()
tom.eat()

这样在初始化方法中定义了猫的名字属性,这样我们在创建对象的时候该对象就有了名字,在调用eat方法时就会打印Tom爱吃鱼

现在我们成功在类的内部封装了对象的属性,但很明显这样的写法依然有问题,如果我们不止一只猫,而是两只或者多只,那么我们创建的猫对象都是具有名字属性且值为tom,但是不可能每一只猫的名字都是一样的。所以我们应该对刚才的属性设置方法进行改动,利用初始化方法的参数来设置属性的初始值,后面每个新创建的对象就重新分配该对象的属性即可。

class Cat:
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f'{self.name} 爱吃鱼')

tom = Cat("汤姆")
tom2 = Cat("汤姆2号")
tom.eat()
tom2.eat()

这样我们就可以在初始化对象的时候设置初始值。

3.5 内置方法和属性

序号 方法名 类型 作用
01 __del__ 方法 对象被从内存中销毁前,会被自动调用
02 __str__ 方法 返回对象的描述信息,使用print()打印查看

3.5.1 __del__方法

在python中,当我们使用类名()创建对象时,为对象分配完内存空间之后,自动调用__init__方法,而当一个对象被从内存中销毁前,会自动调用__del__方法。

  • 应用场景

    • __init__改造初始化方法,可以让创建对象更加灵活
    • __del__如果希望在对象被销毁前,想再做什么,就可以调用该方法
  • 生命周期

    • 一个对象从类名()创建,生命周期开始
    • 一个对象的__del__方法一旦被调用,生命周期结束
    • 在对象的生命周期中,可以访问对象的属性或者让对象调用方法
class Cat:
    def __init__(self, name):
        self.name = name
        print(f'{self.name}来了')

    def __del__(self):
        print(f'{self.name}去了')
      

tom = Cat("Tom")
print(tom.name)

输出结果如下:

Tom来了
Tom
Tom去了

可以看到,我们没有主动调用__del__方法,但是却打印了该方法的内容。

3.5.2 __str__方法

前面我们已经说过,在Python中使用print直接打印对象变量,默认会输出该变量引用的对象是由哪一个类创建的对象,以及该对象在内存中的地址(十六进制表示)。
若是希望使用print输出自定义内容,就可以利用__str__来进行改造。
需要注意的是,该方法必须返回一个字符串。

class Cat:
    def __init__(self, name):
        self.name = name
        print(f'{self.name}来了')

    def __del__(self):
        print(f'{self.name}去了')

    def __str__(self):
        return f'{self.name}'


tom = Cat("Tom")
print(tom)

结果为:

Tom来了
Tom
Tom去了

你可能感兴趣的:(python基础,python)