Python知识回顾(8)

Day 8
本节主要讲解的是Python中面向对象方面的知识。
写在前面:本节博文是就上一节面向对象的补充,博文内容较长。

1. 面向对象进阶

Python 是面向对象的语言,也支持面向对象编程的三大特性:继承、封装(隐藏)、多态。
封装(隐藏)
隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将“细节封装起来”,只对外暴露“相关调用方法”。
通过前面学习的“私有属性、私有方法”的方式,实现“封装”。Python 追求简洁的语法,没有严格的语法级的“访问控制符”,更多的是依靠程序员自觉实现。
继承
继承可以让子类具有父类的特性,提高了代码的重用性。
从设计上是一种增量进化,原有父类设计不变的情况下,可以增加新的功能,或者改进已有的算法。
多态
多态是指同一个方法调用由于对象不同会产生不同的行为。生活中这样的例子比比皆是:同样是休息方法,人不同休息方法不同。张三休息是睡觉,李四休息是玩游戏,程序员休息是“敲几行代码”。

1.1 封装

1.1.1 方法没有重载

在其他语言中,可以定义多个重名的方法,只要保证方法签名唯一即可。方法签名包含 3个部分:方法名、参数数量、参数类型。
Python 中,方法的的参数没有声明类型(调用时确定参数的类型),参数的数量也可以由可变参数控制。因此,Python 中是没有方法的重载的。定义一个方法即可有多种调用方式,相当于实现了其他语言中的方法的重载。如果我们在类体中定义了多个重名的方法,只有最后一个方法有效。
建议:不要使用重名的方法!Python 中方法没有重载。

class Person():
    def say_hi(self):
        print('谭松韵天下第一美!')

    def say_hi(self):
        print('给谭松韵打call!')
A = Person()
A.say_hi()

输出结果为:
给谭松韵打call!
可见,后一个方法将前一个方法覆盖掉了。

1.1.2 方法的动态性

Python 是动态语言,我们可以动态的为类添加新的方法,或者动态的修改类的已有的方法。

class Person():
    def say_hi(self):
        print('谭松韵天下第一美!')
    def say_hii(self):
        print('给谭松韵打call!')
def say_hiii(self):
    print('耿耿!')
def say_hii(self):
    print('袁今夏!')
Person.say_hiii = say_hiii
Person.say_hii = say_hii
A = Person()
A.say_hi()
A.say_hii()
A.say_hiii()

输出结果为:
谭松韵天下第一美!
袁今夏!
耿耿!
我们可以见到添加了say_hiii方法、将say_hii方法进行了替换。

1.1.3 私有属性和私有方法(实现封装)

Python 对于类的成员没有严格的访问控制限制,这与其他面向对象语言有区别。关于私有属性和私有方法,有如下要点:

  1. 通常我们约定,两个下划线开头的属性是私有的(private)。其他为公共的(public)。
  2. 类内部可以访问私有属性(方法)
  3. 类外部不能直接访问私有属性(方法)
  4. 类外部可以通过“_类名__私有属性(方法)名”访问私有属性(方法)

【注】方法本质上也是属性!只不过是可以通过()执行而已。所以,此处讲的私有属性和公有属性,也同时讲解了私有方法和公有方法的用法。如下测试中,同时也包含了私有方法和公有方法的例子。

class TV():
    __identify = "演员"#私有属性,通过dir可以查到_TV.__identify
    def __init__(self,name,age):
        self.name = name
        self.__age = age  #将实例属性私有化

    def introduce(self):
        print("她的年龄是{0}".format(self.__age))#直接访问私有化的实例
        print(self.name,"的职业是:",TV.__identify)#类内部可以直接访问私有属性
        self.__wife()
    def __wife(self):#私有实例方法 通过 dir 可以查到_TV__wife
        print("谭松韵是楷楷的偶像")
TV = TV('谭松韵',18)#实例化TV

print(TV.name)
print(dir(TV))
print(dir(TV._TV__wife))
TV.introduce()
print(TV._TV__age)
#print(TV.__age)#会报错,因为直接访问私有属性
#TV.__wife()#会报错,因为直接访问私有方法

运行结果为:
谭松韵
[’_TV__age’, ‘_TV__identify’, ‘_TV__wife’, ‘class’, ‘delattr’, ‘dict’, ‘dir’, ‘doc’, ‘eq’, ‘format’, ‘ge’, ‘getattribute’, ‘gt’, ‘hash’, ‘init’, ‘init_subclass’, ‘le’, ‘lt’, ‘module’, ‘ne’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘setattr’, ‘sizeof’, ‘str’, ‘subclasshook’, ‘weakref’, ‘introduce’, ‘name’]
[‘call’, ‘class’, ‘delattr’, ‘dir’, ‘doc’, ‘eq’, ‘format’, ‘func’, ‘ge’, ‘get’, ‘getattribute’, ‘gt’, ‘hash’, ‘init’, ‘init_subclass’, ‘le’, ‘lt’, ‘ne’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘self’, ‘setattr’, ‘sizeof’, ‘str’, ‘subclasshook’]
她的年龄是18
谭松韵 的职业是: 演员
谭松韵是楷楷的偶像
18
从打印的 TV 对象所有属性我们可以看出。私有属性“__age”在实际存储时是按照“_TV__age”这个属性来存储的。这也就是为什么我们不能直接使用“__age”而可以使用“_TV__age”的根本原因。

1.1.4 @property 装饰器

@property 可以将一个方法的调用方式变成“属性调用”。下面是一个简单的示例,让大家体会一下这种转变:

#简单测试@property
class Employee:
    @property
    def salary(self):
        return 30000
emp1 = Employee()
print(emp1.salary) #打印 30000
print(type(emp1.salary)) #打印<class 'int'>
#emp1.salary() #报错:TypeError: 'int' object is not callable
#emp1.salary =1000 #@property 修饰的属性,如果没有加 setter 方法,则为只读属性。此处修改报错:AttributeError: can't set attribute

@property 主要用于帮助我们处理属性的读操作、写操作。对于某一个属性,我们可以直接通过:emp1.salary = 30000
如上的操作读操作、写操作。但是,这种做法不安全。比如,我需要限制薪水必须为 1-10000的数字。这时候,我们就需要通过 getter、setter 方法来处理。

#简单测试@property
class Employee:
    def __init__(self,name,salary):
        self.name = name
        self.__salary = salary

    @property  # 相当于 salary 属性的 getter 方法@property #相当于 salary 属性的 getter 方法
    def salary(self):
        print("{2}的月薪为:{0},年薪为:{1}".format(self.__salary,self.__salary*12,self.name))
        return self.__salary
    @salary.setter
    def salary(self,salary):#相当于 salary 属性的 setter 方法
        if (0 < salary < 1000000):
            self.__salary = salary
        else:
            print("薪水录入错误!只能在 0-1000000 之间")


emp1 = Employee("kaikai",10000)
print(emp1.salary)
print(type(emp1.salary)) #打印<class 'int'>
emp1.salary =1000 #@property 修饰的属性,如果没有加 setter 方法,则为只读属性。此处修改报错:AttributeError: can't set attribute
emp1.salary = -1000

运行结果为:
kaikai的月薪为:10000,年薪为:120000
10000
kaikai的月薪为:10000,年薪为:120000
<class ‘int’>
薪水录入错误!只能在 0-1000000 之间

1.1.5 属性和方法命名总结

_xxx:保护成员,不能用“from module import * ”导入,只有类对象和子类对象能访问这些成员。
xxx:系统定义的特殊成员
__xxx: 类中的私有成员,只有类对象自己能访问,子类对象也不能访问。(但在类外部可以通过“对象名. _类名__xxx”这种特殊方式访问。Python 不存在严格意义的私有成员)。
注:再次强调,方法和属性都遵循上面的规则。

1.1.6 类编码风格
  1. 类名首字母大写,多个单词之间采用驼峰原则。
  2. 实例名、模块名采用小写,多个单词之间采用下划线隔开。
  3. 每个类,应紧跟“文档字符串”,说明这个类的作用。
  4. 可以用空行组织代码,但不能滥用。在类中,使用一个空行隔开方法;模块中,使用两个空行隔开多个类。

1.2 继承

继承是面向对象程序设计的重要特征,也是实现“代码复用”的重要手段。
如果一个新类继承自一个设计好的类,就直接具备了已有类的特征,就大大降低了工作难度。已有的类,我们称为“父类或者基类”,新的类,我们称为“子类或者派生类”。
在这里插入图片描述

1.2.1 语法格式

Python 支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:
class 子类类名(父类 1[,父类 2,…]):
类体
如果在类定义中没有指定父类,则默认父类是 object 类。也就是说,object 是所有类的父类,里面定义了一些所有类共有的默认实现,比如:new()。
定义子类时,必须在其构造函数中调用父类的构造函数。调用格式如下:
父类名.init(self, 参数列表)

#测试继承
class TV:
    def __init__(self,name,age):
        self.name = name
        self.__age = age

    def introduce(self):
        print("演员的名称是:{0},年龄是{1}".format(self.name,self.__age))

class Actor(TV):
    def __init__(self,name,age,score):
        self.score = score
        TV.__init__(self,name,age)#构造函数中包含调用父类构造函数。根据需要,不是必须。 子类并不会自动调用父类的__init__(),我们必须显式的调用它。

A = TV("谭松韵",18)
A.introduce()
print(dir(A))

输出结果为:
演员的名称是:谭松韵,年龄是18
[’_TV__age’, ‘class’, ‘delattr’, ‘dict’, ‘dir’, ‘doc’, ‘eq’, ‘format’, ‘ge’, ‘getattribute’, ‘gt’, ‘hash’, ‘init’, ‘init_subclass’, ‘le’, ‘lt’, ‘module’, ‘ne’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘setattr’, ‘sizeof’, ‘str’, ‘subclasshook’, ‘weakref’, ‘introduce’, ‘name’]

1.2.2 类成员的继承和重写
  1. 成员继承:子类继承了父类除构造方法之外的所有成员。
  2. 方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称为“重写”
#测试继承
class TV:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def introduce(self):
        print("演员的名称是:{0},年龄是{1}".format(self.name,self.__age))

    def say_age(self):
        print(self.name, "的年龄是:", self.age)

    def say_name(self):
        print("她是", self.name)

class Actor(TV):
    def __init__(self,name,age,score):
        self.score = score
        TV.__init__(self,name,age)#构造函数中包含调用父类构造函数。根据需要,不是必须。 子类并不会自动调用父类的__init__(),我们必须显式的调用它。

    def say_score(self):
        print(self.name, "的分数是:", self.score)

    def say_name(self):  # 重写父类的方法
        print("报告老师,她是", self.name)

s1 = Actor("谭松韵",15,85)
s1.say_score()
s1.say_name()
s1.say_age()

输出结果为:
谭松韵 的分数是: 85
报告老师,她是 谭松韵
谭松韵 的年龄是: 15

1.2.3 查看类的继承层次结构

通过类的方法 mro()或者类的属性__mro__可以输出这个类的继承层次结构。

class A:pass
class B(A):pass
class C(B):pass
print(C.mro())

执行结果:
[<class ‘main.C’>, <class ‘main.B’>, <class ‘main.A’>, <class ‘object’>]
在这里插入图片描述
object 类是所有类的父类,因此所有的类都有 object 类的属性和方法。我们显然有必要深入研究一下 object 类的结构。对于我们继续深入学习 Python 很有好处。

class Person:
	def __init__(self,name,age):
		self.name = name
		self.age = age
	def say_age(self):
		print(self.name,"的年龄是:",self.age)
obj = object()
print(dir(obj))
s2 = Person("kaikai",18)
print(dir(s2))

实验结果为:
在这里插入图片描述

从上面我们可以发现这样几个要点:

  1. Person 对象增加了六个属性:
    dict module weakref age name say_age
  2. object 的所有属性,Person 类作为 object 的子类,显然包含了所有的属性。
  3. 我们打印 age、name、say_age,发现 say_age 虽然是方法,实际上也是属性。只不过,这个属性的类型是“method”而已。
    age <class ‘int’>
    name <class ‘str’>
    say_age <class ‘method’>
1.2.4 重写__str__()方法

object 有一个__str__()方法,用于返回一个对于“对象的描述”,对应于内置函数 str()经常用于 print()方法,帮助我们查看对象的信息。str()可以重写。

class Person:
    def __init__(self,name,age):
        self.name = name
        self.__age = age
    def __str__(self):
        '''将对象转化成一个字符串,一般用于 print 方法'''
        return "名字是:{0},年龄是{1}".format(self.name,self.__age)
p = Person("楷楷",18)
print(p)

实验结果为:
名字是:楷楷,年龄是18

1.2.5 多重继承

Python 支持多重继承,一个子类可以有多个“直接父类”。这样,就具备了“多个父类”的特点。但是由于,这样会被“类的整体层次”搞的异常复杂,尽量避免使用。
在这里插入图片描述
实验结果为:
cc
bb
aa
Python 支持多继承,如果父类中有相同名字的方法,在子类没有指定父类名时,解释器将“从左向右”按顺序搜索。 MRO(Method Resolution Order):方法解析顺序。 我们可以通过 mro()方法获得“类的层次结构”,方法解析顺序也是按照这个“类的层次结构”寻找的。

#多重继承
class A:
    def aa(self):
        print("aa")
    def say(self):
        print("say AAA!")
class B:
    def bb(self):
        print("bb")
    def say(self):
        print("say BBB!")
class C(B,A):
    def cc(self):
        print("cc")
c = C()
print(C.mro()) #打印类的层次结构
c.say() #解释器寻找方法是“从左到右”的方式寻找,此时会执行B类中的 say()

运行结果为:
[<class ‘main.C’>, <class ‘main.B’>, <class ‘main.A’>, <class ‘object’>]
say BBB!

1.2.6 super()获得父类定义

在子类中,如果想要获得父类的方法时,我们可以通过 super()来做。super()代表父类的定义,不是父类对象。

#super
class A:
    def say(self):
        print("A:",self)
        print("say AAA!")

class B(A):
    def say(self):
        A.say(self)#调用父类的say()方法
        super().say()#通过super调用父类的方法
        print("say BBB!")
b = B()
b.say()

运行结果为:
A: <main.B object at 0x0000026F6E5E4508>
say AAA!
A: <main.B object at 0x0000026F6E5E4508>
say AAA!
say BBB!

1.3 多态

多态(polymorphism)是指同一个方法调用由于对象不同可能会产生不同的行为。在现实生活中,我们有很多例子。比如:同样是调用人的休息方法,张三的休息是睡觉,李四的休息是玩游戏,高淇老师是敲代码。同样是吃饭的方法,中国人用筷子吃饭,英国人用刀叉吃
饭,印度人用手吃饭。
关于多态要注意以下 2 点:

  1. 多态是方法的多态,属性没有多态。
  2. 多态的存在有 2 个必要条件:继承、方法重写。
class Animal:
	def shout(self):
		print("动物叫了一声")
class Dog(Animal):
	def shout(self):
		print("小狗,汪汪汪")
class Cat(Animal):
	def shout(self):
		print("小猫,喵喵喵")
def animalShout(a):
	if isinstance(a,Animal):
		a.shout() #传入的对象不同,shout 方法对应的实际行为也不同。
animalShout(Dog())
animalShout(Cat())

运行结果为:
小狗,汪汪汪
小猫,喵喵喵

1.4 一些特殊的方法与属性汇总

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.5 对象的浅拷贝和深拷贝

变量的赋值操作
只是形成两个变量,实际还是指向同一个对象。
浅拷贝
Python 拷贝一般都是浅拷贝。拷贝时,对象包含的子对象内容不拷贝。因此,源对象和拷贝对象会引用同一个子对象。
深拷贝
使用 copy 模块的 deepcopy 函数,递归拷贝对象中包含的子对象。源对象和拷贝对象所有的子对象也不同。

#测试对象的引用赋值、浅拷贝、深拷贝
import copy
class MobilePhone:
    def __init__(self,cpu,screen):
        self.cpu = cpu
        self.screen = screen
class CPU:
    def calculate(self):
        print("计算,算个 12345")

        print("CPU 对象:", self)

class Screen:
    def show(self):

        print("显示一个好看的画面,亮瞎你的钛合金大眼")
        print("屏幕对象:", self)

c = CPU()
s = Screen()
m = MobilePhone(c, s)
m.cpu.calculate()
n = m  # 两个变量,但是指向了同一个对象
print(m, n)
m2 = copy.copy(m)  # m2 是新拷贝的另一个手机对象
print(m, m2)
m.cpu.calculate()
m2.cpu.calculate()  # m2 和 m 拥有了一样的 cpu 对象和 screen 对象
m3 = copy.deepcopy(m)
m3.cpu.calculate()  # m3 和 m 拥有不一样的 cpu 对象和 screen对象

实验结果为:
计算,算个 12345
CPU 对象: <main.CPU object at 0x0000013B85DF87C8>
<main.MobilePhone object at 0x0000013B85DF8848> <main.MobilePhone object at 0x0000013B85DF8848>
<main.MobilePhone object at 0x0000013B85DF8848> <main.MobilePhone object at 0x0000013B85DF8948>
计算,算个 12345
CPU 对象: <main.CPU object at 0x0000013B85DF87C8>
计算,算个 12345
CPU 对象: <main.CPU object at 0x0000013B85DF87C8>
计算,算个 12345
CPU 对象: <main.CPU object at 0x0000013B85DF8A48>

1.6 组合

“is-a”关系,我们可以使用“继承”。从而实现子类拥有的父类的方法和属性。“is-a”关系指的是类似这样的关系:狗是动物,dog is animal。狗类就应该继承动物类。
“has-a”关系,我们可以使用“组合”,也能实现一个类拥有另一个类的方法和属性。”
has-a”关系指的是这样的关系:手机拥有 CPU。 MobilePhone has a CPU。

#组合测试
class MobilePhone:
	def __init__(self,cpu,screen):
		self.cpu = cpu
		self.screen = screen
class CPU:
	def calculate(self):
		print("计算,算个 12345")
class Screen:
	def show(self):
		print("显示一个好看的画面,亮瞎你的钛合金大眼")
c = CPU()
s = Screen()
m = MobilePhone(c,s)
m.cpu.calculate() #通过组合,我们也能调用 cpu 对象的方法。相当于手机对象间接拥有了“cpu 的方法”
m.screen.show()

运行结果为:
计算,算个 12345
显示一个好看的画面,亮瞎你的钛合金大眼
简单的理解就是:
将两个待组合的类实例化,然后作为一个大类的入参。

1.7 工厂模式与单例模式

设计模式是面向对象语言特有的内容,是我们在面临某一类问题时候固定的做法,设计模式有很多种,比较流行的是:GOF(Goup Of Four)23 种设计模式。当然,我们没有必要全部学习,学习几个常用的即可。对于初学者,我们学习两个最常用的模式:工厂模式和单例模式。

1.7.1 工厂模式的实现

工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进行统一的管理和控制。

#工厂模式测试
class Car_Factory:
    def choose(self,brand):
        if brand == "奔驰":
            return Benz()
        elif brand == "宝马":
            return BMW()
        elif brand == "比亚迪":
            return BYD()
        else:
            return ("请输入其他品牌")

class Benz:
    pass
class BMW:
    pass
class  BYD:
    pass
factory = Car_Factory()
c1 = factory.choose("奔驰")
c2 = factory.choose("大众")
c3 = factory.choose("宝马")
print(c1)
print(c2)
print(c3)

实验结果为 :
<main.Benz object at 0x000001E7A34F0988>
请输入其他品牌
<main.BMW object at 0x000001E7A34F09C8>

1.7.2 单例模式的实现

单例模式(Singleton Pattern)的核心作用是确保一个类只有一个实例,并且提供一个访问该实例的全局访问点。
单例模式只生成一个实例对象,减少了对系统资源的开销。当一个对象的产生需要比较多的资源,如读取配置文件、产生其他依赖对象时,可以产生一个“单例对象”,然后永久驻留内存中,从而极大的降低开销。
单例模式有多种实现的方式,我们这里推荐重写__new__()的方法。

class MySingleton:
    __obj = None
    __init_flag = True
    def __new__(cls, *args, **kwargs):
        if cls.__obj  == None:
            cls.__obj = object.__new__(cls)
            return cls.__obj

    def __init__(self,name):
        if MySingleton.__init_flag:
            print("init...")
            self.name = name
            MySingleton.__init_flag = False
a = MySingleton("aa")
print(a)
b = MySingleton("bb")
print(b)

输出结果为:
init…
<main.MySingleton object at 0x0000021B6695A748>
None
设计模式称之为“模式”,就是一些固定的套路。我们很容易用到其他场景上,比如前面讲的工厂模式,我们需要将工厂类定义成“单例”,只需要简单的套用即可实现:

class Car_Factory:
    __obj = None
    __init_flag = True


    def choose(self,brand):
        if brand == "奔驰":
            return Benz()
        elif brand == "宝马":
            return BMW()
        elif brand == "比亚迪":
            return BYD()
        else:
            return ("请输入其他品牌")
    def __new__(cls, *args, **kwargs):
        if cls.__obj  == None:
            cls.__obj = object.__new__(cls)
            return cls.__obj

    def __init__(self):
        if Car_Factory.__init_flag:
            print("init...")
            Car_Factory.__init_flag = False

class Benz:
    pass
class BMW:
    pass
class  BYD:
    pass
factory = Car_Factory()
c1 = factory.choose("奔驰")
c2 = factory.choose("大众")
c3 = factory.choose("宝马")
print(c1)
print(c2)
print(c3)
factory2 = Car_Factory()
print(factory)
print(factory2)

实验结果为:
init…
<main.Benz object at 0x0000014836903708>
请输入其他品牌
<main.BMW object at 0x00000148369036C8>
<main.Car_Factory object at 0x0000014836903688>
None

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值