1.8【Python】第八章 面向对象

人工智能入门与实战第一季:python基础语法

什么是面向对象编程

面向对象编程,英文Object-oriented programming,缩写:OOP,是一种编程方式,在程序的开发过程根据实际问题抽象出一个个的对象,然后把对象的方法和属性封装其中,最后以提高软件的复用性、灵活性和扩展性。

目前主流的编程语言基本都是面向对象的编程语言,例如python、java、JavaScript、object-c、php,等等。

当然编程语言并非一开始就有面向对象的编程方式,比如我们熟悉的c语言就是面向过程的语言,随着编程技术的发展,才逐渐出现面向对象的编程方式,因为他更接近于我们人的思维方式,更容易被我们理解,所以它是更高级的编程语言。

除了上述说的两种编程方式,还有我们熟知的函数式编程、响应式编程、面向切面编程等等编程方式,这些编程方式并非是对立的,他们的出现都是为了更好、更方便的开发程序,所以当你在一个程序中同时发现有多种编程方式的时候并不要觉得奇怪。

当然面向对象和面向过程是最基本和最常用的编程方式,接下来我会用一个例子来说明面向过程和面向对象的区别,举的例子并不能完全体现出两者的区别,只是让你有一个直观的感受。

案例:我家有一只猫和一只狗,猫的名字叫猫咪,1岁,狗的名字叫狗狗,2岁,猫咪‘喵喵叫’,狗狗‘旺旺叫’
要求使用python把猫咪和狗狗的信息存储并打印,然后给狗狗换个名字,最后让猫咪叫一声,让狗狗叫一声。

1、使用面向过程的编程方式实现:

# 先分析要实现哪些功能,然后一步一步实现

# 1 存储
cat_dic = {'name' : '猫咪', 'age' : 1}
print(cat_dic)

dog_dic = {'name' : '狗狗', 'age' : 2}
print(dog_dic)

# 2 给狗狗换个名字
dog_dic['name'] = '二哈'
print(dog_dic)

# 3 让猫咪和狗狗叫
print('%s 喵喵喵...' % cat_dic['name'])
print('%s 旺旺旺...' % dog_dic['name'])

输出结果:
{'name': '猫咪', 'age': 1}
{'name': '狗狗', 'age': 2}
{'name': '二哈', 'age': 2}
猫咪 喵喵喵...
二哈 旺旺旺...

2、使用面向对象的编程方式实现:
先分析整个程序中有哪些对象参与:猫、狗

# 1 创建猫和狗的类(模板)
class Cat:
    def __init__(self, name, age):
        self.name = name  # 定义属性
        self.age = age

    def introduction(self):  # 定义方法
        print('%s %d岁' % (cat1.name, cat1.age))

    def say(self):  # 定义方法
        print('喵喵喵...')

   

class Dog:
    def __init__(self, name, age):
        self.name = name  # 定义属性
        self.age = age

    def introduction(self):  # 定义方法
        print('%s %d岁' % (dog1.name, dog1.age))

    def say(self):  # 定义方法
        print('旺旺旺...')

根据定义好的类创建对象,并使用对象调用方法

# 2 根据类(模板)来创建对象
cat1 = Cat('猫咪', 1)
dog1 = Dog('狗狗', 2)

# 3 猫咪自我介绍、狗狗自我介绍
cat1.introduction()
dog1.introduction()


# 4 修改狗狗的名字
dog1.name = '二哈'
dog1.introduction()

# 5 喵咪叫、狗狗叫
cat1.say()
dog1.say()

输出结果:
猫咪 1岁
狗狗 2岁
二哈 2岁
喵喵喵...
旺旺旺...

对比以上两种实现方式,我们先不讨论他们的扩展性、复用性、安全性,直观感觉上我们会觉得第二种编程方式更符合我们人类的思考方式,更容易被我们理解。

好了,以上把面向过程编程和面向对象编程的方式做了一个对比,我们先从直观上感受,接下来我们会详细讲解面向对象编程的内容。

类的定义

我们先来搞清楚什么是类?什么是对象?

我们在上面已经创建了2个类,一个是Cat类,一个是Dog类,我们还说了类就是模板,可以通过类(模板)去创建具体的对象,那么我们上面通过Cat类创建了cat1这个对象(就是我家的猫咪),通过Dog类创建了dog1这个对象(就是我家的狗狗)

类和对象的概念搞清楚了吗?我说“人”,他是类,我说“路边走的那个人”,他就是对象;我说“手机”,它也是类,我说“我兜里的这个手机”它就是对象;所以类是一群具体事物的总称,而对象则是类对应的具体的个体。

一个类包含属性和方法,我们上面的例子中猫的名字name和年龄age就是属性,说话say就是方法(行为)。

定义类的语法格式:

class 类名:
类体

定义类的说明:

  • 我们在前面讲过标识符的命名规则,类名首字母大写,包含多个英文单词时单词首字母大写。
  • 类体中定义属性和方法
  • init()是构造方法,用来初始化对象的属性,默认的第一个参数是self,它代表对象本身

例如我们上面定义的Cat类:

class Cat:
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age

    def introduction(self):  # 实例方法
        print('%s %d岁' % (cat1.name, cat1.age))

    def say(self):  # 实例方法
        print('喵喵喵...')

使用类(模板)创建对象:

cat1 = Cat('猫咪', 1) 

使用”类名([参数列表])”默认会调用init方法,给name、age赋值。

实例属性和实例方法

我们上面说了类体中包含属性和方法,怎么又来了一个实例属性和实例方法?这就说明类中的属性和方法是有不同类型的。

实例属性
实例属性可以理解为对象属性,他是属于每个创建的对象的,一般是定义在init方法中的,上面我们定义的Cat类中的name、age都是实例属性。
我们可以通过“对象.属性名”访问实例属性,例如cat1.name

实例方法
实例方法是对象的方法,语法格式:

def 方法名(self [,参数列表])
    方法体

实例方法调用格式:对象.方法名([参数列表])

实例方法的第一个参数必须是self,之后的参数可以自行定义,调用方法时,第一个参数self无需手动传入,它由系统自动传入。
举例:

# 定义Cat类
class Cat:
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age

    def introduction(self):  # 实例方法
        print('%s %d岁' % (cat1.name, cat1.age))

    def say(self):  # 实例方法
        print('喵喵喵...')


# 通过类创建对象(创建一只猫)
cat1 = Cat('猫咪', 1)

# 通过对象访问实例属性
print(cat1.name)

# 通过对象调用实例方法
cat1.say()

类属性和类方法

上面讲了实例属性和实例方法,他们都是属于对象的,类属性和类方法则是属于类的,可以被所有对象共享,我们来看他们的区别

类属性
定义在init之外的属性,类属性使用的频率非常低,使用更多的还是实例属性。
我们可以通过“类名.属性名”访问类属性

类方法
类方法通过装饰器@classmethod来定义,语法格式:

@classmethod
def 类方法(cls [,参数列表])
    函数体

类方法调用格式:
类名.类方法名([参数列表])

类方法的使用说明:

  • 第一个参数必须是cls,之后的参数可以自行定义,调用方法时,第一个参数cls无需手动传入,它由系统自动传入。
  • 类方法中不能访问实例属性和实例方法

静态方法

在类中使用装饰器@staticmethod修饰的方法,称为静态方法,它和我们之前学习的函数没有太大区别,它仅仅托管于某个类的名称空间中,便于使用和维护。

静态方法定义格式:

@staticmethod
def 类方法(参数列表)
    函数体

静态方法的调用:类名.静态方法、对象.静态方法

举例:

class Cat:
    # 类属性
    count = 0

    # 实例方法
    def __init__(self, name, age):
        # 实例属性
        self.name = name
        # 实例属性
        self.age = age
        # 每次创建一只猫,计数加1
        Cat.count = Cat.count + 1

    # 实例方法
    def say(self):
        print('喵喵喵...')

    # 类方法
    @classmethod
    def print_count(cls):
        # 使用类名访问类属性
        print('一共创建%d只猫' % Cat.count)
        # 类方法中不能访问实例属性和实例方法

    # 定义静态方法
    @staticmethod
    def help():
        print('这里是猫类,你可以创建一只小猫和你玩儿')


cat2 = Cat('mimi' , 3)

# 使用类名调用类方法
Cat.print_count()

# 使用对象调用静态方法
cat2.help()

# 使用类名调用静态方法
Cat.help()

输出结果:
一共创建1只猫
这里是猫类,你可以创建一只小猫和你玩儿
这里是猫类,你可以创建一只小猫和你玩儿

关于实例属性、实例方法和类属性、类方法的区别,可以简单这样来记:前者属于对象,可以使用对象来访问,后者属于类,可以使用类来访问。

私有属性和私有方法

我们知道定义类的时候,类体包含属性和方法,python中默认的属性和方法,外部都是可以访问的,当然我们也可以将属性和方法设置成私有的,以限制外部的访问。

就像你去餐馆吃饭,我们只可以取做好的饭菜,但是餐馆内的食材我们是触及不到的,以保证食物的安全性。几乎所有面向对象的编程语言都对属性和方法设置了访问权限,以保证程序的安全性。

python中如何设置私有属性和私有方法呢?
1、属性和方法名前以两条下划线__开始的为私有属性和私有方法
2、在类的内部可以直接访问私有属性和私有方法
3、在类外部原则上是不可以访问私有属性和私有方法(python没有严格意义上的不可访问,这点和其他语言并不一样)

举例:

class Cat:
    def __init__(self, name, age):
        self.name = name
        # 私有属性
        self.__age = age

    def say(self):
        # 类内部可以访问私有属性
        print('%s %d岁 喵喵喵...' % (self.name, self.__age))

    # 私有方法
    def __hello(self):
        print('hello...')


cat3 = Cat('tom', 1)
cat3.say()
# 类外部正常情况下无法访问私有属性和私有方法
# print(cat3.__age)
# cat3.__hello()

# 可以通过类名来强制访问私有属性(一般我们不这样做)
print(cat3._Cat__age)
cat3._Cat__hello()

输出结果:
tom 1岁 喵喵喵...
1
hello...

面向对象三大特性:封装、继承、多态

封装

比如我们定义的Cat类,有一个say方法,对于外部的使用者来说并不知道内部是怎么实现的,需要的时候调用即可。
再比如我们讲到的私有属性和私有方法,保证了程序的安全性,这些都是面向对象封装性的体现。

继承

还记得本章开始我们定义了两个类,一个Cat类,一个Dog类,我们现在回过头来看会发现他们的属性和方法都一样,都有name和age属性,都有say方法,这些重复相同的代码显然违背了程序设计的理念:代码的复用性,那么通过继承可以完美的解决这个问题。

猫和狗都是动物,所以我们可以抽象出一个Animal类,动物也有名字和年龄,也都会发出声音,所以我们可以这样写:

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

    def say(self):
        print('%s 叫...' % self.name)

接下来我们创建Cat类和Dog类,但这次不同的是他们都要继承Animal:

# 注意继承的语法格式
class Cat(Animal):
    # 使用pass表示不做任何事,以后可以在此继续添加代码
    pass

class Dog(Animal):
    pass

cat4 = Cat('tom', 1)
cat4.say()

dog4 = Dog('jerry', 2)
dog4.say()

输出结果:
tom 叫...
jerry 叫...

可以看到Cat类和Dog类没有写任何额外代码,只是继承了Animal,就拥有了Animal的所有属性和方法,这就是继承的魅力。

如果父类中的属性和方法无法满足子类,可以在子类中添加额外的属性和方法:

class Dog(Animal):
    def __init__(self, name, age, hair_color):

        # 新增毛发颜色属性
        self.hair_color = hair_color
        self.name = name
        self.age = age

        # 也可以调用父类的__init__方法对name和age赋值
        # Animal.__init__(self, name, age)

    # 新增跑的方法
    def run(self):
        print('%s 跑...' % self.name)


dog4 = Dog('jerry', 2, 'yellow')
dog4.run()

输出结果:
jerry 跑...

重写父类方法
为什么要重写父类的方法?原因很简单就是父类的方法无法满足子类时,比如我们发现Animal类中的say方法并不能满足Dog类(狗有狗的叫法),重写父类的方法之后,再调用时就会调用子类的方法了。

class Dog(Animal):

    # 重写父类的say方法,重写英文名叫override
    def say(self):
        print('旺旺旺...')

dog4 = Dog('jerry', 2)
dog4.say()

输出结果:
旺旺旺...

我们现在来总结一下继承相关特点:
1、被继承的类被称为父类或超类,例如Animal就是Cat类和Dog类的父类
2、继承之后的类称为子类,例如Cat类和Dog类
3、继承的语法格式:(python中支持多重继承,即子类可以继承多个父类)

class 子类类名(父类1[, 父类2, ...]):
    类体

4、在子类中可以添加新的属性和方法
5、在子类中可以重写父类中的方法

多态

多态这个概念并不太好解释,我还是拿Animal类来尝试着举例:

我们知道动物几乎都有叫声,猫有猫的叫声,狗有狗的叫声,我们人类也有自己的呐喊声,对于同一类的同一个行为所表现出的多种状态我们称之为多态。

多态对于程序的灵活性、扩展性方面起到了非常好的作用,我们看它是怎么做到的?

1、先创建Animal类

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

    def say(self):
        print('动物叫...')

2、创建一个猫类,重写父类的say方法

class Cat(Animal):
    def say(self):
        print('喵喵喵...')

3、创建一个狗类,重写父类的say方法

class Dog(Animal):
    def say(self):
        print('旺旺旺...')

4、创建一个人类,重写父类的say方法(人类也是动物,也可以继承Animal类)

class Person(Animal):
    def say(self):
        print('吃了吗您?')

5、定义一个函数,要求每个动物都给大家打声招呼

def animal_say(animal):
    # 这里要做容错处理,万一传进来的不是动物类就报错了
    if isinstance(animal, Animal):
       animal.say()
    else:
        print('参数异常!请传入动物类型的数据!')

6、调用animal_say函数,传入不同的动物,打声招呼

cat5 = Cat('tom', 1)
dog5 = Dog('jerry', 2)
p1 = Person('李大爷', 88)

animal_say(cat5)
animal_say(dog5)
animal_say(p1)

输出结果:
喵喵喵...
旺旺旺...
吃了吗您?

我们发现传入的都是动物类型的数据,他们在执行同一个行为say方法是,表现出了不同的形态(看打印的内容)。

这其实也不难理解,因为程序会识别传进来的具体是哪种动物类型,然后去调用他们自己的say方法,所以才表现出了不同的行为。

扯淡系列
...也许这是你给一家动物园写的一个让动物打招呼的程序,后来动物园长说我们又新来一批动物,也要他们给大家打招呼...

...你淡定的跟园长讲,我早已预见你有这样的需求了,没关系,只要让新来的动物继承Animal类,重写say方法...

...然后再把他们放进animal_say方法里,无需修改animal_say方法,他们就会自动给大家打招呼了...

...这时你心里暗自窃喜,幸好当初自己使用了多态...

以上的例子你明白了吗?这就是我们开始说的,多态的使用会增加程序的灵活性和扩展性,这也是面向对象的特性之一。

本章对应的源码文件:se1_ch8_OOP.py

本章作业

1、编写一个飞机类,飞机有颜色、名称、时速等属性,有飞行的方法。
2、编写一个战斗机类,除了有题目1中的属性和方法外,同时战斗机有作战高度、有射击的方法。

本章总结

学完本章之后,有几个概念你一定要掌握,什么是类?什么是对象?什么是对象的属性和方法?什么是类属性和类方法?除了概念之外,你还要会创建类,通过类创建对象,以及调用对象的属性和方法。

本章就到这里,我是猪弟爸爸,这里我会持续更新人工智能自学内容,有问题请关注我的公众号zhudipapa,我会统一在公众号下方回复,我们下节见。

你可能感兴趣的:(1.8【Python】第八章 面向对象)