欢迎来到赛兔子家园

Python【第四篇】面向对象编程上

面向对象编程

1、概述

  面向对象(Object Oriented)的英文缩写是OO,它是一种设计思想。我们经常听说的面向对象编程(Object Oriented Programming,即OOP)就是主要针对大型软件设计而提出的,它可以使软件设计更加灵活,并且能更好地进行代码复用。

  面向对象中的对象(Object),通常是指客观世界中存在的对象,这个对象具有唯一性,对象之间个不相同,各有各的特点,每一个对象都有自己的运动规律和内部状态;对象和对象之间又是可以相互联系、相互作用的。

对象:是一个抽象概念,英文称为Object,表示任意存在的事物。世间万物皆对象,现实世间中随处可以的一种事物就是对象,对象是事物存在的实体,如一个人。

   通常将对象划分为两个部分:静态部分和动态部分。静态部分被称为【属性】,任何对象都具备自身属性,这些属性不仅是客观存在的,而且是不能被忽视的。如人的性别。动态部分指的是对象的行为,即对象执行的动作,如人可以行走。

面向过程编程通常具有下面的表现形式:

  1. 导入各种模块
  2. 定义需要的全局变量
  3. 写一个函数完成特定功能
  4. 写一个函数完成特定功能
  5. ...
  6. 写一个main函数作为程序入口

函数是核心,函数调用是关键,一切围绕函数展开。

面向对象编程中,将函数和变量进一步封装成类,类才是程序的基本元素,它将数据和操作紧密连结在一起,并保护数据不会被外界的函数意外改变。类和类的实例(也称对象)是面向对象的核心概念,是和面向过程编程、函数式编程的根本区别。

面向对象编程通常具有如下表现形式:

  1. 导入各种模块
  2. 设计各种全局变量
  3. 设计类
  4. 给每个类提供完整的一组操作
  5. 使用继承来表现不同类之间的共同点
  6. 根据需求,决定是否写一个main函数作为程序入口

类的形态:

class  Human:
    def __init__(self,name,age):
         self.name = name
         self.age  = age
        
    def print_age(self):
        print("{0}:{1}".format(self.name,self.age))

  类具有封装、继承、多态三大特点。一个类定义了具有相似性质的一组对象。而继承是对具有层次关系的类的属性和操作进行共享的一种方式。所谓面向对象就是基于对象概念,以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界

和设计、构建相应的软件系统。

2、术语

  • 类(Class)---> 用来描述具有相同属性和方法的对象集合。定义了该集合中每个对象所共有的属性和方法。其中对象被称作类的实例;
  • 实例         --->    也称对象。通过类定义的初始化方法,赋予具体的值;
  • 实例化     --->    创建类的实例的过程或操作。
  • 实例变量 --->     定义在实例中的变量,只作用于当前实例。
  • 类变量     --->    类变量是所有实例公有的变量。类变量定义在类中,但在方法体之外。
  • 数据成员  --->    类变量、实例变量、方法、类方法、静态方法和属性等的统称。
  • 方法        --->     类中定义的函数。
  • 静态方法 --->    不需要实例化就可以有类执行的方法。
  • 类方法    --->    类方法是将类本身作为对象进行操作的方法。
  • 方法重写 --->    从父类继承的方法不能满足子类的需求,可以对父类的方法进行重写。
  • 继承       --->     一个派生类(derived class)继承父类(base class)的变量和方法。
  • 多态      --->      根据对象类型的不同以不同的方式进行处理。

3、类和实例

,英文名字Class,类别,分类,聚类的意思。必须牢记类是抽象的模板,用来描述具有相同属性和方法的对象集合。而实例是根据类创建出来的一个个具体的【对象】,每个对象都拥有相同的方法,但各种的数据可能不同。

Python使用class关键字来定义类:

class 类名(父类列表):

    pass

类名通常采用驼峰式命名方式,尽量让字面意思体现出类的作用。Python采用多继承机制,一个类可以同时继承多个父类(也叫基类、超类),继承的基类有先后顺序,写在类名后的圆括号里。继承的父类列表可以为空,此时圆括号可以省略。

Python3中,类似于 class Student:pass的方法没有显示继承任何父类定义的类,默认就继续object类。因为object是Python3中所有类的基类。

class  Student:
    classroom = '101'
    address = 'guangzhou'

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

    def print_age(self):
        print("{0}:{1}".format(self.name,self.age))

默认情况下,使用obj = Student() 的方式就可以生成一个类的实例。但是通常每个类的实例都会有自己的实例变量,例如:name和age,为了在实例化的时候体现实例的不同,Python提供了一个def __init__(self)的实例化机制。

任何一个类中,名字为__init__的方法就是类的实例化方法,具有__init__方法的类在实例化的时候,会自动调用该方法,并传递对应的参数:

例如:

wang = Student("老王",24)

liu   = Student("老刘",25)

实例变量和类变量

实例变量:

实例变量指的是实例本身拥有的变量,每个实例的变量在内存中都不一样。Student类中__init__方法里的name和age就是两个实例变量。通过实例变量名+圆点的方式调用实例变量。

print(wang.name)

print(wang.age)

print(liu.name)

print(liu.age)

********************************************

>>>老王

>>>24

>>>老刘

>>>25

类变量:

定义在类中,方法之外的变量,称为类变量。类变量是所有实例公有的变量,每一个实例都可以访问和修改类变量。在Student类中,classsroom和address两个变量就是类变量。

可以通过【类名】或者【实例名】+ 圆点的方式访问类变量。

例如:

Student.classroom

Student.address

wang.classroom

liu.address

使用实例变量和类变量的时候一定要注意,使用类似 : wang.name访问变量的时候,实例会先在自己的实例变量列表里查找是否有这个实例变量,如果没有,那么它就会去类变量列表中查找,如果还没有,抛出异常;

Python动态语言的特点:可以随时给实例添加新的实例变量,给类添加新的类变量和方法。因此在使用liu.classroom =  '102' 的时候,要么是给已有的实例变量classroom重新赋值,要么就是新建一个liu专属的实例变量classroom并赋值为"102"

 例子:

class Student:
    classroom = "120"
    address = "广州"

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

    def print_age(self):
        print("{0}:{1}".format(self.name,self.age))


wang = Student("老王",32) #创建一个实例
liu      = Student("老刘",23) #创建一个实例
print(liu.classroom) #liu本身没有classroom实例变量,所以去寻找类变量,它找到了
print(wang.classroom)
print(Student.classroom)

liu.classroom = "110" #关键的一步,实际是为liu创建了独有的实例变量,只不过名字和类变量一样,都叫做classroom。
print("再次访问classroom",liu.classroom) #访问到的是liu自己的实例变量classroom
print("老王没有实例变量classroom",wang.classroom)#wang没有实例变量classroom,依然访问类变量classroom
print("类变量classroom保存不变:",Student.classroom) #保持不变
del liu.classroom #删除li的实例变量classroom
print("恢复到原样:",liu.classroom)
print(wang.classroom)
print(Student.classroom)

类的方法

  Python类中包含实例方法、静态方法和类方法三种方法。这些方法无论是在代码编排中还是内存中都归属于类,区别在于传入的参数和调用方式不同。在类的内部,使用def 关键字来定义一个方法。

实例方法

  类的实例方法由实例调用,至少包含一个self参数,且为第一个参数。执行实例方法时,会自动调用该方法的实例赋值给self。self代表的是类的实例,而非类本身。

self不是关键字,而是Python约定的命名,可以自定义但是不建议自定义。

例如:

class Student:
    classroom = "120"
    address   = "广州"

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

    def print_age(self): #实例方法
        print("{0}:{1}".format(self.name,self.age))

wang = Student("老王",32)   #创建一个实例
liu  = Student("老刘",23) #创建一个实例

#调用方法
wang.print_age()
liu.print_age()

静态方法

  静态方法由类调用,无默认参数。将实例方法中self去掉,然后在方法定义上加上@staticmethod,就成为静态方法。它属于类,和实例无关。使用类名.静态方法的调用方式。(虽然也可以使用实例名.静态方法的方式调用)

class  Foo:
    @staticmethod
    def static_method():
          pass
#调用方法
Foo.static_method()

类方法

  类方法由类调用,采用@classmethod装饰,至少传入一个cls(指类本身,类似于self)参数。执行类方法时,自动将调用该方法的类赋值给cls。使用类名.类方法的调用方式(也可以使用实例名.类方法的方式调用)

class Foo:

    @classmethod
    def class_method(cls):
        pass
Foo.class_method()

 综合例子:

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

    def ord_func(self):
        """实例方法"""
          print("实例方法")

    @classmethod
    def class_func(cls):
        """类方法"""
          print("类方法")

    @staticmethod
    def static_func():
        """静态方法,无默认参数"""
          print("静态方法")

#调用实例方法
f = Foo("tom")
f.ord_func()
Foo.ord_func(f) # 虽然可以,但是建议不要这么调用

#调用类方法
Foo.class_func()
f.class_func() #虽然可以,但是建议不要这么调用

#调用静态方法
Foo.static_func()
f.static_func() #虽然可以,但是建议不要这么调用

类、类方法、类变量、类的实例和实例变量在内存中如何保存?

类、类的所有方法以及类变量在内存中只有一份,所有的实例共享它们。而每一个实例都在内存中独立的保存自己和自己的实例变量。

创建实例时,实例中除了封装如name和age的实例变量之外,还会保存一个类对象指针,该值指向实例所属的类地址。因此实例可以寻找到自己的类,并进行相关调用,而类无法寻找到自己的某个实例。

 封装、继承和多态

面向对象编程有三大重要特征:封装、继承、多态。

封装

  封装是指将【数据】和【具体操作】的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现,外界只能通过接口使用该对象,而不能通过任何形式修改对象内部实现。

例如:

class Student:
    classroom = "120"
    address   = "广州"

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

    def print_age(self):
        print("{0}:{1}".format(self.name,self.age))

#以下是错误的用法
#类将它内部的变量和方法封装起来,阻止外部的直接访问
print(classroom)
print(adress)
print_age()

继承

   继承机制实现了代码的复用,多个类公用的代码部分可以只在一个类中提供,而其它类只需要继承这个类即可。

   OOP程序设计中,定义一个新类的时候,新的类成为子类(Subclass),而被继承的类称为基类、父类、或者超类(Base class、Super class)。

   继承最大的好处就是子类获得了父类的全部变量和方法的同时,又可以根据需要进行修改、扩展。

语法结构如下:

  class  Foo(superA,superB,superC....):

  class DerivedClasssName(modname.BaseClassName): ##当父类定义在另外的模块时

  Python支持多父类的继承机制,所以需要注意圆括号中基类的顺序,若是基类中有相同的方法名,并且在子类使用时未指定,Python会从左至右搜索基类中是否包含该方法。

class People:
    def __init__(self,name,age,weight):
        print("父类的init方法")
        self.name = name
        self.age  = age
        self.__weight = weight

    def speak(self):
        print("父类的speak方法")
        print("{0}说:我{1}岁。".format(self.name,self.age))

#单继承
class Student(People):
    def __init__(self,name,age,weight,grade):
        #调用父类的实例方法
        People.__init__(self,name,age,weight)
        self.grade = grade

    def speak(self):
        print("子类的speak方法")
        print("{0}说:我{1}岁了,我在读{2}年级".format(self.name,self.age,self.grade))

s = Student('tian',10,20,3)
s.speak()

Python3的继承机制

  • 子类在调用某个方法或变量的时候,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
  • 根据父类定义中的顺序,已【深度优先】的方式逐一查找父类。

例一:

继承关系如下:

class D:
    pass

class C(D):
    pass

class B(C):
    def show(self):
        print("我是B")

class G:
    pass

class F(G):
    pass

class E(F):
    def show(self):
        print("我是E")

class A(B,E):
    pass

a = A()
a.show()

#运行结果:我是B

 类A中没有show()方法,于是去父类查找,先从在B类中找(先后顺序从左到右),结果找到了,执行B类的show()方法。

 如果B类中没有show方法,而是D类有呢?

class D:
    def show(self):
        print("我是D")

class C(D):
    pass

class B(C):
   pass

class G:
    pass

class F(G):
    pass

class E(F):
    def show(self):
        print("我是E")

class A(B,E):
    pass

a = A()
a.show()

#输出:我是D

执行结果是【我是D】,左边具有深度优先权,当一条路走到黑页面没有找到的时候,才换另一条路。

 

例二:

  类D和类G同时继承了类H。当只有B和E有show方法的时候,和上面例子一样,找到B就不找了,直接打印【我是B】,但如果只有H和E右show方法呢?

class H:
    def show(self):
        print("我是H")

class D(H):
    pass

class C(D):
    pass

class B(C):
   pass

class G(H):
    pass

class F(G):
    pass

class E(F):
    def show(self):
        print("我是E")

class A(B,E):
    pass

a = A()
a.show()

#输出:我是E

这种情况下继承顺序如下图:

super()函数

 子类中如果有与父类同名的成员,那么就会覆盖父类里的成员。如果需要强制调用父类的成员呢?

 使用super()函数,这是一个非常重要的函数,最常见的就是通过super调用父类实例化方法__init__

语法:super(子类名,self).方法名(),需要传入子类名和self,调用的是父类里的方法,按父类的方法需要传入参数。

class A:
    def __init__(self,name):
        self.name = name
        print("父类的__init__方法被执行了!")

    def show(self):
        print("父类的show方法被执行了")

class B(A):
    def __init__(self,name,age):
        super(B,self).__init__(name=name)
        self.age = age

    def show(self):
        super(B,self).show()

obj = B("tian",22)
obj.show()

多态

class Animal:
    def kind(self):
        print("我是aniaml")

class Dog(Animal):

    def kind(self):
        print("我是一只狗")

class Cat(Animal):

    def kind(self):
        print("我是一只猫")

class Pig(Animal):

    def kind(self):
        print("我是一只猪")

#这个函数接收一个animal参数,并调用它的kind方法
def show_kind(aniaml):
    aniaml.kind()

d = Dog()
c = Cat()
p = Pig()

show_kind(d)
show_kind(c)
show_kind(p)

----------------------------
打印结果:

我是一只狗
我是一只猫
我是一只猪

  狗、猫、猪都继承了动物类,并各自重写了kind方法。show_kind()函数接收一个aniaml参数,并调用它的kind方法。可以看出,无论我们给animal传递的是狗、猫、猪都能正确调用相应的方法,打印对应信息,这就是多态;

动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用;

 成员保护和访问限制

  在类的内部,有各种变量和方法。这些数据成员,可以在类的外部通过实例或者类名进行调用。

例如:

class  People:
    title = "人类"

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

    def print_age(self):
        print("{0}:{1}".format(self.name,self.age))

obj = People("tian",13)
obj.age = 18
obj.print_age()
print(People.title)

  上面的调用方式是大多数情况下都需要的,但是往往我们也不希望所有的变量和方法能被外部访问,需要针对性地保护某些成员,限制对这些成员的访问。这样的程序才是健壮、可靠的、也符合业务的逻辑。

在Python中,如果要让内部成员不被外部访问,可以在成员的名字前面加上两个下划线__,这个成员就变成了一个私有成员(private)。私有成员只能在类的内部访问,外部无法访问。

class People:
    title = "人类"

    def __init__(self,name,age):
        self.__name = name
        self.__age  = age

    def print_age(self):
        print("{0}:{1}".format(self.__name,self.__age))

obj = People("tian",18)
obj.__name   #会报错

如果要对__name和__age进行访问和修改呢?在类的内部创建外部可以访问的get和set方法;

class People:
    title = "人类"
def __init__(self,name,age): self.__name = name self.__age = age def print_age(self): print("{0}:{1}".format(self.__name,self.__age)) def get_name(self): return self.__name def get_age(self): return self.__age def set_name(self,name): self.__name = name def set_age(self,age): self.__age = age obj = People("tian",18) obj.get_name() obj.set_name("xiang")

  这样做,不但对数据进行了保护同时也提供了外部访问的接口,而且在get_name,set_name这些方法中,可以添加验证代码等等各种操作,作用巨大;

那么,以双下划线开头的数据成员是不是一定就无法从外部访问呢?其实也不是,从内部机制原来讲,外部不能直接访问__age是因为Python解释器对外把__age变量改成了_People__age,也就是_类名__age(类名前是一个下划线)。

因此,投机取巧的话,可以通过_People__age在类的外部访问__age变量:

obj  = People("tian",23)

print(obj._People__name)

扩展:由于Python内部会对双下划线开头的私有成员进行名字变更,所以会出现下面情况:

class People:
    title = "人类"
        def __init__(self,name,age):
        self.__name = name
        self.__age  = age

    def print_age(self):
        print("{0}:{1}".format(self.__name,self.__age))

    def get_name(self):
        return self.__name

       def set_name(self,name):
        self.__name = name

obj = People("tian",18)
obj.__name = "xiang"
print("obj.__name:",obj.__name) #相当于给obj实例添加了一个新的实例变量__name,而不是对原有私有成员__name的重新赋值。
print("obj.get_name():",obj.get_name())

   此外有些时候,你会看到一个下划线开头的成员名,例如_name,这样的数据成员在外部是可以访问的,但是按照约定的规定,看到这样的标识符时,意思就是【虽然我可以被外部访问,但是请把我视为私有成员,不要访问在外部访问我】

 类的成员与下划线总结:

  • _name、_name_、_name_建议性的私有成员,不要在外部访问。
  • __name、__name_强制的私有成员,但是依然可以在外部访问。
  • __name__特殊成员,与私有性质无关,例如__doc__。
  • name_、name__没有任何特殊性,普通的标识符,但最好不要这么起名字。

posted on 2019-04-17 16:39  赛兔子  阅读(947)  评论(0编辑  收藏  举报

导航