脚本语言系列之Python | Python面向对象

1 面向对象思想

面向对象编程(Object Oriented Programming,简称OOP),是利用类和对象来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因不仅因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

1.1 面向对象和面向过程

一、面向对象(Object Oriented,简称OO),是一种程序设计思想,如python和java语言就是一种面向对象的编程语言:

(1)OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
(2)OOP把程序看做不同对象的相互调用,OOP的抽象程度比函数要高。

二、面向过程(Procedure Oriented简称PO),也是一种常见的程序设计思想,如c语言:

(1)面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
(2)面向过程编程是把函数看着程序的最基本单元,一个函数包括要处理的数据及算法逻辑。
(3)面向过程编程是把程序看作不同函数之间的互相调用。
(3)面向过程编程的抽象层度相对较低。

优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。

1.2 面向对象的常见概念

(1)类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法,对象是类的实例。

(2)类变量:类变量在整个实例化的对象中是公用的,类变量定义在类中且在函数体之外。

(3)局部变量:定义在方法中的变量,只作用于当前实例的类。

(4)实例变量:在类的声明中,类属性是用变量来表示的。这种变量就称为实例变量,一般使用self.variableName。

(5)实例化:创建一个类的实例,类的具体对象。

(6)方法:类中定义的函数,类中的方法必须有一个参数self,也必须是在位置参数的第一位。

(7)对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

(8)方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。

2 类的创建及调用

2.1 类的创建

使用class关键字来创建一个新类,class之后为类的名称()并以冒号结尾:

class ClassName():
   '''类的帮助信息'''
   类体,包括类的变量和方法

下面写一个动物类的案例:

class Animal():
    #这些都是类变量,在类中,方法外
    nicheng = "动物"

    #类中的方法参数中必须要有一个self,而且必须是在位置参数的第一位
    #实例(对象)变量,在变量前需要有一个self.
    def info(self):
        self.age = 0
        self.gender = "male"
        self.weight = 0
        self.brand = "xxx"        

    def eat(self):
        print("站着吃")

    def sleep(self):
        print("趴着睡")

2.2 类对象的创建及使用

A=className()
类对象支持两种操作:属性引用和方法引用
标准语法:obj.name

针对上文动物类的对象创建及使用:

# 创建对象pig,及使用对象的变量和方法
pig = Animal()
pig.sleep()
pig.eat()
print(Animal.nicheng)
pig.info()
print(pig.age)

2.3 类变量的使用

class Person():
    # 国籍定义为类变量比较好
    guoji = "中国"


# 创建了小王的对象
xiaowang = Person()
print(Person.guoji)  # 通过类访问
print(xiaowang.guoji)  # 通过对象访问

# 如果通过xiaowang调用类变量进行赋值,则此处创建了一个与类变量同名的实例变量,
# 修改的也是该对象的实例变量值,而不是类变量值
xiaowang.guoji = "英国"
print(Person.guoji)  # 通过类访问
print(xiaowang.guoji)  # 通过对象访问

# 如果通过类名调用类变量进行赋值,此处修改的是类变量值,其他新对象的该值也改变了
Person.guoji = "法国"
print(xiaowang.guoji)
xiaoli = Person()
print(xiaoli.guoji)

2.4 构造方法的使用

(1)构造方法可以实现对实例变量的初始化操作。
(2)init的特殊方法(构造方法),在类实例化时会自动调用。
(3)init方法可以有参数,参数通过 init传递到类的实例化操作上。
(4)可以不显示地写init方法,会默认使用无参数的构造方法。
(5)如果显示地写了构造方法,则不能再使用无参数的构造方法。

class studentInit():
    #声明类变量
    type1 = '学生'

    # 通过构造方法实现对实例变量的初始化
    def __init__(self,age,name,ID):
        # 定义并对实例变量进行初始化
        self.age = age
        self.name = name
        self.ID = ID

    # 定义第一个方法info,实现对实例变量的显示
    def info(self):  # 显示对象属性的方法
        print("年龄是{}、姓名是{}、学号是{}".format(self.age,self.name,self.ID))

    def study(self):
        score=80
        self.ID = "0000000"
        print("学生{}的学习成绩是{}".format(self.ID,score))

    def play(self):
        print("学习之余需要玩会游戏!")

#创建类的对象(实例化:由抽象到具体)
#如果你显示地写了带参数的构造方法,则不再允许使用默认的无参数的构造方法
xiaohua = studentInit(20,"xiaohua","12345678")
print(xiaohua.type1)
print(xiaohua.age)
xiaohua.info()
xiaohua.play()
xiaohua.study()

2.5 self的使用

(1)类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是self。

(2)self不是关键字,你可以把它改成其他名称,虽然代码能正常运行,但是还是会提示:Method should have self as first argument。

(3)self代表的是类的实例,代表当前对象的地址,而self.__class__则指向类。

class Person():
    def ppp(self):
        print(self)
        print(self.__class__)

# 下面两个输出的是同一个地址空间
# 说明self就是类的具体实例,此处就是指的小王
xiaowang = Person()
print(xiaowang)
xiaowang.ppp()
输出如下:
<__main__.Person object at 0x0000015009306070>
<__main__.Person object at 0x0000015009306070>
<class '__main__.Person'>

3 面向对象的三大特性

三大特性包括:

封装
继承
多态

3.1 封装

对于私有的属性,不能被外界使用,但不可避免的要被访问和修改,我们就提供一些方法进行修改和对外访问接口,这种方式叫做封装 - 将变化隔离 - 提高复用性 - 提高安全性。

3.1.1 变量私有化

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)。
(1)__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部直接访问,在类内部的方法中使用时self.private_attrs。
(2)__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用self.private_methods,不能在类的外部调用。

class Student():
    def __init__(self,name,age,sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

one = Student('allen',18,'男')

# print(one.__name)  # 报错,无该属性
# print(one.name)    # 报错,无该属性

print(one._Student__name)
print(one._Student__age)
print(one._Student__sex)

one._Student__age = 20
print(one._Student__age)

此时发现,我们虽然不能使用one.__name或者one.name访问到该属性。但是我们可使用one._Student__age访问到对象的age属性并且能修改。说明python在设置私有属性的时候,只是把属性的名字换成了其他的名字

3.1.2 提供对外访问

将变量私有化后,还需要对外提供公共的访问方式:

class Student():
    def __init__(self,name,age,sex):
        self.__name = name
        self.__age = age
        self.__sex = sex
    def info(self):
        print("姓名:{}年龄:{}性别:{}".format(self.__name,self.__age,self.__sex))

    def get_name(self):
        return self.__name

    def set_name(self,name):
        if len(name) > 1 :
            self.__name = name
        else:
            print("name的长度必须要大于1个长度")

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age > 0 and age < 150:
            self.__age = age
        else:
            print("输入的年龄必须要大于0,小于150岁")

one = Student('plf',18,'男')
one.info()

one.set_name('a')   # 通过自己设置接口,可以有效规避脏数据
print(one.get_name()) # 通过接口获取数据


one.set_age(-9)   # 通过自己设置接口,可以有效规避脏数据
print(one.get_age()) # 通过接口获取数据

3.1.3 装饰器

除此之外还可以使用@property 装饰器提供私有数据的访问。

class Student():
    def __init__(self,name,age,sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

    def info(self):
        print("姓名{}年龄{}性别{}".format(self.__name,self.__age,self.__sex))
        
    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,name):
        if len(name) > 1 :
            self.__name = name
        else:
            print("name的长度必须要大于1个长度")

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age > 0 and age < 150:
            self.__age = age
        else:
            print("输入的年龄必须要大于0,小于150岁")


one = Student('plf',18,'男')
one.info()

one.name = '张三'
print(one.name)

one.age = 170
print(one.age)

3.2 继承

继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

继承,其实这样理解,就是我写了一个爸爸类和儿子类,爸爸有钱,儿子却没钱,于是儿子决定继承爸爸,调用爸爸的钱(爸爸类的变量和方法)。

(1)面向对象的编程带来的主要好处之一是代码的重用,通过继承机制实现。
(2)通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。
(3)继承的实现必须基于类的“继承”关系。

继承语法是:

class 派生类名(基类名):
    类的代码
    ...

3.2.1 继承示例

# 引入继承的理念:
# 提取不同类中共有的属性和行为:年龄、性别、颜色、吃鱼、睡、玩
# 把提取的属性和行为定义到一个动物类中:


class animal():  # 父类
    def __init__(self,age,sex,color):
        self.age = age
        self.sex = sex
        self.color = color

    def eat(self):
        print("吃饭")

    def sleep(self):
        print("睡觉")

    def play(self):
        print("玩")

#可以使用继承的方式来写子类
class cat(animal):#子类
    #重写:前提是必须有继承关系,父类的行为在子类中不一定全部通用
    #子类有自己的特性,那就把父类的行为重写一下
    #方法名保持一致,参数无所谓
    def eat(self,food):
        print("猫吃鱼")


#创建一只小猫
c = cat(1,"male","yellow")
print(c.color)
c.eat("鱼")
c.play()

3.2.2 调用基类方法

在python中继承中的一些特点:
(1)如果在子类中需要父类的构造方法就需要显式的调用父类的构造方法,或者不重写父类的构造方法。
(2)在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。

class Parent():        # 定义父类
   parentAttr = 100
   def __init__(self):
      print("调用父类构造函数")

   def parentMethod(self):
      print('调用父类方法')


class Child(Parent): # 定义子类
   def __init__(self):
      print("调用子类构造方法")

   def setAttr(self, attr):
          Parent.parentAttr = attr

   def getAttr(self):
          print("父类属性 :", Parent.parentAttr)

   def childMethod(self):
      Parent.parentMethod(self)
      print('调用子类方法')

c = Child()          # 实例化子类
c.childMethod()      # 调用子类的构造方法
c.parentMethod()     # 调用父类方法
c.setAttr(200)     # 调用子类方法修改父类属性 - 设置属性值
c.getAttr()        # 调用子类方法查看父类属性 - 获取属性值

3.2.3 方法重写

方法重写,如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法:

class Parent():        # 定义父类
    def myMethod(self):
      print('调用父类方法')

class Child(Parent): # 定义子类
    pass
    # def myMethod(self):#字类重写父类的同名方法
    #   print('调用子类方法')


c = Child()    # 子类实例
c.myMethod()   # 子类调用重写方法,如果不重写,则调用父类方法

3.3 多态

Java中多态性,可以理解为一个事物的多种形态,比如我们说猫是动物、猫也是猫,猫就具备了不同的形态。

同样python中也支持多态,但是是有限的的支持多态性,主要是因为python中变量的使用不用声明,所以不存在父类引用指向子类对象的多态体现,同时python不支持重载。

在python中 多态的使用不如Java中那么明显,所以python中刻意谈到多态的意义不是特别大。

更多推荐

python-8-面向对象编程