类
类就是一种封装的思想
类的定义:类通常使用class关键字来定义,类名通常使用首字母大写的驼峰命名法(MyClass)(MyClass)。类的定义一般包括属性和方法。
class MyClass:
"""
关于类的说明
"""
# 类的属性
my_attribute = 0
# 类的方法
def my_method(self):
print("Hello World")
实例
class MyClass:
def my_method(self):
print("Hello World")
mc1 = MyClass()
mc2 = MyClass()
mc3 = MyClass()
类名()返回一个实例,此时mc1,mc2,mc3我们就可以成为 MyClass类的实例
封装
简单封装
将数据以及对应的操作封装成类
class Person:
def __init__(self, _name, _age): #这是一个魔法函数中的初始化函数,我们后文会讲,我们像函数里传入两个形参
print('初始化函数执行了') #为了区分两个name,我们为传入的实例属性前加下划线
self.name = _name
self.age = _age # 这里把实例属性传入函数
def get_name(self): #封装一个获取名字的方法
return self.name
def set_name(self, _name): #封装一个更改名字的方法
self.name = _name
# 创建实例
p1 = Person('张三',12)
#实例中的 get_name方法获取名字
print(p1.name) #输出 张三
print(p1.get_name()) #输出 张三
#实例中的 set_name方法更改名字
p1.set_name('李四')
print(p1.name) #输出 李四
类属性封装
通过下文讲解类的数据访问级别,我们知道可以通过在类中封装函数通过调用类中函数来实现私有数据的访问,除了这种方法我们还有以下方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def get_name(self):
print('执行获取')
return self.name
def set_name(self, name):
print('执行设置')
if 2 <= len(name) <= 4:
self.name = name
else:
print(f"名字不合法")
def get_age(self):
return self.age
def set_age(self, age):
if 0 < age < 120:
self.age = age
else:
print('输入不合法')
p_name = property(fset=set_name, fget=get_name) #通过类的封装,此时才算真正意义上的类
#此时p_name具有get_name 和 set_name两种方法
p_age = property(fset=set_age, fget=get_age)
p = Person('张三', 12)
print(p.p_name)
p.p_name = '' #我们为设置数据添加了限制,只能2-4个字符,因此空字符不合法
print(p.p_name)
如果代码量大我们会发现通过 p_name = property(fset=set_name, fget=get_name) 这种方法来封装还是比较麻烦,下面是深一步优化代码(通过装饰器来实现)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def p_name(self):
return self.name
@p_name.setter
def p_name(self, name):
if 2 <= len(name) <= 4:
self.name = name
else:
print("长度不合法")
@property
def p_age(self):
return self.age
@p_age.setter
def p_age(self, age):
if 2 <= age <= 100:
self.age = age
else:
print('输入不合法')
p = Person('张三', 20)
p.p_name = '李四'
p.p_age = 12
print(p.p_name, p.p_age) #输出 李四 12
继承
-
单继承
父类:定义类时出现在类名后的括号中
默认所有类都继承object 可以省略
class Preson(object): #这里object一般省略
"""
当类Person没有编写__str__时会直接使用父类(又叫基类、超类)的__str__
如果自己定义了__str__ 则使用自己的__str__
"""
#__str__ 是一个魔法函数中的转字符串函数 后文我们会讲解
pass
p1 = Person()
print(p1) #因为当前类中没有__str__ 所以去object中找__str__ 打印object中的__str__
子类:
当调用实例的方法时,先在本身找对应的方法实现, 如果找不到则取父类中找对应的实现
class Person:
def __init__(self, name):
self.name = name
def __str__(self):
return f"姓名:{self.name}"
def walk(self):
print('可以走路')
p1 = Person('小王')
print(p1)
class SuperPerson(Person): #子类继承父类Person
def __init__(self, name, skill):
super().__init__(name)
父类的出现解释降低代码的重复性,SuperPerson的父类Person已经初始化name了
我们通过这个方法直接调用父类中的name
#添加人物技能skill
self.skill = skill
def __str__(self):
"""
父类已经实现
:return:
"""
return f"{super().__str__()}\t技能:{self.skill}"
def fly(self):
return "可以飞行"
sp1 = SuperPerson("绿巨人", "变身") #调用父类中的初始化name,子类中添加了初始化技能skill
print(sp1)
sp1.walk() #调用父类中的walk方法
sp1.fly() #调用子类中的fly方法
通过以上代码介绍,不难发现继承的应用就是减少代码的复用,并且继承父类后可以调用父类的方法并同时可以使用自己类的方法
-
多继承
多继承是面向对象编程中一个强大的特性,它允许一个类从多个父类继承属性和方法。这在某些情况下非常有用,但同时也增加了代码的复杂性和维护难度
多继承时按照广度优先来继承的,就是依次按照继承顺序往下数 可以通过 可以使用类的 __mro__ 属性或 mro() 方法来查看 MRO 即:继承顺序,会按照这个顺序依次找寻父类中的子类调用的方法
下面我们举例说明上图
class A00:
pass
class A000:
pass
class A0(A00, A000):
pass
class A11:
pass
class A1(A11): #继承A11
def __str__(self):
return '我在这里我是A1'
class B0:
pass
class A(A0, A1): 继承A0 A1
pass
class B(B0): 继承B0
pass
class D:
pass
class C(A, B, D): 继承ABCD
# def __str__(self):
# return '我在这里'
pass
method = C()
print(method) #这里会返回我们定义的 __str__字符串 '我在这里我是A1'
#继承顺序
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.A0'>, <class '__main__.A00'>, <class '__main__.A000'>, <class '__main__.A1'>, <class '__main__.A11'>, <class '__main__.B'>, <class '__main__.B0'>, <class '__main__.D'>, <class 'object'>
如果我们这里放开C中的__str__,同时存在两个__str__会发生什么呢?
class A00:
pass
class A000:
pass
class A0(A00, A000):
pass
class A11:
pass
class A1(A11):
def __str__(self):
return '我在这里我是A1'
class B0:
pass
class A(A0, A1):
pass
class B(B0):
pass
class D:
pass
class C(A, B, D):
def __str__(self):
return '我在这里我是C'
method = C()
print(method) #输出 '我在这里我是C'
这里会根据广度优先算法 优先输出前面C的__str__ 后面A1中的__str__则不会被输出
多态
在 Python 中,多态通过“鸭子类型”来实现,即只要对象拥有特定的方法或属性,就可以被当做特定类型的对象使用,无需显式地继承某个类或实现某个接口。
下面是一个简单的示例来说明多态的概念:
class Dog:
def sound(self):
print("Dog barks")
class Cat:
def sound(self):
print("Cat meows")
def make_sound(animal):
animal.sound()
# 创建 Dog 和 Cat 的实例
dog = Dog()
cat = Cat()
# 调用 make_sound 函数,传入不同的动物对象
make_sound(dog) # 输出:Dog barks
make_sound(cat) # 输出:Cat meows
在上面的示例中,Dog
和 Cat
类都有一个 sound
方法,当我们调用 make_sound
函数时,不同的动物对象会作出不同的响应,这就展示了多态的概念。
魔法函数(基本的四个魔法函数详解)
Python 的魔法函数,又称为特殊方法,是 Python 类中定义的一组特殊的方法,具有一些独特的特性和用途。这些方法以双下划线(__)开头和结尾,它们在 Python 编程中扮演着特殊角色。魔法函数不是显式调用的,而是在特定的语境下由 Python 解释器隐式自动调用。它们的触发通常与 Python 中的某些内置行为或操作相对应
魔法函数的工作原理
自动触发:魔法函数的调用不是直接进行的。例如,当你对类的实例执行 a + b 操作时,Python 将自动调用该类的 __add__ 方法(如果定义了的话)。
模仿内置类型:魔法函数可以让自定义对象模仿内置类型的行为。例如,通过实现 __len__ 方法,可以使对象支持 len() 函数;通过实现 __getitem__ 方法,可以使对象支持索引操作,如 obj[key]。
增强灵活性:魔法函数提供了一种方式,可以在不改变语法结构的前提下,为自定义对象添加额外的功能。
魔法函数 new init del函数关系
我们都知道一个最基本的魔术方法,init。通过此方法可以定义一个对象的初始操作。但init并不是第一个被调用的方法。
实际上,还有new方法,来实例化这个对象。然后,在创建时给初始化函数传递参数。在对象生命周期的另一端,也有del方法。
接下来看一看这三个方法。
new(cls, […])是在一个对象实例化的时候所调用的第一个方法,所以它才是真正意义上的构造方法。它的第一个参数是这个类,其他的参数是用来直接传递给init方法。
new 决定是否要使用该init方法,因为new可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果new没有返回实例对象,则init不会被调用。代码如下:
class Person(object):
def __new__(cls, *args, **kwargs):
print("__new__()方法被调用了")
print('这个是*agrs', *args)
print('这个是kwagrs', **kwargs)
# cls表示这个类,剩余所有的参数传给__init__()方法,
# 若不返回,则__init__()不会被调用
#return object.__new__(cls)
def __init__(self, name, age):
print("__init__()方法被调用了")
self.name = name
self.age = age
print(self.name, self.age)
p = Person("张三", 20)
__new__()方法被调用了
这个是*agrs 张三 20
这个是kwagrs
可以发现 由于__new__ 没有返回值,__init__并没有被调用
构造函数 __new__
new()方法是一个特殊的魔法函数,在创建新对象是隐式调用,它返回一个新的对象,并在之后由__init__()方法初始化该对象。init()方法用于初始化对象的属性和状态。这两个魔法函数通常是在类的定义中同时使用的。
class Manage:
def __new__(cls, *args, **kwargs):
pass
初始化函数 __init__
在创建类之后,类通常会自动创建一个名为__init__()的方法。这个方法是Python中的一个特殊方法,类似于其他编程语言中的构造函数。每当创建一个类的新实例时,Python会自动调用它。
init()方法必须包含一个名为self的参数,这个参数是实例本身的引用,可以用来访问类中的属性和方法。在__init__()方法中,可以设置实例的初始状态,定义实例的行为和行为规则
class Manage:
def __init__(self):
print('初始化实例')
析构函数 __del__
- “__del__”是析构函数,当使用del 删除对象时,会调用他本身的析构函数,另外当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数也会被调用一次,这样可以用来释放内存空间。
- __del__()也是可选的,如果不提供,则Python 会在后台提供默认析构函数、
垃圾回收机制
当用del删除一个对象时,并没有直接清除该对象的内存空间。Python 采用‘引用计数’ 的算法方式来处理回收,即:当某个对象在其作用域内不再被其他对象引用的时候,Python 就自动清除对象。
而析构函数 __del__()在引用的时候会自动清除被删除对象的内存空间。
class Manage:
def __del__(self):
"""
析构函数:回收函数
当删除对象时,解释器会自动调用del方法
"""
print('释放')
转字符串函数 __str__
class Manage:
def __str__(self):
return "返回字符串"
m = Manage() #实例化对象
print(m) #输出 返回字符串"
class Manage1:
pass
m = Manage1()
print(m) #输出 <__main__.Manage1 object at 0x000001CB03CD1D90>
#由于所有子类继承object Manage1中没有__str__函数 则会自动打印object中的__str__
此方法定义了对象的字符串表示。当您打印该对象或在需要字符串表示的地方使用该对象时,例如print(Manage())
,它将输出字符串"这是一个示例对象"
实例和类的属性、方法
实例属性
实例属性是从属于实例对象的属性,也称为“实例变量”,要点如下:
(1)实例属性一般在init()方法中通过如下代码定义“self.实例属性名 = 初始值”。
(2)在本类的其他实例方法中,也是通过self进行访问“self.实例属性名”。
(3)实例属性可修改、新增、删除。
需要注意的是,如果在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用修改,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性。当类属性与实例属性同名时,一个实例访问这个属性时实例属性会覆盖类属性,但类访问时不会。
代码如下
class People(object):
country = 'China' #类属性
# 访问类属性
print(People.country)
# 实例化对象
p = People()
# 访问实例属性
print(p.country)
# 修改实例属性
p.country = 'Japan'
# 访问实例属性,实例属性会屏蔽掉同名的类属性
print(p.country)
# 访问类属性,会发现没有改变
print(People.country)
#通过类对象去引用修改类属性
People.country = "UK"
# 访问类属性
print(People.country)
China
China
Japan
China
UK
实例方法
之前的例子中,在类中以def关键字定义的都可以称之为实例方法,不仅如此,类的初始化方法init()理论上也属于实例方法,只不过它比较特殊。实例方法最大的特点就是,它最少也要包含一个self参数,用于绑定调用此方法的实例对象“Python 会自动完成绑定”。实例方法通常会用类对象直接调用
类属性
注意:类属性是声明在类的内部,实例方法的外部的属性,即在class内,__init__(self)方法之前
class People(object):
name = 'VenusAI' #公有的类属性
__age = 7 #私有的类属性
def __init__(self):
pass
p = People()
print(p.name) #正确
print(People.name) #正确
#print(p.__age) #错误,不能在类外通过实例对象访问私有的类属性
#print(People.__age) #错误,不能在类外通过类对象访问私有的类属性
print(p._People__age) #这种特殊的访问方法可以在类外访问私有的类属性
VenusAI
VenusAI
7
类方法
Python类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为cls,类方法是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数,可以通过实例对象和类对象去访问。类方法还有一个用途就是可以对类属性进行修改。代码如下:
class People(object):
country = 'China'
#类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
@classmethod
def setCountry(cls,country):
cls.country = country
p = People()
print(p.getCountry()) #可以通过实例对象引用
print(People.getCountry()) #可以通过类对象引用
p.setCountry('Japan')
print(p.getCountry())
print(People.getCountry())
'''
China
China
Japan
Japan
'''
静态方法
静态方法需要通过修饰器@staticmethod来进行修饰。静态方法是类中的函数。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。例如,笔者想定义一个关于时间操作的类,其中有一个获取当前时间的函数,代码如下:
import time
class TimeTest(object):
def __init__(self, hour, minute, second):
self.hour = hour
self.minute = minute
self.second = second
@staticmethod
def showTime():
return time.strftime("%H:%M:%S", time.localtime())
# 使用类对象调用静态方法
print(TimeTest.showTime())
# 实例化对象
t = TimeTest(2, 10, 10)
# 使用实例对象调用静态方法
print(t.showTime())
14:22:56
14:22:56
类的数据访问级别
普通成员
普通名字,所有位置都是公开的可以直接使用
class Person:
name = '张三'
__age = 14
_sex = '男'
def __init__(self,sex):
self.sex = sex
p = Person('男') # 实例化对象
print(p.sex) #通过实例化访问实例方法 可以访问
print(Person('男').sex) #通过调用类访问实例方法 可以访问
print(p.name) # 通过实例访问类属性 可以访问
print(Person.name) # 通过调用类访问类属性 可以访问
保护成员
类内 子类可以访问 ,为子类服务,不建议在类外访问
class Student(object):
def __init__(self, name, score):
self._name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
class Class(Student):
pass
s = Student('小王',12)
c = Class('小名',13)
print(s._name) #不建议这样访问
print(c._name) #不建议这样访问 通过子类访问
私有成员
__开头
私有
只能在类内访问
class Student(object):
def __init__(self, name, score):
self._name = name
self.__score = score
def get_score(self):
return self.__score
def print_score(self):
print('%s: %s' % (self.name, self.score))
class Class(Student):
pass
s = Student('小王',12)
c = Class('小名',13)
# print(c.__score) #纯私有, 外部无法访问
print(s.get_score()) #外部访问可以通过调用函数
单例类
单例模式在需要全局唯一实例的场景中非常有用。它不仅节省资源,还能确保全局状态的一致性和避免资源冲突。通过合理的设计和实现,单例模式能够为应用程序带来更高的效率和稳定性。
class Manage:
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls)
print('创造实例', id(instance))
#return instance #这里我们先注释掉返回函数 观察三个实例返回值
def __init__(self):
print('初始化实例', id(self))
m1 = Manage()
m2 = Manage()
m3 = Manage()
print(m1 is None, m2 is None, m3 is None)
print(id(m1), id(m2), id(m3))
创造实例 1870795055632
创造实例 1870795055632
创造实例 1870795055632
True True True
140709378172416 140709378172416 140709378172416
class Manage:
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls)
print('创造实例', id(instance))
return instance #放开
def __init__(self):
print('初始化实例', id(self))
m1 = Manage()
m2 = Manage()
m3 = Manage()
print(m1 is None, m2 is None, m3 is None)
print(id(m1), id(m2), id(m3))
创造实例 2059343962736
初始化实例 2059343962736
创造实例 2059343962784
初始化实例 2059343962784
创造实例 2059343962832
初始化实例 2059343962832
False False False
2059343962736 2059343962784 2059343962832
通过以上验证不难发现,我们一个类会创造多个实例,在某些特定的场景下,例如,在需要全局配置或管理状态的情况下,单例模式非常适合。
那么我们如何来实现呢?
class Manage:
instance = None
def __new__(cls, *args, **kwargs):
if not Manage.instance:
Manage.instance = super().__new__(cls)
return Manage.instance
class AduioManage(Manage):
pass
am1 = AduioManage()
am2 = AduioManage()
print(am1 is am2) #任何一个类继承Manage 都变成单例类
class EffectManage(Manage):
pass
em1 = EffectManage()
em2 = EffectManage()
print(em1 is em2)
返回
True
True 此时我们就做到了一个类只能创造一个实例
抽象类
抽象类是一种不能被实例化的类,它的存在主要用于定义其他类的公共接口和行为。抽象类只能被继承,并且子类必须实现抽象类中定义的抽象方法。
在许多编程语言中,包括Python,抽象类通常用作接口或基类。它们提供了一种机制,以确保派生类具有特定的属性和方法。
以下是抽象类的一些特点和用途:
-
不能被实例化:抽象类不能直接创建实例,因为它是不完整的,缺少具体的实现细节。只能通过派生类来创建对象。
-
包含抽象方法:抽象类中至少包含一个或多个抽象方法。抽象方法是没有实现的方法,只有方法的声明,派生类必须实现这些抽象方法。
-
定义接口:抽象类可以用于定义类的接口,规定子类必须实现的方法。它为派生类提供了一组共同的方法和属性,使得派生类具有一致的行为。
-
提供默认实现:抽象类可以包含非抽象方法,这些方法可以提供默认的实现。派生类可以选择性地覆盖这些方法,根据需要进行自定义实现。
-
促进代码复用:抽象类可以作为其他类的基类,通过继承来继承其公共方法和属性。这样可以避免在多个类中重复编写相同的代码,提高代码的可维护性和复用性
from abc import ABC, abstractmethod
class Shape(ABC): # 抽象类
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape): # 子类
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)
rect = Rectangle(5, 3)
print(rect.area()) # 输出: 15
print(rect.perimeter()) # 输出: 16
在上面的示例中,Shape
是一个抽象类,它定义了两个抽象方法 area()
和 perimeter()
。Rectangle
是 Shape
的子类,必须实现父类中的抽象方法。通过继承和实现抽象方法,实现了计算矩形面积和周长的功能。