Python--面向对象初识

Posted YJ.li

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python--面向对象初识相关的知识,希望对你有一定的参考价值。

Python基础-初识面向对象

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

面向过程 VS 面向对象

面向过程的程序设计的核心是(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好的一条流水线 ,考虑周全什么时候处理什么东西。

  • 优点:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
  • 缺点:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
  • 应用场景:一旦完成基本很少改变的场景,著名的例子有Linux内核,git,以及Apache HTTP Server 等。

面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。

  • 优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
  • 缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
  • 应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

在python 中面向对象的程序设计并不是全部。

面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

了解一些名词:类、对象、实例、实例化

  • 类:具有相同特征的一类事物(人、狗、老虎)
  • 对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)
  • 实例化:类——>对象的过程

示例说明:

示例说明面向过程和面向对象在程序流程上的不同之处。

假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示:

std1 = { \'name\': \'Michael\', \'score\': 98 }
std2 = { \'name\': \'Bob\', \'score\': 81 }

 而处理学生成绩可以通过函数实现,比如打印学生的成绩:

def print_score(std):
    print(\'%s: %s\' % (std[\'name\'], std[\'score\']))

如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有namescore这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。

class Student(object):

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

    def print_score(self):
        print(\'%s: %s\' % (self.name, self.score))

给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:

bart = Student(\'Bart Simpson\', 59)
lisa = Student(\'Lisa Simpson\', 87)
bart.print_score()
lisa.print_score()

类相关的知识

申明

def functionName(args):
    \'函数文档字符串\'
    函数体

\'\'\'
class 类名:
    \'类的文档字符串\'
    类体
\'\'\'

#创建一个类
class Data:
    pass

注意:类名通常是大写开头的单词

属性

class Person:   #定义一个人类
    role = \'person\'  #人的角色属性都是人
    def walk(self):  #人都可以走路,也就是有一个走路方法
        print("person is walking...")


print(Person.role)  #查看人的role属性
print(Person.walk)  #引用人的走路方法,注意,这里不是在调用

 实例化:类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征

class Person:   #定义一个人类
    role = \'person\'  #人的角色属性都是人
    def __init__(self,name):
        self.name = name  # 每一个角色都有自己的昵称;
        
    def walk(self):  #人都可以走路,也就是有一个走路方法
        print("person is walking...")


print(Person.role)  #查看人的role属性
print(Person.walk)  #引用人的走路方法,注意,这里不是在调用

实例化的过程就是类——>对象的过程

语法:对象名 = 类名(参数)

p1 = Person()

slef

self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,但是长的帅的人都不会这么做。
因为你瞎改别人就不认识

类属性的补充

一:我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值

二:特殊的类属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)

对象相关的知识

  • 实例化对象
    格式:对象名 = 类名(参数列表)
    注意:没有参数,小括号也不能省略

访问对象的属性和方法

  • 访问属性
    格式:对象名.属性名
    赋值:对象名.属性名 = 新值
  • 访问方法
    格式:对象名.方法名(参数列表)
class 类名:
    def __init__(self,参数1,参数2):
        self.对象的属性1 = 参数1
        self.对象的属性2 = 参数2

    def 方法名(self):pass

    def 方法名2(self):pass

对象名 = 类名(1,2)  #对象就是实例,代表一个具体的东西
                  #类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法
                  #括号里传参数,参数不需要传self,其他与init中的形参一一对应
                  #结果返回一个对象
对象名.对象的属性1   #查看对象的属性,直接用 对象名.属性名 即可
对象名.方法名()     #调用类中的方法,直接用 对象名.方法名() 即可
# 在终端输出如下信息

\'\'\'
小明,10岁,男,上山去砍柴
小明,10岁,男,开车去东北
小明,10岁,男,最爱大保健
老李,90岁,男,上山去砍柴
老李,90岁,男,开车去东北
老李,90岁,男,最爱大保健
\'\'\'


class Person(object):
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

    def print_hooby(self, hobby):
        print ("%s, %s岁, %s, %s "%(self.name, self.age, self.sex, hobby))

p1 = Person("小明", 10, "")
p1.print_hooby("上山去砍柴")
p1.print_hooby("开车去东北")
p1.print_hooby("最爱大保健")

p2 = Person("老李", 90, "")
p2.print_hooby("上山去砍柴")
p2.print_hooby("开车去东北")
p2.print_hooby("最爱大保健")
练习题
# 计算一个类实例过多少个对象

class Count:
    count = 0

    def __init__(self):
        Count.count = self.count + 1

a = Count()
b = Count()
c = Count()
d = Count()
print(Count.count)
from math import pi

class Circle:
    """
    定义了一个圆形类;
    提供计算面积(area)和计算周长(perimeter)的方法
    """
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return self.radius ** 2 * pi

    def perimeter(self):
        return self.radius * 2 * pi

circle = Circle(3)  # 实例化一个圆
area1 = circle.area()   # 计算圆面积
per1 = circle.perimeter()   # 计算圆周长
print(area1, per1)  # 打印圆面积和周长
一个简单的例子理解面向对象

类名称空间与对象的名称空间

创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性

类有两种属性:静态属性和动态属性

  • 静态属性就是直接在类中定义的变量
  • 动态属性就是定义在类中的方法

其中类的数据属性是共享给所有对象的

class Person:
    name = "小白"
    def fun1(self):
        print(self.name)

p1 = Person()
p2 = Person()

print(id(p1.name)) 
2436828402064
print(id(Person.name))  
2436828402064

而类的动态属性是绑定到所有对象的

class Person:
    name = "小白"
    def fun1(self):
        print(self.name)

p1 = Person()
p2 = Person()

print(p1.fun1)
<bound method Person.fun1 of <__main__.Person object at 0x000002375E4A8C18>>
print(Person.fun1)
<function Person.fun1 at 0x000002375E4AB8C8>

创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性

查询顺序:

  • 对象.属性:先从对象空间找,如果找不到,再从类空间找,再找不到,再从父类找...
  • 类名.属性:先从本类空间找,如果找不到,再从父类找...

对象与对象之间是互相独立的.

面向对象的组合用法

组合指的是,给一个类的对象封装一个属性,这个属性是另一个类的对象

什么有什么的关系使用组合。比如:学生有书的关系,可以使用组合

圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长。
这个时候,我们就首先实现一个圆形类,计算一个圆的周长和面积。然后在"环形类"中组合圆形的实例作为自己的属性来用

from math import pi

class Circle:
    """
    定义了一个圆形类;
    提供计算面积(area)和计算周长(perimeter)的方法
    """
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return self.radius ** 2 * pi

    def perimeter(self):
        return self.radius * 2 * pi

circle = Circle(3)  # 实例化一个圆
area1 = circle.area()   # 计算圆面积
per1 = circle.perimeter()   # 计算圆周长
print(area1, per1)  # 打印圆面积和周长

class Ring:
    def __init__(self, radius_outside, radius_inside):
        """
        :param radius_outside: 外圆的半径
        :param radius_inside: 内圆的半径
        """
        self.outside_redius = Circle(radius_outside)
        self.inside_redius = Circle(radius_inside)

    def rin_area(self):
        " 计算圆环的面积,用外圆的面积 - 内圆的面积"
        return self.outside_redius.area() - self.inside_redius.area()

    def rin_perimeter(self):
        " 计算圆环的周长,用外圆的周长 + 内圆的周长"
        return self.outside_redius.perimeter() + self.inside_redius.perimeter()


ring = Ring(9, 3)   # 实例化一个圆环
rinArea = ring.rin_area()   # 计算圆环的面积
rinPer = ring.rin_perimeter()   # 计算圆环的周长
print(rinArea, rinPer)  # 打印圆环的面积和周长
"""
模拟英雄联盟写一个游戏人物的类.
  要求:
  (1)创建一个 Game_role的类.
  (2) 构造方法中给对象封装name,ad(攻击力),hp(血量).三个属性.
  (3) 创建一个attack方法,此方法是实例化两个对象,互相攻击的功能:
      例: 实例化一个对象 盖伦,ad为10, hp为100
      实例化另个一个对象 剑豪 ad为20, hp为80
      盖伦通过attack方法攻击剑豪,此方法要完成 \'谁攻击谁,谁掉了多少血,  还剩多少血\'的提示功能.
"""

class Gamerole:
    def __init__(self, name, ad, hp):
        self.name = name
        self.ad = ad
        self.hp = hp

    def attack(self, p1):
        hp = p1.hp - self.ad
        print("%s攻击了%s, %s掉了%s血, 还剩%s血" % (self.name, p1.name, p1.name, self.ad, hp))


gailun = Gamerole("盖伦", 10, 100)
jianhao = Gamerole("剑豪", 20, 80)
gailun.attack(jianhao)



# 添加武器进行攻击:斧子,刀,枪,棍,棒...,
# 版本一:
# 代码不合理: 人物利用武器攻击别人,你的动作发起者是人,而不是武器.

class Gamerole:
    def __init__(self, name, ad, hp):
        self.name = name
        self.ad = ad
        self.hp = hp

    def attack(self, p1):
        hp = p1.hp - self.ad
        print("%s攻击了%s, %s掉了%s血, 还剩%s血" % (self.name, p1.name, p1.name, self.ad, hp))


class Arms:
    def __init__(self, name, ad):
        self.name = name
        self.ad = ad

    def fight(self, p1, p2):
        hp = p2.hp - self.ad
        print("%s使用%s攻击了%s, %s掉了%s血, 还剩下%s血" % (p1.name, self.name, p2.name, p2.name, self.ad, hp))

gailun = Gamerole("盖伦", 10, 100)
jianhao = Gamerole("剑豪", 20, 80)
dabaodao = Arms("大宝刀", 20)
dabaodao.fight(gailun, jianhao)


# 版本二:通过组合来实现,让代码合理

class Gamerole:
    def __init__(self, name, ad, hp):
        self.name = name
        self.ad = ad
        self.hp = hp

    def armament_weapon(self, aex): # 调用时传入武器对象
        self.aex = aex

class Arms:
    def __init__(self, name, ad):
        self.name = name
        self.ad = ad

    def fight(self, p1, p2):
        hp = p2.hp - self.ad
        print("%s使用%s攻击了%s, %s掉了%s血, 还剩下%s血" % (p1.name, self.name, p2.name, p2.name, self.ad, hp))


gailun = Gamerole("盖伦", 10, 100)
jianhao = Gamerole("剑豪", 20, 80)
dabaodao = Arms("大宝刀", 20)

gailun.armament_weapon(dabaodao)    # 给盖伦装备了大宝刀这个武器
gailun.aex.fight(gailun, jianhao)
组合小练习

面向对象三大特征

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

继承

什么是什么的关系使用继承,节省代码。比如:人是动物,狗也是动物,那么人和狗都可以继承动物这个类

继承: 单继承,多继承.

单继承

继承是一种创建新类的方式,在Python中,新建的类可以继承一个或者多个父类,父类又称为基类或超类,新建的类称为派生类或子类

子类可以自动拥有父类中除了私有属性外的其他所有内容。说⽩了, ⼉⼦可以随便用爹的东西。但是朋友们, 一定要认清楚⼀个事情。必须先有爹, 后有⼉⼦。 顺序不能乱,在Python中实现继承非常简单。在声明类的时候, 在类名后⾯面添加一个小括号,把要继承的类传进去就可以完成继承关系。

那么什么情况可以使用继承呢?

单纯的从代码层⾯上来看,当两个类具有相同的功能或者特征的时候,可以采用继承的形式。提取一个父类,这个父类中编写两个类中相同的部分。然后两个类分别去继承这个类就可以了。这样写的好处是我们可以避免写很多重复的功能和代码。

class Animal:

    def eat(self):
        print "%s 吃 " %self.name

    def drink(self):
        print "%s 喝 " %self.name

    def shit(self):
        print "%s 拉 " %self.name

    def pee(self):
        print "%s 撒 " %self.name


class Cat(Animal):

    def __init__(self, name):
        self.name = name
        self.breed = \'\'

    def cry(self):
        print \'喵喵叫\'

class Dog(Animal):
    
    def __init__(self, name):
        self.name = name
        self.breed = \'\'
        
    def cry(self):
        print \'汪汪叫\'
        

# ######### 执行 #########

c1 = Cat(\'小白家的小黑猫\')
c1.eat()

c2 = Cat(\'小黑的小白猫\')
c2.drink()

d1 = Dog(\'胖子家的小瘦狗\')
d1.eat()

综合示例:

动物都有吃、喝、拉、撒共同属性,各动物有各动物的方法. 定义一个动物类(Animal),定义一个鸟类(Bird),定义一个马类(Horse)

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

    def eat(self):
        print("%s 在吃东西" % self.name)

    def drink(self):
        print("%s 在喝东西" % self.name)

    def pull(self):
        print("%s 在拉粑粑" % self.name)

    def sow(self):
        print("%s 在嘘嘘" % self.name)


class Bird(Animal):
    def flight(self):
        print("%s 在自由的飞翔" % self.name)


class Horse(Animal):
    def running(self):
        print("%s 在飞驰的奔跑" % self.name)


bird1 = Bird("麻雀", 1.2, "")   # 实例化一只鸟
bird1.flight()  # 调用自身独有的方法
bird1.eat()     # 调用父类公共的方法
horse1 = Horse("草原马", 3, "")   # 实例化一匹马
horse1.running()    # 调用自身独有的方法
horse1.pull()       # 调用父类公共的方法

现在要给鸟类添加一个翅膀的属性,给马类添加一个尾巴的属性

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

    def eat(self):
        print("%s 在吃东西" % self.name)

    def drink(self):
        print("%s 在喝东西" % self.name)

    def pull(self):
        print("%s 在拉粑粑" % self.name)

    def sow(self):
        print("%s 在嘘嘘" % self.name)


class Bird(Animal):
    def __init__(self, name, sex, age, wing):
        # 方法一:通过父类名.父类方法名(参数)
        # Animal.__init__(self, name, sex, age)
        # 方法二:通过super().父类方法名(参数(自动传入self))
        super().__init__(name, sex, age)    # 还可以这样写super(Bird,self).__init(name, sex, age)
        self.wing = wing

    def flight(self):
        print("%s 在自由的飞翔" % self.name)


class Horse(Animal):
    def __init__(self, name, sex, age, tail):
        # Animal.__init__(self, name, sex, age)
        super().__init__(name, sex, age)
        self.tail = tail

    def running(self):
        print("%s 在飞驰的奔跑" % self.name)


bird2 = Bird("鹦鹉", 6, "", "红翅膀")   # 实例化一只鸟,加入翅膀
print(bird2.__dict__)       # 查看是否将翅膀属性成功添加
# {\'name\': \'鹦鹉\', \'sex\': 6, \'age\': \'母\', \'wing\': \'红翅膀\'}

horse2 = Horse("汗血马", 3, "", "长尾巴")    # 实例化一匹马,加入尾巴
print(horse2.__dict__)      # 查看是否将尾巴属性成功添加
# {\'name\': \'汗血马\', \'sex\': 3, \'age\': \'公\', \'tail\': \'长尾巴\'}

总结:

只执行父类的方法:子类中不要定义与父类同名的方法

只执行子类的方法:在子类创建这个方法

问题:既要执行子类的方法,又要执行父类的方法?

有两种解决方法:

1、在子类中执行父类的方法

  • 父类名.父类方法名(参数)

2、通过super()

  • super().父类方法名(参数(自传self))

多继承

类: 经典类, 新式类

  • 新式类: 凡是继承object类都是新式类.   python3x 所有的类都是新式类,因为python3x中的类都默认继承object.
    class A(object):
        pass
  • 经典类: 不继承object类都是经典类.  python2x:(既有新式类,又有经典类) 所有的类默认都不继承object类,所有的类默认都是经典类.你可以让其继承object.
    class A:
        pass

单继承: 新式类,经典类查询顺序一样
多继承:

  • 新式类: 遵循广度优先.  广度优先 : 一条路走到倒数第二级,判断,如果其他路能走到终点,则返回走另一条路.如果不能,则走到终点.
  • 经典类: 遵循深度优先.  深度优先 : 一条路走到底.
  • 深度优先,广度优先:只能是继承两个类的情况

 

经典类多继承:

class A:
    def func(self):
        print(" IN A")

class B(A):
    def func(self):
        print(" IN B")

class C(A):
    def func(self):
        print(" IN C")

class D(B):
    def func(self):
        print(" IN D")

class E(C):
    def func(self):
        print(" IN E")

class F(D, E):
    def func(self):
        print(" IN F")

func1 = F()
func1.func()
# 执行func方法时:
# 首先去F类找,如果F没有去D类找,如果D类没有去B类找,如果B类没有去A类找,如果A类没有去E类找,如果E类没有去C类找,如果C类还没有则报错
# 查找顺序为F——>D——>B——>A——>E——>C
# 在上述查找func方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了

新式类多继承:

class A(object):
    def func(self):
        print(" IN A")

class B(A):
    def func(self):
        print(" IN B")

class C(A):
    def func(self):
        print(" IN C"

以上是关于Python--面向对象初识的主要内容,如果未能解决你的问题,请参考以下文章

python 初识面向对象

python -- 面向对象初识

Python基础之(面向对象初识)

Python 面向对象初识

Python 入门 之 初识面向对象

Python 基础第十六---十九天(面向对象初识)