笔记内容:Python3 面向对象
笔记日期:2017-11-13


<br>

Python3 面向对象

  • Python3 面向对象
  • 面向对象技术简介
  • 类的定义
  • 类的对象
  • 类的方法
  • 继承
  • 多继承
  • 方法重写
  • 类的私有属性与私有方法

<br>

Python3 面向对象


Python从设计之初就已经是一门面向对象的语言,所以我们可以以面向对象的方式去编写python代码。面向对象就是将任何事情都当做对象去看待,一个对象会拥有属性和行为。在面向对象的语言中,有两个概念,一个是类,一个是实例对象。类是对象的设计蓝图,对象是类的实例,对象中的属性和行为就是类的成员,其中分为静态成员和实例成员。

面向对象可以说是目前最为贴近人类思维和现实生活的一种程序设计方式,我们把所有的事情都当做对象去看待,所谓万事万物皆对象。正因为如此,所以会有很多的对象,而我们要把这些对象给进行分类,所以就需要有类的概念。例如:小明和小红就是两个对象,他们都拥有眼睛、鼻子、嘴巴、耳朵等属性,拥有读书写字、吃饭睡觉等行为。显而易见的这两个对象都属于人类,而小明和小红就是人类的两个实例对象。

我这里只是简单的描述了一下面向对象的概念,如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。

<br>

面向对象技术简介


  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例,类是对象的设计蓝图,所以它定义了每个对象所共有的属性和方法(行为)。
  • 类变量(静态属性):类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关数据,所以类变量和实例变量都属于数据成员。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 实例变量(实例属性):定义在方法中的变量,只作用于当前的实例对象。
  • 封装:封装最主要的目的是进行数据的隔离,封装后的数据无法直接对其进行操作,只能通过其封装后提供的接口进行操作,从而保护了数据的安全性和隔离复杂的数据。就好比我们的电脑主机一样,把所有的硬件都封装在了机箱里,我们只需要按开机键就能让这些硬件运作起来,不需要去研究它里面的硬件,并且有了机箱的保护,这些硬件也就没那么容易受到损坏。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。
  • 多态:多态就是让子类转换成父类的类型,形象点说就是让子类穿上父类的衣服,装扮成父类的样子,能起到隐藏子类的作用,因为子类转换成父类后就可以作为父类去使用了。
  • 实例化:创建一个类的实例,类的具体对象,实例化就是创建对象。
  • 方法(行为):在类中定义的函数,其实函数和方法的概念没什么太大区别,很多时候都把函数和方法当做同一个东西去看待。
  • 对象:通过类所定义的数据结构实例,对象一般包括两个数据成员(类变量和实例变量)和方法。

和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制,所以在这一方面和其他面向对象的编程语言有所差异。

Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,也就是可以进行多继承,派生类(子类)可以覆盖基类(父类)中的任何方法,方法中可以调用基类中的同名方法。
一个实例对象可以包含任意数量和类型的数据。

<br>

类的定义


语法格式如下:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

使用class关键字定义一个类,类实例化成对象后,就可以通过对象使用其属性。实际上,创建一个类之后,也可以通过类名访问其属性。

定义一个简单的类:

"""定义一个简单的类"""
class MyClass :

    # num就是类的属性
    num=100
    # method就是类的方法
    def method(self):
        print("Hello World!")

在python的类中,每个方法都需要有self参数。

<br>

类的对象


我们实例化一个类的对象后,可以通过这个实例对象进行调用此对象中的属性和方法。
和其他编程语言一样,python中也是使用点作为访问符去调用对象中的属性和方法,例如:obj.name。
下面我们来将上面定义的类进行实例化:

"""定义一个简单的类"""
class MyClass:
    # num就是类的属性
    num = 100

    # method就是类的方法
    def method(self):
        print("Hello World!")

# 实例化类的对象
obj = MyClass()

# 调用对象的num属性,属性被调用后,会返回该属性的值
print("MyClass类的num属性的值为:", obj.num)

# 调用对象的method方法,方法被调用后,里面的代码就会被执行
print("MyClass类的method方法的输出为:")
obj.method()

python实例化一个对象不需要像其他大部分编程语言那样使用new关键字,直接类名加一个小括号就可以了,以上将实例化的对象交给了obj变量,此时obj就是MyClass的实例化对象。

以上示例的运行结果为:

myClass类的num属性的值为: 100

myClass类的method方法的输出为:

Hello World!

一般情况下,在编写一个类的时候,都会倾向于让类的对象被创建为有初始状态的。因此类可能会定义一个名为 init() 的特殊方法,此方法被称为初始化方法或者构造方法,与Java中的构造器概念是一样的,声明格式如下:

def __init__(self):
    self.name = ""

类中定义了 init() 初始化方法的话,在实例化该类的对象时会自动调用 init() 方法。 init() 方法和普通方法一样也是可以声明参数的,如果声明的参数不是默认参数的话,那么在实例化该类的对象时就必须得传递相应的参数。如下示例:

class myClass:

    def __init__(self,name,phoneNumber):
        print("name参数的值是:",name)
        print("phoneNumber参数的值是:",phoneNumber)

# 实例化对象时必须要传递参数
obj = myClass("小明","158223366555")

运行结果:

name参数的值是: 小明

phoneNumber参数的值是: 158223366555

<br>
关于self:

从以上的示例可以发现,类中的每一个方法都带有一个self参数,这个self代表的是当前的实例对象而非类,这个self类似于Java中的this关键字。我们可以通过这个self参数初始化实例属性的值:

class myClass:
    def __init__(self, name, phoneNumber):
        # 把参数的值赋值给属性
        self.name = name
        self.phoneNumber = phoneNumber

# 实例化对象时必须要传递参数
obj = myClass("小明", "158223366555")

print("name属性的值是:", obj.name)
print("phoneNumber属性的值是:", obj.phoneNumber)

运行结果:

name属性的值是: 小明

phoneNumber属性的值是: 158223366555

类的方法与普通的函数只有一个区别——它们必须有一个额外的第一个参数名称, 这个参数就是 self。
下面我们通过self来演示一下类与对象在解释器中的区别:

class myClass:

    def prt(self):
        print(self)
        print(self.__class__)

obj = myClass()
obj.prt()

运行结果:

<main.myClass object at 0x00000230BBDED390>

<class 'main.myClass'>

从运行结果中,可以看到,self代表着当前对象的内存地址,而self.class才是指向类。

实际上self并不是python的关键字,也就说这个名称并非是是固定的,你换成别的名称也一样可以,但是约定俗成的名称就是self,例如我随便改成其他名称也可以运行:

class myClass:

    def prt(abcd):
        print(abcd)
        print(abcd.__class__)

obj = myClass()
obj.prt()

运行结果:

<main.myClass object at 0x00000205A81CD3C8>

<class 'main.myClass'>

虽然可以将self改为其他的名称,但是在实际开发中,尽量不要使用其他的名称,以免造成混淆的问题。

<br>

类的方法


从以上的示例已经知道在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的当前实例对象。

下面我们使用类中的方法来实现一个三角形的打印:

class myClass:
    # 在实例化对象时需要传递三角形行数
    def __init__(self, row):
        self.row = row

    def prt(self):
        # 从self中拿出行数
        for i in range(self.row):
            for j in range(self.row - i):
                print(" ", end="")
            for k in range(2 * i + 1):
                print("*", end="")

            print()

# 创建对象并调用prt方法
obj = myClass(10)
obj.prt()

运行结果:
Python3 面向对象概述

<br>

继承


Python 同样支持类的继承,如果一种面向对象语言不支持继承,类就没有什么意义。子类(派生类)的定义如下所示:

class DerivedClassName(BaseClassName1):
    <statement-1>
    .
    .
    .
    <statement-N>

需要注意小括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
BaseClassName(示例中的父类名)必须与子类定义在一个作用域内。除了类,还可以用表达式,父类定义在另一个模块中时这一点非常有用:

class DerivedClassName(modname.BaseClassName):

下面我们用实际例子演示一下简单的单继承:

# 定义父类
class people:
    # 定义基本属性
    name = ""
    age = 0

    # 初始化属性的值
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 此方法用于打印属性的值
    def speak(self):
        print("My name is", self.name)
        print("I am", self.age, "years old")

# 定义子类,父类的名称放在小括号内,这是单继承
class student(people):
    def __init__(self, name, age):
        # 把参数传递给父类的初始化方法
        people.__init__(self, name, age)

# 实例化子类对象
stu = student("Jack", 15)
# 子类对象可以调用父类的方法和属性
stu.speak()
print("name is:", stu.name)
print("age is:", stu.age)

运行结果:

My name is Jack

I am 15 years old

name is: Jack

age is: 15

从以上示例我们可以知道继承的一个特性:子类继承父类后会拥有父类的所有非私有的东西(关于私有成员后面会介绍)。所以我们可以通过子类对象去调用父类的属性或方法。

<br>

多继承


Python同样有限的支持多继承形式。多继承的类定义形如下例:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

需要注意小括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
我们先来看看一个简单的多继承:

# 定义父类
class people:
    # 定义基本属性
    name = ""
    age = 0

    # 初始化属性的值
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 此方法用于打印属性的值
    def speak(self):
        print("My name is", self.name)
        print("I am", self.age, "years old")

# 定义父类
class singleDog:

    girlfriend=True
    def __init__(self,girlfriend):
        self.girlfriend=girlfriend

    def fff(self):
        if self.girlfriend:
            print("I am mot singleDog,I have girlfriend.hahaha~")
        else:
            print("I am singleDog,I don't have girlfriend.")

# 定义子类,父类的名称放在小括号内
class student(people,singleDog):
    def __init__(self, name, age,girlfriend):
        # 把参数传递给父类的初始化方法
        people.__init__(self, name, age)
        singleDog.__init__(self,girlfriend)

# 实例化子类对象
stu = student("Jack", 15,False)
# 子类对象可以调用所有父类的方法和属性
stu.speak()
stu.fff()

运行结果:

My name is Jack

I am 15 years old

I am singleDog,I don't have girlfriend.

多继承其实就是有多个父类罢了,虽然可能有时候会让人看起来有点乱。

以上我们提到了,如果继承多个父类的时候,A父类和B父类都有一个相同的方法,那么如果子类使用这个方法时没有指定使用哪一个父类的方法的话,就会默认调用从左边数起第一个父类的方法,例如我把上例中两个父类的方法都改为一样的,然后继承时不指定父类的方法看看会发生啥:

# 定义子类,父类的名称放在小括号内
class student(people,singleDog):
    def __init__(self, name, age,girlfriend):
        # 把参数传递给父类的初始化方法
        people.__init__(self, name, age)
        singleDog.__init__(self,girlfriend)

# 实例化子类对象
stu = student("Jack", 15,False)
# 两个父类的方法都改为一样的名称了
stu.speak()

运行结果:

My name is Jack

I am 15 years old

可以从以上运行结果看到,只调用了people类的方法,而singleDog类的方法没有被调用。在这种情况下如果你想调用另一个父类的方法可以使用以下方式:

# 实例化子类对象
stu = student("Jack", 15,False)
# 使用父类调用方法,把子类对象传递过去
people.speak(stu)
singleDog.speak(stu)

运行结果:

My name is Jack

I am 15 years old

I am singleDog,I don't have girlfriend.

<br>

方法重写


在继承中有一个概念,就是方法重写,子类可以重写父类的方法。如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法。并且要记住一个定律:在方法的调用上,子类有就调用子类的,子类没有才会调用父类的。代码示例:

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

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

# 实例化子类对象
child = Child()  
# 子类有就会调用子类的
child.myMethod()

运行结果:

调用子类方法

重写父类的方法时,记得方法名、参数和返回值的数据类型要一致,例如:

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

# 定义子类
class Child(Parent):
    # 重写父类方法
    def myMethod(self,name):
        print('调用子类方法:',name)
        return '子类'

# 实例化子类对象
child = Child()
# 子类有就会调用子类的
print(child.myMethod('子类'))

运行结果:

调用子类方法: 子类

子类

<br>

类的私有属性与私有方法


类的私有属性

__private_attrs:命名一个变量时以两个下划线开头,声明该属性为私有。私有的属性不能在类的外部被调用和直接访问,只能在该私有属性所属类的内部中使用,即便是子类也不能访问父类的私有属性,在类的内部方法使用私有属性时同样使用self进行调用: self.__private_attrs

类的私有方法

__private_method:和定义私有属性一样,在定义一个方法时,方法名以两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。同样的在内部调用时也是使用self去调用:self.__private_methods

私有属性示例,如果在外部调用类的私有属性的话就会报错,只能在内部调用:

class TestPrivate:
    # 定义私有属性
    __name = ""
    age = 0

    def __init__(self):
        self.__name = "Private"

    def prn(self):
        # 只有类的内部才能调用
        print("内部调用:",self.__name)

test = TestPrivate()
test.prn()
# 外部调用就会报错
print(test.__name)

运行就会报错:

内部调用: Private
Traceback (most recent call last):
  File "E:/PythonProject/TestPrivate.py", line 17, in <module>
    print(test.__name)       # 报错,实例对象不能访问私有属性
AttributeError: 'TestPrivate' object has no attribute '__name'

类的私有方法声明如下,同样的私有方法不能被外部调用只能内部调用:

class TestPrivateMethod:

    def __init__(self):
        # 同样的通过self调用私有方法
        self.__privateMethod()

    # 定义一个私有方法
    def __privateMethod(self):
        print("这是私有方法")

    def publicMethod(self):
        print("这是公共方法")

obj=TestPrivateMethod()
obj.publicMethod()
# 调用私有方法就会报错
obj.__privateMethod()

运行结果:

这是私有方法
这是公共方法
Traceback (most recent call last):
  File "E:/PythonProject/TestPrivateMethod.py", line 17, in <module>
    obj.__privateMethod()
AttributeError: 'TestPrivateMethod' object has no attribute '__privateMethod'

通过将变量、属性变形为私有成员,才能够实现封装。

<br>
####类的专有方法:

  • init: 构造函数,在生成对象时调用
  • del : 析构函数,释放对象时使用
  • repr : 打印,转换
  • setitem : 按照索引赋值
  • getitem: 按照索引获取值
  • len: 获得长度
  • cmp: 比较运算
  • call: 函数调用
  • add: 加运算
  • sub: 减运算
  • mul: 乘运算
  • div: 除运算
  • mod: 求余运算
  • pow: 乘方

<br>

运算符重载:

Python同样支持运算符重载,我们可以对类的专有方法进行重载,代码示例:

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b

   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)

   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

运行结果:

Vector(7,8)

<br>
针对 str 方法给出一个比较直观的例子:

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

    def __str__(self):
        return '这个人的名字是%s,已经%d岁了!'%(self.name,self.age)

a=people('Jack',15)
print(a)

运行结果:

这个人的名字是Jack,已经15岁了!

<br>
如果没有进行重载的话:

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

a=people('Jack',15)
print(a)

运行结果:

<main.people object at 0x0000019ACF76D3C8>

没有进行重载的话,打印的是这个实例对象的内存地址。