栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

自学Python第八天- 面向对象

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

自学Python第八天- 面向对象

自学Python第八天- 面向对象
  • 类和对象的概念
  • 类的声明和实例化
    • 初始化函数 __init__()
  • 单例
  • 类的属性和方法
    • 访问对象的属性
    • 内置的类属性
    • 类的私有属性和私有方法
  • 类的继承
    • 继承
    • 多重继承
    • 重写
    • 重载
  • 多态
  • 类的特殊方法
  • 类的浅拷贝与深拷贝
    • 浅拷贝
    • 深拷贝

现在几乎所有编程语言都是面向对象的编程,python 也不例外。在 python 中,万物皆对象,除了变量是指针函数是过程(其实从广义上来说,函数也是个对象,是个只调用其过程的对象)外,其他的包括数值、字符等等等等均为对象。

类和对象的概念

面向对象编程有2个非常重要的概念:类和对象。

  • 类是用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。
  • 对象是某个具体的实物,是类的实例,拥有自己的特征和行为。

类是对象的类型,对象是类的实例。在概念上 python 和其他语言基本相同。区别只在具体的定义和使用上。

类的声明和实例化

python 使用 class 语句声明(创建)一个新类,格式为 class 类的名称(): ,之后的代码块为类的内容(也叫成员)。

类的成员包括数据成员(也叫属性)和函数成员(也叫方法)。数据成员是一个对象,函数成员是一段可执行代码。python 中类可以使用将类赋值给一个变量的形式实例化类成为对象。在类实例化后,可以使用 类名.成员名 的方式进行调用。

class MyClass():		# 类的声明
	name = 'JavaScript'	# 类的数据成员
	def funcA(self):		# 定义方法, self 是必须的且是类内函数的第一个参数,表示实例化后的对象自身
		print(f'Class {self.name} 正在发展中')	# 使用 self.name 调用对象的 name 成员

x = MyClass()			# 类实例化成对象
x.funcA()				# 调用对象的方法
# 执行结果:
# Class JavaScript 正在发展中
x.name = 'Python'		# 给对象的数据成员赋值
print(x.name)			# 调用对象的成员,查看赋值情况
# 执行结果:
# Python
x.funcA()				# 对象方法被调用时,使用的是更改后的数据成员
# 执行结果:
# Class Python 正在发展中
初始化函数 __init__()

python 中使用 __init__() 来进行初始化,它是一个特殊的属于类的专有的方法,也被称为类的构造函数。在每次类被实例化的时候会被调用,作用就是初始化已被实例化的对象。如果 __init__() 拥有参数,则必须在类实例化时传入。

class Person():		# 定义类
	def __init__(self,name,sex):		# 实例化时传入的实参依次赋值给 self 之后的形参,self 不能被赋值和更改
		self.name = name				# 给类的数据成员赋值
		self.sex = sex
		print(self.__class__)			
	def showInfo(self,country):	
		print(f'我叫{self.name},我是{self.sex}生,来自{country}。')	# 变量 country 是形参,不是数据成员,所以不用加 self

xiaoming = Person('小明','男')		# 类实例化,将实参传给构造函数
xiaoming.showInfo('中国')					# 调用对象的方法,看一下初始化时的赋值情况
# 执行结果:
# 我叫小明,我是男生,来自中国。
单例

单例是在系统中只有唯一的一个实例的类。单例是不可变数据类型,但是单例的成员可以是可变数据类型。

创建单例时,可以在类中设置一个状态属性。每次创建单例的实例时,查看状态变量的值。根据值决定是创建实例还是返回已经创建的实例对象。例:

class MusicPlayer():
	# 状态属性,记录第一个被创建对象的引用
	instance = None
	def __new__(cls, *args, **kwargs):
		# 判断类属性状态是否是空对象
		if cls.instance is None:
			# 调用父类方法,为第一个对象分配内存空间
		 	cls.instance = super().__new__(cls)
		# 返回类属性保存的对象的引用
		return cls.instance

单例只在创建对象是使用同一个实例,但是每次创建时初始化行为都会被执行。如果需要将初始化行为也只执行一次,则类似于单例再创建一个状态属性。

class MusicPlayer():
	# 状态属性,记录第一个被创建对象的引用
	instance = None
	# 状态属性,记录是否初始化
	init_flag = False
	def __new__(cls, *args, **kwargs):
		# 判断类属性状态是否是空对象
		if cls.instance is None:
			# 调用父类方法,为第一个对象分配内存空间
		 	cls.instance = super().__new__(cls)
		# 返回类属性保存的对象的引用
		return cls.instance

	def __init__(self):
		# 判断初始化状态属性
		if MusicPlayer.init_flag:
			return
		else:
			.
			.
			.
			MusicPlayer.init_flag = True			
类的属性和方法 访问对象的属性

一般操作对象时,使用的是 对象名.成员 来访问对象的属性,也可以使用一些函数来访问对象的属性:

  • getattr(obj,name[,default]) :访问对象的属性,如果没有该属性则返回 default 值,如果没有 default 则报错
  • hasattr(obj.name) :检查对象是否存在一个属性
  • setattr(obj.name,value) :设置一个属性。如果属性不存在,则会创建一个新属性
  • delattr(obj.name) :删除属性

name 表示属性名,使用时需要加引号(作为字符串使用)。obj 为对象名。

class Person():		# 定义类
	def __init__(self,name,sex):		
		self.name = name				
		self.sex = sex
	def showInfo(self,country):	
		print(f'我叫{self.name},我是{self.sex}生,来自{country}。')	

xiaoming = Person('小明', '男')
xiaohong = Person('小红', '女')

print(xiaoming.name)				# 执行结果:	小明
print(getattr(xiaohong, 'name'))		# 执行结果: 小红
print(hasattr(xiaoming, 'age'))		# 执行结果: False	
setattr(xiaoming, 'age', 18)		
print(hasattr(xiaoming, 'age'))		# 执行结果:True
delattr(xiaoming, 'age')
print(getattr(xiaoming, 'age', '没有 age 这个属性'))		# 执行结果:没有 age 这个属性
内置的类属性

当类被定义后,就有了默认的内置的属性:

  • __dict__ :对象的属性(包含一个字典,由对象的数据成员组成)
  • __doc__ :类的文档字符串
  • __name__ :类名
  • __module__ :类定义所在的模块
  • __bases__ :类的所有父类构成元素(包含了一个由所有父类组成的元组)
  • __class__ :类的类型
类的私有属性和私有方法
  • 私有属性:在类中定义的属性,如果以两个下划线开头,则声明该属性为私有。不能在类的外部被使用或直接访问,只能在类的内部进行访问。
  • 私有方法:定义类的方法时,以两个下划线开头,则声明该方法为私有方法。不能在类外部调用,只能在类的内部调用。
class Student:  # 定义类
    def __init__(self, name, age, *score):		# 初始化
        self.__name = name		# 定义私有属性 __name
        self.__age = int(age)	# 定义私有属性 __age
        if len(score) == 1:		# 定义私有属性 __score
            self.__score = list(score[0])
        else:
            self.__score = list(score)

    def get_name(self):
        return self.__name		

    def get_age(self):
        return self.__age

    def __max(self):		# 私有方法
        return max(self.__score)

    def get_course(self):
        return self.__max()


zhangsan = Student('张三', 22, [70, 80, 90])

print(zhangsan.get_name(), zhangsan.get_age(), zhangsan.get_course(), sep='   ')
# 执行结果:
# 张三   22   90
类的继承

类的继承 python 在概念上和其他程序语言类似,这里只研究具体的方法和实例。

继承

在继承中,类分为父类(基类)和子类(派生类)。在 python 中,父类的定义和普通的类定义方式一样,子类的定义有所不同:

  • 在类的首行声明中,将父类名加入到括号中,表示从哪个父类进行继承
  • 在子类中定义成员(不论属性和方法)如果名称在父类中使用过,则为重写
  • 在子类中调用父类的成员,使用 super() 表示父类
  • 在类的首行声明中,括号内没有声明任何父类,则默认继承 object 类。即所有类都是 object 类的子类。
class Animal():  # 创建 Animal 类,作为基类
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self, food):
        print(f'我是{self.name},爱吃{food}')

class Dog(Animal):  # 创建 Dog 类,作为派生类,从 Animal 类继承
    def __init__(self, name, age):  # 初始化方法
        super().__init__(name, age)  # 调用父类的初始化方法,并传参 name,age

class Cat(Animal):  # 创建 Cat 类,作为派生类,从 Animal 类继承
    def __init__(self, name, age):  # 初始化方法
        super().__init__(name, age)  # 调用父类的初始化方法,并传参 name,age
多重继承

如果在派生类的声明中,声明继承自父类的元组中列了一个以上的类,则称为多重继承。

需注意的是,如果调用的成员没有在子类中重写,则会按照先后顺序从声明继承的父类中查找并调用该成员。

class A():
    aaa = 'aAa'
    def __init__(self):
        print('继承自 A 类')

class B():
    bbb = 'bBb'
    aaa = '1a1'
    def __init__(self):
        print('继承自 B 类')

class C(A, B):
    def p(self):
        print(f'继承自父类的属性aaa={self.aaa},父类B的属性aaa={B.aaa}')

class D(B, A):
    aaa = '111'
    def p(self):
        print(f'D的aaa={self.aaa},父类的aaa={super().aaa}')

c = C()         # C类没有初始化函数,继承父类初始化函数,根据顺序继承自A类
d = D()         # D类没有初始化函数,继承父类初始化函数,根据顺序继承自B类
print(c.aaa)    # c对象没有属性aaa,继承父类的属性aaa,根据顺序继承自A类
print(d.aaa)    # d对象有重写的属性aaa
d.p()           # d对象重写的属性aaa,也可以在函数内部调用父类属性aaa
c.p()           # c对象继承父类的属性aaa,也可在函数内部指定调用其他父类的属性aaa

# 执行结果
# 继承自 A 类
# 继承自 B 类
# aAa
# 111
# 继承的属性aaa=111
# D的aaa=111,父类的aaa=1a1
# 继承自父类的属性aaa=aAa,父类B的属性aaa=1a1
class A():  # 父类 A
    def __init__(self, name):
        self.name = name

class B():  # 父类 B
    def __init__(self, age):
        self.age = age

class C(A, B):  # 子类 C ,继承自 A和B
    def __init__(self, name, age):  # 重写 __init__ 方法
        A.__init__(self, name)  # 调用父类 A 的 __init__ 方法
        B.__init__(self, age)  # 调用父类 B 的 __init__ 方法

xiaoc = C('jack', 30)
print(xiaoc.name)
print(xiaoc.age)
重写

当父类的方法不适合子类使用时,子类可以重新定义同名函数,这种方式叫做重写。重写函数的调用方式不变,只是调用的是子类的方法而不是父类的。

重载

方法的重载一般用于同一个函数但是参数不同的时候,让函数根据参数的类型、数量自动选择调用的函数。而在 python 中,同名的函数后定义的会覆盖掉先定义的,所以不能使用这种方式进行重载。

其实需要重载的场景基本上有2个:

  • 参数的类型不同
  • 参数的数量不同

python 是一门动态语言,不需要声明变量类型,函数中可以接受任何类型的参数也就无法根据参数类型来支持重载。python没有必要去考虑参数的类型问题,这些都可以在函数内部判断处理,并无必要去再写一个函数。

python 有多种传参方式,当参数数量不固定可以使用可变长参数,所以也没有必要重写函数。

总之,python 没有重载,重载的功能由其他方式就能够实现了。

多态

简单的说,多态就是具有多种形态,它指的是:即便不知道一个变量所引用的对象到底是什么类型,仍然可以通过这个变量调用方法。在运行过程中可以根据变量所引用的对象的类型,动态决定调用哪个对象中的方法。

# 定义多个类
class Animal():
	def eat(self):
		print('动物吃东西')
class Dog(Animal):
	def eat(self):
		print('狗吃肉')
class Cat(Animal):
	def eat(self):
		print('猫吃鱼')
class Person():
	def eat(self):
		print('人吃五谷杂粮')
# 定义多态的方法函数
def func(obj):		# 定义函数func,参数为obj
	obj.eat()		# 调用obj类的eat方法

# 使用多态函数
func(Cat())			# 将具体需要使用的类作为参数传入
func(Dog())			# 就使用参数类的 eat 方法
func(Animal())
类的特殊方法

python 中万物皆对象,数值也不例外。那么我们做一个加法是怎么做的呢?其实是使用了对象 a 加上对象 b 的方法,即:

a, b = 10, 20
c = a + b		# 正常加法,其实是两个对象相加的省略写法
d = a.__add__(b)	# 即调用对象 a 的 __add__ 方法
print(c, d)		# 运行结果: 30 30

那么是否能在自定义的类中使用类似的方法呢?

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

    def __add__(self, other):	# 加法函数
        return self.name + other.name

    def __len__(self):     	    # len() 函数,原来只能对可迭代对象使用
        return len(self.name)
        
    def __str__(self):			# 不使用此方法,则输出类的结果是个对象,现在能以字符串形式直接输出
    	return str(self.name)
    	
stu1 = Student('张三')
stu2 = Student('李四')
s = stu1 + stu2				# 使用类里的加法函数,进行两个对象相加
print(f'{s},{type(s)}') 	# 执行结果: 张三李四,
print(len(stu1))			# 执行结果: 2
print(stu1, s)					# 执行结果: 张三 张三李四

另 __new__() 方法和 __init__() 方法都是在创建对象时执行的,有什么区别呢?

class Person():			
    def __new__(cls, *args, **kwargs):		# 定义 __new__() 方法
        print(f'__new__被执行了,cls的id值为:{id(cls)}')
        obj = super().__new__(cls)			# 创建实例,是基于父类的 __new__ 方法
        print(f'创建的对象id为:{id(obj)}')
        return obj		# 返回创建的实例对象

    def __init__(self, name, age):			
        print(f'__init__被执行了,self的id值为:{id(self)}')
        self.name = name
        self.age = age

print(f'object类对象的id为:{id(object)}')
print(f'Person类对象的id为:{id(Person)}')
p1 = Person('张三', 20)
print(f'p1实例对象的id为:{id(p1)}')
# 执行结果
# object类对象的id为:140703632627584
# Person类对象的id为:1733508260672
# __new__被执行了,cls的id值为:1733508260672
# 创建的对象id为:1733541903280
# __init__被调用了,self的id值为:1733541903280
# p1实例对象的id为:1733541903280

由上个实例可以发现,__new__() 方法用于从类创建实例为实例对象分配内存空间,并返回对象。在方法结束前还没有真的实例,所以没有 self 指向的对象,只有 cls 指向的类定义(类对象)。而实例化的过程可以将类对象传给父类的 __new__() 从而实例化。

类的浅拷贝与深拷贝

变量赋值后,就创建了一个变量。但是这个变量其实只是一个指针,指向一个具体的对象。如果是将不同的变量赋同样的值(不可变类型数据),其实是指向了同一个对象。

a = 10
b = 10				# 不同的对象赋值为同样的不可变类型
print(a is b)		# 执行结果: True
c = [1]
d = [1]				# 不同的对象赋值为同样的可变类型
print(c, d, c is d) # 执行结果: [1] [1] True False

如果使用变量的复制也只是将两个变量的指针指向同一个对象。

a = [1]
b = a							# 变量的复制
print(a, b, a == b, a is b)		# 执行结果: [1] [1] True True

而类(或可变类型数据)的复制则不同,是将对象实例进行复制。需注意的是,类对象、函数对象视为不可变类型。类的复制分为浅拷贝与深拷贝。

浅拷贝

python 的复制一般都是浅拷贝,拷贝时,对象包含的子对象内容不拷贝,因此,源对象与拷贝对象会引用同一个子对象。由于 python 的万物皆对象,所以浅拷贝其实就是将源对象的所有成员的变量(带名称的指针)复制一次并赋给了拷贝对象。

from copy import copy

class CPU():
    pass

class Computer():
    def __init__(self, cpu):
        self.cpu = cpu

cpu1 = CPU()
com1 = Computer(cpu1)
com2 = copy(com1)			# 浅拷贝

print(id(com1), id(com2), id(com1.cpu), id(com2.cpu), id(cpu1))
# 执行结果:
# 1876811397776 1876811397008 1876811398064 1876811398064 1876811398064

由此可见,对象 com2 复制了 com1 的实例,所以 com2 和 com1 是不同的对象。但是双发的子对象还是同一个对象。

深拷贝

python 中可以使用 copy 的 deepcopy 函数,递归拷贝对象中包含的子对象,形成深拷贝。即所有的数据(可变数据)和对象都做一个副本并赋给拷贝对象。所以源对象和拷贝对象所有的子对象是不相同的。

from copy import deepcopy

class CPU():
    def __init__(self, cache):
        self.cache = cache

class Cache():
    pass

class Computer():
    def __init__(self, cpu):
        self.cpu = cpu

cache1 = Cache()
cpu1 = CPU(cache1)
com1 = Computer(cpu1)
com2 = deepcopy(com1)		# 深拷贝

print(id(com1), id(com2), id(com1.cpu), id(com2.cpu))
print(id(com1.cpu.cache), id(com2.cpu.cache))
# 执行结果:
# 1907925465216 1907925665632 1907925465744 1907925665344
# 1907925466032 1907925665152

由此可见,深拷贝将源对象的子对象复制了副本,并且子对象的子对象也复制了副本。即深拷贝是遍历了所有子对象,复制了副本给拷贝对象。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/958713.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号