第八篇 python 面向对象编程

Posted AI浩

tags:

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

11 面向对象编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。

而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

11.1 类和对象

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

11.1.1 类

人以类聚 物以群分。
具有相似内部状态和运动规律的实体的集合(或统称为抽象)。 
具有相同属性和行为事物的统称

类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象

11.2.2 对象

某一个具体事物的存在 ,在现实世界中可以是看得见摸得着的。
可以是直接使用的

11.2.3 类和对象的关系

类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;

11.2.4 定义类

定义一个类,格式如下:

class 类名:
    方法列表

例:定义一个Car类

# 定义类
class Car:
    # 方法
    def getCarInfo(self):
        print('车轮子个数:%d, 颜色%s'%(self.wheelNum, self.color))

    def move(self):
        print("车正在移动...")

说明:

  • 定义类时有2种:新式类和经典类,上面的Car为经典类,如果是Car(object)则为新式类
  • 类名 的命名规则按照"大驼峰"

11.2.5 创建对象

通过上一节,定义了一个Car类;就好比有车一个张图纸,那么接下来就应该把图纸交给生成工人们去生成了

python中,可以根据已经定义的类去创建出一个个对象

创建对象的格式为:

对象名 = 类名()

创建对象demo:

# 定义类
class Car:
    # 移动
    def move(self):
        print('车在奔跑...')

    # 鸣笛
    def toot(self):
        print("车在鸣笛...嘟嘟..")


# 创建一个对象,并用变量BMW来保存它的引用
BMW = Car()
BMW.color = '黑色'
BMW.wheelNum = 4 #轮子数量
BMW.move()
BMW.toot()
print(BMW.color)
print(BMW.wheelNum)

运行结果:

车在奔跑...
车在鸣笛...嘟嘟..
黑色
4

11.2.6 __init__()方法

1)使用方式

def 类名:
    #初始化函数,用来完成一些默认的设定
    def __init__():
        pass

2)__init__()方法的调用

# 定义汽车类
class Car:

    def __init__(self):
        self.wheelNum = 4
        self.color = '蓝色'

    def move(self):
        print('车在跑,目标:夏威夷')

# 创建对象
BMW = Car()

print('车的颜色为:%s'%BMW.color)
print('车轮胎数量为:%d'%BMW.wheelNum)

运行结果:

车的颜色为:蓝色
车轮胎数量为:4

11.2.7 魔法方法

1)打印id()

如果把BMW使用print进行输出的话,会看到如下的信息,例:

# 定义汽车类
class Car:

    def __init__(self):
        self.wheelNum = 4
        self.color = '蓝色'

    def move(self):
        print('车在跑,目标:夏威夷')

# 创建对象
BMW = Car()

print(BMW)

运行结果:

<__main__.Car object at 0x0000014F596F8400>

即看到的是创建出来的BMW对象在内存中的地址

2) 定义__str__()方法

class Car:
    def __init__(self, newWheelNum, newColor):
        self.wheelNum = newWheelNum
        self.color = newColor
    def __str__(self):
        msg = "嘿。。。我的颜色是" + self.color + "我有" + str(self.wheelNum) + "个轮胎..."
        return msg

    def move(self):
        print('车在跑,目标:夏威夷')


BMW = Car(4, "白色")
print(BMW)

运行结果:

嘿。。。我的颜色是白色我有4个轮胎...

3)定义__del__()方法

创建对象后,python解释器默认调用__init__()方法;

当删除一个对象时,python解释器也会默认调用一个方法,这个方法为__del__()方法

import time


class Animal(object):
    # 初始化方法
    # 创建完对象后会自动被调用
    def __init__(self, name):
        print('__init__方法被调用')
        self.__name = name

    # 析构方法
    # 当对象被删除时,会自动被调用
    def __del__(self):
        print("__del__方法被调用")
        print("%s对象马上被干掉了..." % self.__name)


# 创建对象
dog = Animal("哈皮狗")
# 删除对象
del dog
cat = Animal("波斯猫")
cat2 = cat
cat3 = cat
print("---马上 删除cat对象")
del cat
print("---马上 删除cat2对象")
del cat2
print("---马上 删除cat3对象")
del cat3
print("程序2秒钟后结束")
time.sleep(2)

运行结果:

__init__方法被调用
__del__方法被调用
哈皮狗对象马上被干掉了...
__init__方法被调用
---马上 删除cat对象
---马上 删除cat2对象
---马上 删除cat3对象
__del__方法被调用
波斯猫对象马上被干掉了...
程序2秒钟后结束
  • 当有1个变量保存了对象的引用时,此对象的引用计数就会加1
  • 当使用del删除变量指向的对象时,如果对象的引用计数不会1,比如3,那么此时只会让这个引用计数减1,即变为2,当再次调用del时,变为1,如果再调用1次del,此时会真的把对象进行删除

总结

  • 在python中方法名如果是__xxxx__()的,那么就有特殊的功能,因此叫做“魔法”方法
  • 当使用print输出对象的时候,只要自己定义了__str__(self)方法,那么就会打印从在这个方法中return的数据

常用的魔法方法列表

魔法方法含义
基本的魔法方法
_new_(cls[, …])1. new 是在一个对象实例化的时候所调用的第一个方法 2. 它的第一个参数是这个类,其他的参数是用来直接传递给 init 方法 3. new 决定是否要使用该 init 方法,因为 new 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 new 没有返回实例对象,则 init 不会被调用 4. new 主要是用于继承一个不可变的类型比如一个 tuple 或者 string
_init_(self[, …])构造器,当一个实例被创建的时候调用的初始化方法
_del_(self)析构器,当一个实例被销毁的时候调用的方法
_call_(self[, args…])允许一个类的实例像函数一样被调用:x(a, b) 调用 x.call(a, b)
_len_(self)定义当被 len() 调用时的行为
_repr_(self)定义当被 repr() 调用时的行为
_str_(self)定义当被 str() 调用时的行为
_bytes_(self)定义当被 bytes() 调用时的行为
_hash_(self)定义当被 hash() 调用时的行为
_bool_(self)定义当被 bool() 调用时的行为,应该返回 True 或 False
_format_(self, format_spec)定义当被 format() 调用时的行为
有关属性
_getattr_(self, name)定义当用户试图获取一个不存在的属性时的行为
_getattribute_(self, name)定义当该类的属性被访问时的行为
_setattr_(self, name, value)定义当一个属性被设置时的行为
_delattr_(self, name)定义当一个属性被删除时的行为
_dir_(self)定义当 dir() 被调用时的行为
_get_(self, instance, owner)定义当描述符的值被取得时的行为
_set_(self, instance, value)定义当描述符的值被改变时的行为
_delete_(self, instance)定义当描述符的值被删除时的行为
比较操作符
_lt_(self, other)定义小于号的行为:x < y 调用 x.lt(y)
_le_(self, other)定义小于等于号的行为:x <= y 调用 x.le(y)
_eq_(self, other)定义等于号的行为:x == y 调用 x.eq(y)
_ne_(self, other)定义不等号的行为:x != y 调用 x.ne(y)
_gt_(self, other)定义大于号的行为:x > y 调用 x.gt(y)
_ge_(self, other)定义大于等于号的行为:x >= y 调用 x.ge(y)
算数运算符
_add_(self, other)定义加法的行为:+
_sub_(self, other)定义减法的行为:-
_mul_(self, other)定义乘法的行为:*
_truediv_(self, other)定义真除法的行为:/
_floordiv_(self, other)定义整数除法的行为://
_mod_(self, other)定义取模算法的行为:%
_divmod_(self, other)定义当被 divmod() 调用时的行为
_pow_(self, other[, modulo])定义当被 power() 调用或 ** 运算时的行为
_lshift_(self, other)定义按位左移位的行为:<<
_rshift_(self, other)定义按位右移位的行为:>>
_and_(self, other)定义按位与操作的行为:&
_xor_(self, other)定义按位异或操作的行为:^
_or_(self, other)定义按位或操作的行为:|
反运算
_radd_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rsub_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rmul_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rtruediv_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rfloordiv_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rmod_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rdivmod_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rpow_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rlshift_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rrshift_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_rxor_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
_ror_(self, other)(与上方相同,当左操作数不支持相应的操作时被调用)
增量赋值运算
_iadd_(self, other)定义赋值加法的行为:+=
_isub_(self, other)定义赋值减法的行为:-=
_imul_(self, other)定义赋值乘法的行为:*=
_itruediv_(self, other)定义赋值真除法的行为:/=
_ifloordiv_(self, other)定义赋值整数除法的行为://=
_imod_(self, other)定义赋值取模算法的行为:%=
_ipow_(self, other[, modulo])定义赋值幂运算的行为:**=
_ilshift_(self, other)定义赋值按位左移位的行为:<<=
_irshift_(self, other)定义赋值按位右移位的行为:>>=
_iand_(self, other)定义赋值按位与操作的行为:&=
_ixor_(self, other)定义赋值按位异或操作的行为:^=
_ior_(self, other)定义赋值按位或操作的行为:|=
一元操作符
_neg_(self)定义正号的行为:+x
_pos_(self)定义负号的行为:-x
_abs_(self)定义当被 abs() 调用时的行为
_invert_(self)定义按位求反的行为:~x
类型转换
_complex_(self)定义当被 complex() 调用时的行为(需要返回恰当的值)
_int_(self)定义当被 int() 调用时的行为(需要返回恰当的值)
_float_(self)定义当被 float() 调用时的行为(需要返回恰当的值)
_round_(self[, n])定义当被 round() 调用时的行为(需要返回恰当的值)
_index_(self)1. 当对象是被应用在切片表达式中时,实现整形强制转换 2. 如果你定义了一个可能在切片时用到的定制的数值型,你应该定义 index 3. 如果 index 被定义,则 int 也需要被定义,且返回相同的值
上下文管理(with 语句)
_enter_(self)1. 定义当使用 with 语句时的初始化行为 2. enter 的返回值被 with 语句的目标或者 as 后的名字绑定
_exit_(self, exc_type, exc_value, traceback)1. 定义当一个代码块被执行或者终止后上下文管理器应该做什么 2. 一般被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作
容器类型
_len_(self)定义当被 len() 调用时的行为(返回容器中元素的个数)
_getitem_(self, key)定义获取容器中指定元素的行为,相当于 self[key]
_setitem_(self, key, value)定义设置容器中指定元素的行为,相当于 self[key] = value
_delitem_(self, key)定义删除容器中指定元素的行为,相当于 del self[key]
_iter_(self)定义当迭代容器中的元素的行为
_reversed_(self)定义当被 reversed() 调用时的行为
_contains_(self, item)定义当使用成员测试运算符(in 或 not in)时的行为

11.2.8 self

self可以理解为自己,类似C#中的this,例:

# 定义一个类
class Animal:

    # 方法
    def __init__(self, name):
        self.name = name

    def printName(self):
        print('名字为:%s' % self.name)


# 定义一个函数
def myPrint(animal):
    animal.printName()


dog1 = Animal('淘淘')
myPrint(dog1)
dog2 = Animal('贝贝')
myPrint(dog2)

运行结果:

名字为:淘淘
名字为:贝贝

总结

所谓的self,可以理解为自己

可以把self当做C++中类里面的this指针一样理解,就是对象自身的意思

某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以开发者只需要传递后面的参数即可

11.2 属性

属性分为类属性和实例属性。

类属性就是属于类所有,可以直接用类名.属性名直接调用,类的属性在内存中只有一份。实例属性就是在__init__()方法中初始化的属性;
实例属性属于类的对象所有,可以用对象名.属性名的形式进行调用,但是不能用类名.属性名进行调用 。因为实例属性只有在实例创建时,才会初始化创建。

11.2.1 类属性

类属性就是类对象所拥有的属性,它被所有类对象实例对象所共有,在内存中只存在一个副本,这个和C#中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象实例对象访问,例:

class People(object):
    name = 'Tom'  #公有的类属性
    __age = 12     #私有的类属性

p = People()

print(p.name)           #正确
print(People.name)      #正确
print(p.__age)            #错误,不能在类外通过实例对象访问私有的类属性
print(People.__age)        #错误,不能在类外通过类对象访问私有的类属性

类属性的访问:

import time


class Test(object):
    name = 'scolia'


a = Test()
print(Test.name)  # 通过类进行访问
print(a.name)  # 通过实例进行访问

运行结果:

scolia
scolia

可以访问的,但是,试图修改这个属性的话:

class Test(object):
    name = 'scolia'


a = Test()
Test.name = 'scolia good'  # 通过类进行修改
print(Test.name)
print(a.name)

运行结果:

scolia good
scolia good

我们发现两者都修改成功了。再尝试通过实例来修改属性的话:

class Test(object):
    name = 'scolia'

a = Test()
a.name = 'scolia good'  # 通过实例进行修改
print(Test.name)
print(a.name)

运行结果:

scolia
scolia good

我们发现类的属性没有修改,而实例的属性则修改成功了。这究竟是为什么?

其实这里的情况非常类似于局部作用域和全局作用域。

我在函数内访问变量时,会先在函数内部查询有没有这个变量,如果没有,就到外层中找。这里的情况是我在实例中访问一个属性,但是我实例中没有,我就试图去创建我的类中寻找有没有这个属性。找到了,就有,没找到,就抛出异常。而当我试图用实例去修改一个在类中不可变的属性的时候,我实际上并没有修改,而是在我的实例中创建了这个属性。而当我再次访问这个属性的时候,我实例中有,就不用去类中寻找了。如果用一张图来表示的话:

11.2.2 实例属性(对象属性)

实例属性是在_init_(self)方法定义的属性,属于对象的本身,只能通过对象.属性来访问,不能用过类.属性来访问。例:

class People(object):
    address = '山东' #类属性
    def __init__(self):
        self.name = 'xiaowang' #实例属性
        self.age = 20 #实例属性

p = People()
p.age =12 #实例属性
print(p.address) #正确
print(p.name)    #正确
print(p.age)     #正确

print(People.address) #正确
print(People.name)    #错误
print(People.age)     #错误

11.2.3 私有属性

如果有一个对象,当需要对其进行修改属性时,有2种方法

  • 对象名.属性名 = 数据 ---->直接修改
  • 对象名.方法名() ---->间接修改

为了更好的保存属性安全,即不能随意修改,一般的处理方式为

  • 将属性定义为私有属性
  • 添加一个可以调用的方法,供调用
class People(object):

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

    def getName(self):
        return self.__name

    def setName(self, newName):
        if len(newName) >= 5:
            self.__name = newName
        else:
            print("error:名字长度需要大于或者等于5")

xiaoming = People("AI浩")
print(xiaoming.__name)
Traceback (most recent call last):
  File "C:/Users/WH/Desktop/Python基础/第一个Python程序.py", line 16, in <module>
    print(xiaoming.__name)
AttributeError: 'People' object has no attribute '__name'

直接调用私有属性会报错,我需要定义个方法,调用方法

class People(object):

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

    def getName(self):
        return self.__name

    def setName(self, newName):
        if len(newName) >= 5:
            self.__name = newName
        else:
            print("error:名字长度需要大于或者等于5")


xiaoming = People("张三")

xiaoming.setName("wanger")
print(xiaoming.getName())

xiaoming.setName("lisi")
print(xiaoming.getName())

运行结果:

wanger
error:名字长度需要大于或者等于5
wanger

总结

  • 如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性
  • Python中没有像C++中public和private这些关键字来区别公有属性和私有属性
  • 它是以属性命名方式来区分,如果在属性名前面加了2个下划线’__',则表明该属性是私有属性,否则为公有属性(方法也是一样,方法名前面加了2个下划线的话表示该方法是私有的,否则为公有的)。

11.3 继承

在程序中,继承描述的是事物之间的所属关系,例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物;同理,波斯猫和巴厘猫都继承自猫,而沙皮狗和斑点狗都继承足够,如下如所示:

# 定义一个父类,如下:
class Cat(object):

    def __init__(self, name, color="白色"):
        self.name = name
        self.color = color

    def run(self):
        print("%s--在跑" % self.name)


# 定义一个子类,继承Cat类如下:
class Bosi(Cat):

    def setNewName(self, newName):
        self.name = newName

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


bs = Bosi("印度猫")
print('bs的名字为:%s' % bs.name)
print('bs的颜色为:%s' % bs.color)
bs.eat()
bs.setNewName('波斯')
bs.run()

运行结果:

bs的名字为:印度猫
bs的颜色为:白色
印度猫--在吃
波斯--在跑

结论:

  • 虽然子类没有定义__init__方法,但是父类有,所以在子类继承父类的时候这个方法就被继承了,所以只要创建Bosi的对象,就默认执行了那个继承过来的__init__方法

  • 子类在继承的时候,在定义类时,小括号()中为父类的名字

  • 父类的属性、方法,会被继承给子类

11.3.1 私有属性和方法的继承

class Animal(object):

    def __init__(self, name='动物', color='白色'):
        self.__name = name
        self.color = color

    def __test(self):
        print(self.__name)
        print(self.color)

    def test(self):
        print(self.__name)
        print(self.color)


class Dog(Animal):
    def dogTest1(self):
        # print(self.__name) #不能访问到父类的私有属性
        print(self.color)

    def dogTest2(self):
        # self.__test() #不能访问父类中的私有方法
        self.test()


A = Animal()
# print(A.__name) #程序出现异常,不能访问私有属性
print(A.color)
# A.__test() #程序出现异常,不能访问私有方法
A.test()

print("------分割线-----")

D = Dog(name="小花狗", color="黄色")
D.dogTest1()
D.dogTest2()

运行结果:


白色
动物
白色
------分割线-----
黄色
小花狗
黄色

结论:

私有的属性,不能通过对象直接访问,但是可以通过方法访问
私有的方法,不能通过对象直接访问
私有的属性、方法,不会被子类继承,也不能被访问
一般情况下,私有的属性、方法都是不对外公布的,往往用来做内部的事情,起到安全的作用

11.3.2 多继承

Python中多继承的格式如下:

# 定义一个父类
class A:
    def printA(self):
        print('----A----')

# 定义一个父类
class B:
    def printB(self):
        print('----B----')

# 定义一个子类,继承自A、B
class C(A,B):
    def printC(self):
        print('----C----')

obj_C = C()
obj_C.printA()
obj_C.printB()

运行结果:

----A----
----B----

结论:

python中是可以多继承的
父类中的方法、属性,子类会继承

如果父类A和父类B中,有一个同名的方法,那么通过子类去调用的时候,调用哪个?

# coding=utf-8
class base(object):
    def test(self):
        print('----base test----')


class A(base):
    def test(self):
        print('----A test----')


# 定义一个父类
class B(base):
    def test(self):
        print('----B test----')


# 定义一个子类,继承自A、B
class C(A以上是关于第八篇 python 面向对象编程的主要内容,如果未能解决你的问题,请参考以下文章

Python全栈开发之路 第八篇:面向对象编程设计与开发

第八篇:面向对象编程

Python之路第八篇:Python基础(24)——面向对象进阶

python基础篇第八篇面向对象(下)

python学习之路基础篇(第八篇)

第八篇:面向对象