- 类和对象的概念
- 类的声明和实例化
- 初始化函数 __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
由此可见,深拷贝将源对象的子对象复制了副本,并且子对象的子对象也复制了副本。即深拷贝是遍历了所有子对象,复制了副本给拷贝对象。



