python从零到一之基础篇 终章 面向对象基础

python 基础篇

  • 前言
  • 面向对象(终极篇)
    • 什么是面向对象
    • 对象的演变
    • 类的定义
    • 构造方法
    • 实例属性和实例方法
      • 实例属性
      • 实例方法
      • 方法和函数的区别
    • 类对象
    • 类属性
    • 类方法
    • 静态方法
    • 内存分析实例对象和类对象创建过程(重要)
    • __ del __ 方法(析构函数)和垃圾回收机制
    • __ call__方法和可调用对象
    • 关于方法的重载问题
    • 方法的动态性
    • 私有属性和私有方法(实现封装)
    • @property装饰器
    • 属性和方法命名总结
    • 类编码风格
    • 面向对象三大特征介绍
      • 继承
        • 语法格式
        • 类成员的继承和重写
        • 查看类的继承和重写
        • object根类
        • 多重继承
        • MRO()
        • super()获得父类定义
      • 多态
        • 特殊方法和运算符重载
      • 特殊属性
      • 对象的浅拷贝和深拷贝
      • 组合
      • 设计模式_工厂模式
      • 设计模式_单例模式

前言

本次博客更新的是python基础篇的最后一章,也是相当重要的一章——面向对象。然后我们即将进入python进阶篇的学习。
本次篇幅较长,因为及其重要,希望耐心观看!!!

面向对象(终极篇)

什么是面向对象

在了解什么是面向对象之前,我们需要记住一句话:python一切皆对象
面向对象编程的思想主要是针对大型的软件设计的,面向对象使得程序扩展性更强,可读性更好。

面向对象编程将数据和操作数据的方法封装到了对象中,组织代码和数据的方式更灵活,python是真正的面向对象的编程语言,完全支持面向对象的基本功能:继承、多态、封装。

python支持面向对象,也包含了面向过程和函数式编程,它是集成多种编程方式的编程范式

对象的演变

随着编程面临的问题越来越复杂,编程语言本身也在进化,从主要处理简单的数据开始,随着数据变多进化有了数组;数据类型变复杂进化出了结构体;处理数据的方式和逻辑变复杂进化出了对象。

  1. 简单数据
    类似于10,20,30这样数值型的数字,可以看成是简单数据。最初的计算机编程都是这样的数字。
  2. 数组
    将同类型的数据放到一起就是数组
  3. 结构体
    将不同类型的数据放在一起是C语言中的数据结构,如:
struct resume {
	int age;
	char name[10];
	double t;
};
  1. 对象
    将不同类型的数据、函数放在一起就是对象:
class Student:
	school = “小学”			#类属性
	score = 100				#类属性
	def __init__(self,name,age):
		self.name = name		#实例属性
		self.age = age
		Student.score = Student.count + 1

	def hello(self):	#实例方法
		print("我的学校是:",Student.school)
		print(slef.name,"的年龄是:",self.age) 

前面我们了解过的数据类型,如整数也是一个对象,是一个包含了加法。乘法等方法的对象

类的定义

对象是类的实例,类是对象的抽象
我们通过类定义数据类型的属性和方法,也就是说:类将行为和状态打包在一起。

python从零到一之基础篇 终章 面向对象基础_第1张图片
对象是累的具体实现,一般称为类的实例。
从一个类创建对象时,每个对象都会共享这个类的行为(类中定义的方法),也会有自己的属性值。也就是说:方法代码是共享的,属性数据不是。
python从零到一之基础篇 终章 面向对象基础_第2张图片
python中一切皆对象。类也被称为类对象,类的实例称为实例对象。

定义类的语法格式:

class 类名:
	类体

注意:

  1. 类名必须符合标识符的规则:首字母大写,采用“驼峰原则”
  2. 类体中我们可以定义属性和方法
  3. 属性用来描述数据,函数用来描述这些数据相关的操作

【操作】类的定义:

class Student:
  def __init__(self,name,score):  #构造方法第一个参数必须是self
    self.name = name        #实例属性
    self.score = score       #

  def tell(self):       #实例方法
    print(self.name,"的分数是:",self.score)

s1 = Student("shy-2",100)		#s1是实例对象 自动调用__init__()方法
s1.tell()

构造方法

类是抽象的,也被称为对象的模版。我们需要通过类这个模版创建类的实例对象,然后才能使用类定义的功能

python对象包含三个部分,前面博客提到过:id(identity 识别码)、type(对象类型)、value(对象的值)

现在我们可以精确的说,一个python对象包含以下部分:

  1. id
  2. type
  3. value
    • 属性
    • 方法

创建对象,我们需要定义构造函数__init__()。构造函数用于执行实例对象的初始化工作,即对象创建后初始化当前对象的相关属性,无返回值。

__ init__ ()注意事项:

  1. 名称固定,必须是:__ init __()
  2. 第一个参数固定,必须为:self 。self指的就是刚刚创建好的实例对象。
  3. 构造函数通常用来执行实例对象的实例属性:
def __init__(self,name,score):
	self.name = name   #实例属性
	self.score = score
  1. 通过类名(参数列表)来调用构造函数,调用后,将创建好的对象返回给相应的变量

__ init__()和 __ new__():
__ init__()方法:初始化创建好的对象,初始化指的是:给实例属性赋值

__ new__()方法:用于创建对象,我们不需要重新定义该方法

如果我们不定义构造函数,系统会提供一个默认的构造方法

实例属性和实例方法

实例属性

实例属性是从属于实例对象的属性,也叫做实例变量,它的使用有以下要点:

  1. 实例属性一般在 构造方法中通过代码定义:self.实例属性名 = 初始值
  2. 在本类的其他实例方法中,也是通过self进行访问的:self.实例属性名
  3. 创建实例对象后,通过实例对象访问:
    object = 类名() #创建对象,调用构造方法初始化属性
    object.实例属性名 = 值 #可以是给已有的属性赋值也可以新加属性

实例方法

实例方法是从属于实例对象的方法,实例方法定义格式:

def 方法名(self,[形参列表]):
	函数体

方法的调用格式如下:对象.方法名([实参列表])
注意:

  1. 定义实例方法时,第一个参数必须为self,和前面一样,self指当前的实例对象
  2. 调用实例方法时,不需要也不能给self传参,self由解释器自动传参

其他的操作:

  1. dir(obj)可以获得对象的所有属性和方法
  2. obj.__dict __对象的属性字典
  3. pass空语句
  4. isinstance(对象,类型)判断对象是不是指定类型

方法和函数的区别

  1. 都是用来完成一个功能的语句块,本质一样
  2. 方法调用时,通过对象来调用,方法时从属于特定的实例对象
  3. 直观上看,方法定义时需要传递self,函数不需要

类对象

在类定义:class 类名:,当解释器执行class语句时,就会创建一个类对象
【操作】创建类对象:


class Student:
    pass #空语句print(type(Student))
print(id(Student))
S = Student
stu = S()
print(stu)

执行结果:
<class 'type'>
2660071558648
<__main__.Student object at 0x0000026B593619E8>

可以看到生成了一个变量名就是类名Student的对象。我们可以通过赋值给新变量S,也能实现相关的调用

类属性

类属性从属于类对象的属性,也是称为类变量、由于类属性是从属于类对象,可以被所有的实例对象共享

类属性的定义方式:

class 类名:
	类变量名 = 初始值

在类中或者类的外面我们可以通过:类名.类变量名来调用

【操作】类属性的使用:


class Student:
    school = "aabb"   #类属性
    score = 10       #类属性
    
    def __init__(self,name,age):
        self.name = name  #实例属性
        self.age = age
        Student.score = Student.score+10
        
    def say_age(self):    #实例方法
        print("我的学校是:",Student.school)
        print(self.name,"年龄是:",self.age)
    
s1 = Student("张三",10)   #s1是实例对象,自动调用__init__()方法
s1.say_age()
print("一共创建{0}个Student对象".format(Student.score))
我的学校是: aabb
张三 年龄是: 10
一共创建20个Student对象

类方法

类方法是从属于类对象的方法,类方法通过装饰器@classmethod来定义,格式:

@classmethod
def 类方法名(cls[,形参列表]):
	函数体

要点:

  1. @classmethod必须位于方法上面一行
  2. 第一个cls必须有:cls指的就是类对象本身
  3. 调用类方法的格式:类名.类方法名(参数列表),参数列表中不需要也不能给cls传值
  4. 类方法中访问实例属性和实例方法会导致错误
  5. 子类继承父类方法时,传入cls是子类对象,而非父类对象

【操作】类方法使用:

class Student:
    company = "xixi"  #类属性
    
    @classmethod
    def printCompany(cls):
        print(cls.company)

Student.printCompany()

静态方法

python中允许定义与类对象无关的方法称为:静态方法

静态方法和在模块中定义的普通函数没有区别,只不过静态方法放到了类的名字空间里面需要类才能调用

静态方法通过装饰器@staticmethod来定义,格式:

@staticmethod
def 静态方法名([形参列表]):
	函数体

注意:

  1. @staticmethod必须位于方法上面一行
  2. 调用静态方法格式:类名.静态方法名(参数列表)
  3. 静态方法中访问实例属性和实例方法会导致错误

【操作】静态方法的使用:

class Student:
    company = 'xixi'  #类属性
    
    @staticmethod
    def add(a,b):  #静态方法
        print("{0}+{1}={2}".format(a,b,(a+b)))
        return a+b
Student.add(10,10)

内存分析实例对象和类对象创建过程(重要)

class Student:
    company = 'xixi'   #类属性
    count = 0          #类属性
    
    def __init__(self,name,score):
        self.name = name    #实例属性
        self.score = score
        Student.count = Student.count +1
        
    def st_score(self):    #实例方法
        print("我的公司是:",Student.company)
        print(self.name,"的分数是:",self.score)
​
s1 = Student("shy-2",22)   #s1时实例对象 自动调用构造方法
s1.st_score()
print("一共创建{0}个Student对象".format(Student.count))


执行结果:
我的公司是: xixi
shy-2 的分数是: 22
一共创建1个Student对象

python从零到一之基础篇 终章 面向对象基础_第3张图片

__ del __ 方法(析构函数)和垃圾回收机制

__del__方法称为“析构方法”,用于实现对象被销毁时所需的操作。比如:释放对象占用的资源,例如:打开的文件资源、网络连接等。

Python实现自动的垃圾回收,当对象没有被引用时(引用计数为 0),由垃圾回收器 调用__del__方法。

我们可以通过del语句删除对象,从而保证调用__del__方法
系统会自动提供__del__方法,一般不需要自定义析构方法。

#  析构函数
class Person:
    def __del__(self):
        print("销毁对象:{0}".format(self))

p1 = Person()
p2 = Person()

del p2
print("程序结束")

执行结果:
销毁对象:<__main__.Person object at 0x0000011CB59BD748>
销毁对象:<__main__.Person object at 0x0000011CB59BD518>
程序结束

__ call__方法和可调用对象

定义了__call__方法的对象称为可调用对象,即该对象可以像函数一样被调用

# 测试__call__ 可调用对象
class SalaryAccount:
    """工资计算类"""
    
    def __call__(self,salary):
        yearSalary = salary*12
        daySalary = salary//30
        hourSalary = daySalary//8
        return dict(monthSalary=salary,yearSalary=yearSalary,daySalary=daySalary ,hourSalary=hourSalary)

s = SalaryAccount()

print(s(5000))# 可 以 像 调 用 函 数 一 样 调 用 对 象 的 _ _ c a ll _ _ 方 法

执行结果:
{'monthSalary': 5000, 'yearSalary': 60000, 'daySalary': 166, 'hourSalary': 20}

关于方法的重载问题

在其他的编程语言中,可以定i多个重名的方法,只要保证方法签名唯一即可。方法签名包含三个部分:方法名、参数数量、参数类型

python中,方法的参数没有声明类型(只有在调用时才能确定参数的类型),参数的数量也可以由可变参数控制,所以python中是没有方法的重载的,定义一个方法即可有多种调用方式,相当于其他编程语言的方法重载。

如果我们在类体中定义了多个重名的方法,只有最后一个有效

最好不要使用重名的方法,python中没有重载

# python中没有方法的重载 定义多个同名的方法 只有最后一个有效

class Person:
    def say_s(self):
        print("hello")
        
    def say_s(self,name):
        print("{0},hello".format(name))
p1 = Person()
# p1.say_s()  #不带参报错:TypeError: say_s() missing 1 required positional argument: 'name'
p1.say_s("shy-2")

执行结果:
shy-2,hello

方法的动态性

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

# 测试方法的动态性
class Person:
    def work(self):
        print("努力上班")

def play_game(self):
    print("{0}玩游戏".format(self))
    
def work2(s):
    print("好好工作,努力上班")
    
Person.play = play_game
Person.work = work2
p = Person()
p.play()
p.work()

执行结果:
<__main__.Person object at 0x0000011CB5A802B0>玩游戏
好好工作,努力上班

可以看到Person动态的新增了play_game方法以及 work2替换了原有的work方法

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

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

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

注意:
方法本质上也是属性!只不过是可以通过()执行而已。所以,此处讲的私有属性和公 有属性,也同时讲解了私有方法和公有方法的用法。

【操作】:私有属性和公有属性的使用:

# 测试私有属性 私有方法
class Employee:
    __company = "dddd"   #私有类属性 通过dir可以查到_Employee__company

    def __init__(self,name,age):
        self.name = name
        self.__age = age    # 私 有 实 例 属 性
        
    def say_company(self):
        print("我的公司是:",Employee.__company)  #类内部可以直接访问私有属性
        print(self.name,"年龄是:",self.__age)
        self.__work()
        
    def __work(self):
        # 私有实例方法 通过dir可以查到_Employee__cork
        print("工作赚钱")

p1 = Employee("shy-2",22)
print(p1.name)
print(dir(p1))
p1.say_company()
print(p1._Employee__age)  # 通 过 这 种 方 式 可 以 直 接 访 问 到 私 有 属 性 。 通 过 d ir 可 以 查 到 属 性 : _ E m p l o y e e _ _ a g e

# print(p1.__age) # 直 接 访 问 私 有 属 性 , 报 错 
# p1.__sleep() # 直 接 访 问 私 有 方 法 , 报 错
p1._Employee__work()

执行结果:
shy-2
['_Employee__age', '_Employee__company', '_Employee__work', '__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__', 'name', 'say_company']
我的公司是: dddd
shy-2 年龄是: 22
工作赚钱
22
工作赚钱

@property装饰器

@property 可以将一个方法的调用方式变成“属性调用”。

# 测试@property
class Employee:
    @property
    def salary(self):
        return 3000

emp1 = Employee()
print(emp1.salary)   #打印:3000
print(type(emp1.salary))  #打印:
# emp1.salary() #报错: 'int' object is not callable

emp1.salary = 2000          #此处修改报错:AttributeError: can't set attribute
# @ p r o p e r t y 修 饰 的 属 性 , 如 果 没 有 加 s e t t e r 方 法 , 则 为 只 读 属 性 

@property 主要用于帮助我们处理属性的读操作、写操作。对于某一个属性,我们可以直接通过:
emp1.salary = 30000

如上的操作读操作、写操作。但是,这种做法不安全。比如,我需要限制薪水必须为1-10000 的数字。这时候,我们就需要通过getter、setter方法来处理。

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

emp1 = Employee("shy-2",2000)
print(emp1.salary)
emp1.salary = -2000

执行结果:
月薪为2000,年薪为24000
2000
薪水录入错误!只能在0~1000000之间

属性和方法命名总结

  • _xxx:保护成员,不能用“frommodule import * ”导入,只有类对象和子类对象能访 问这些成员
  • __xxx __:系统定义的特殊成员
  • __xxx: 类中的私有成员,只有类对象自己能访问,子类对象也不能访问。(但在类外 部可以通过“对象名._类名__xxx”这种特殊方式访问。 Python 不存在严格意义的私有成员)

类编码风格

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

面向对象三大特征介绍

Python是面向对象的语言,也支持面向对象编程的三大特性:继承、封装(隐藏)、多态。

继承:
继承可以让子类具有父类的特性,提高了代码的重用性。

从设计上是一种增量进化,原有父类设计不变的情况下,可以增加新的功能,或者改进 已有的算法。

封装:

隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将“细节封装起来”,只对外暴露“相关调用方法”。

通过前面学习的“私有属性、私有方法”的方式,实现“封装”。Python 追求简洁的 语法,没有严格的语法级别的“访问控制符”,更多的是依靠程序员自觉实现

多态:

多态是指同一个方法调用由于对象不同会产生不同的行为。生活中这样的例子比比皆 是:同样是休息方法,人不同休息方法不同。张三休息是睡觉,李四休息是玩游戏,程序员休息是敲代码。

继承

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

语法格式

Python支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:

class 子类类名(父类1[,父类2,……]):
	类体

如果在类定义中没有指定父类,则默认父类是object 类。也就是说,object 是所有类的父 类,里面定义了一些所有类共有的默认实现,比如:__new __()。

定义子类时,必须在其构造函数中调用父类的构造函数。调用格式如下:

父类名.__init__(self,参数列表)
class Person:
    def __init__(self,name,age):
        self.name = name
        self.__age = age
    
    def say_age(self):
        print(self.name,"的年龄是:",self.__age)
    
class Student(Person):
    def __init__(self,name,age,score):
        self.score = score
        Person.__init__(self,name,age)
        
s1 = Student("shy-2",22,100)
s1.say_age()
print(dir(s1))

执行结果:
shy-2 的年龄是: 22
['_Person__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__', 'name', 'say_age', 'score']

类成员的继承和重写

  1. 成员继承:子类继承了父类除构造方法之外的所有成员。

  2. 方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称为“重写

【操作】继承和重写:

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    
    def say_age(self):
        print(self.name,"的年龄是:",self.age)
        
    def say_name(self):
        print("我是",self.name)
        
class Student(Person):
    def __init__(self,name,age,score):
        self.score = score
        Person.__init__(self,name,age)  #构造函数中包含父类构造函数
        
    def say_score(self):
        print(self.name,"的分数是:",self.score)
        
    def say_name(self):   #重写父类的方法
        print("报告老师,我是",self.name)

s1 = Student("shy-2",22,100)
s1.say_score()
s1.say_name()
s1.say_age()

执行结果:
shy-2 的分数是: 100
报告老师,我是 shy-2
shy-2 的年龄是: 22

查看类的继承和重写

通过类的方法mro或者类的属性__mro__可以输出这个类的继承层次结构
【操作】查看类的继承层次结构:

class A:pass
class B(A):pass
class C(A):pass

print(C.mro())
# print(C.__mro__)

执行结果:
[<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

python从零到一之基础篇 终章 面向对象基础_第4张图片

object根类

object 类是所有类的父类,因此所有的类都有object 类的属性和方法。我们显然有必要深 入研究一下object 类的结构。对于我们继续深入学习Python 很有好处。

dir()查看对象属性:

为了深入学习对象,我们先学习内置函数 dir(),他可以让我们方便的看到指定对象所有的 属性。

【操作】查看对象所有的属性以及和object进行比较:

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))
print('*'*20)
s1 = Person("shy-2",22)
print(dir(s1))

执行结果:
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
********************
['__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__', 'age', 'name', 'say_age']

从执行结果中可以看出:

  1. Person对象增加了六个属性: __dict __ __module __ __weakref __ age name say_age
  2. object的所有属性,Person 类作为object 的子类,显然包含了所有的属性。
  3. 我们打印age、name、say_age,发现say_age 虽然是方法,实际上也是属性。只不过, 这个属性的类型是“method”而已。

重写__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("shy-2",18)
print(p)

执行结果:
名字是:shy-2,年龄是18

多重继承

Python支持多重继承,一个子类可以有多个“直接父类”。这样,就具备了“多个父 类”的特点。但是由于,这样会被“类的整体层次”搞的异常复杂,尽量避免使用。

python从零到一之基础篇 终章 面向对象基础_第5张图片
运算结果:
cc
bb
aa

MRO()

Python支持多继承,如果父类中有相同名字的方法,在子类没有指定父类名时,解释器将 “从左向右”按顺序搜索。

MRO(MethodResolution 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

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 0x0000011CB5ADBC88>
# say AAA
# say BBB

多态

多态(polymorphism)是指同一个方法调用由于对象不同可能会产生不同的行为。在现实 生活中,我们有很多例子。比如:。同样是吃饭的方法,中国人用筷子吃饭,英国人用刀叉吃 饭,印度人用手吃饭。

关于多态要注意两点:

  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())

执行结果:
小狗 汪汪汪
小猫 喵喵喵

特殊方法和运算符重载

Python的运算符实际上是通过调用对象的特殊方法实现的。比如:

a = 20
b = 30
c = a + b
d = a.__add__(b)
print("c=",c)
print("d=",d)

执行结果:
c= 50
d= 50

常见的特殊方法统计如下:
python从零到一之基础篇 终章 面向对象基础_第6张图片

每个运算符实际上都对应了相应的方法,统计如下:

python从零到一之基础篇 终章 面向对象基础_第7张图片
python从零到一之基础篇 终章 面向对象基础_第8张图片

我们可以重写上面的 特殊方法实现运算符的重载:

class Person:
    def __init__(self,name):
        self.name = name
    
    def __add__(self,other):
        if isinstance(other,Person):
            return "{0}--{1}".format(self.name,other.name)
        else:
            return "不是同类对象 不能相加"

    def __mul__(self,other):
        if isinstance(other,int):
            return self.name*other
        
p1 = Person("shy-1")
p2 = Person("shy-2")
x = p1 + p2
print(x)

print(p1*3)

执行结果:
shy-1--shy-2
shy-1shy-1shy-1

特殊属性

Python对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊用法。这 里我们列出常见的特殊属性:
python从零到一之基础篇 终章 面向对象基础_第9张图片

class A:
    pass

class B:
    pass

class C(B,A):
    def __init__(self,nn):
        self.nn = nn
    def cc(self):
        print("cc")
        
c = C(3)

print(dir(c))
print(c.__dict__)
print(c.__class__)
print(C.__bases__)
print(C.mro())
print(A.__subclasses__)

# 执行结果:
['__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__', 'cc', 'nn']
{'nn': 3}
<class '__main__.C'>
(<class '__main__.B'>, <class '__main__.A'>)
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
<built-in method __subclasses__ of type object at 0x0000011CB5410668>

对象的浅拷贝和深拷贝

  • 变量的赋值操作
    只是形成两个变量,实际还是指向同一个对象。
  • 浅拷贝
    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 0x0000011CB5ACBA90>
<__main__.MobilePhone object at 0x0000011CB5ABE2E8> <__main__.MobilePhone object at 0x0000011CB5ABE2E8>
<__main__.MobilePhone object at 0x0000011CB5ABE2E8> <__main__.MobilePhone object at 0x0000011CB5ACBCC0>
计算,算个12345
CPU对象 <__main__.CPU object at 0x0000011CB5ACBA90>
计算,算个12345
CPU对象 <__main__.CPU object at 0x0000011CB5ACBA90>
计算,算个12345
CPU对象 <__main__.CPU object at 0x0000011CB5AA2BE0>

组合

“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("计算")
        
class Screen:
    def show(self):
        print("显示")
    
c = CPU()
s = Screen()

m = MobilePhone(c,s)
m.cpu.calculate()    #通过组合我们也可以调用CPU对象的方法 相当于手机对象间接的拥有了CPU的方法
# m.Screen.show()

计算

设计模式_工厂模式

设计模式是面向对象语言特有的内容,是我们在面临某一类问题时候固定的做法

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

# 工厂模式
class CarFactory:
    def createCar(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 = CarFactory()
c1 = factory.createCar("奔驰")
c2 = factory.createCar("宝马")
print(c1)
print(c2)

执行结果:
<__main__.Benz object at 0x0000011CB5A6F438>
<__main__.BMW object at 0x0000011CB5A6F358>

设计模式_单例模式

单例模式(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 0x0000011CB5B252B0>
<__main__.MySingleton object at 0x0000011CB5B252B0>

设计模式称之为“模式”,就是一些固定的套路。我们很容易用到其他场景上,比如前面讲 的工厂模式,我们需要将工厂类定义成“单例”,只需要简单的套用即可实现:

class CarFactory:
    __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):
        if CarFactory.__init_flag:
            print("init....")
            CarFactory.__init_flag = False
            
    def createCar(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 = CarFactory()
c1 = factory.createCar("奔驰")
c2 = factory.createCar("宝马")
print(c1)
print(c2)

factory2 = CarFactory()
print(factory)
print(factory2)

执行结果:
init....
<__main__.Benz object at 0x0000011CB5B76208>
<__main__.BMW object at 0x0000011CB5B76278>
<__main__.CarFactory object at 0x0000011CB5B76240>
<__main__.CarFactory object at 0x0000011CB5B76240>

你可能感兴趣的:(python从零到一)