0x00 前言简述
如果你接触过 Java、Golang 编程语言,那么你一定知道面向对象编程(OOP)的概念。面向对象编程(OOP)
是相对于面向过程编程而言的,面向过程编程是一种以过程为中心的开发模式,而面向对象编程则是以对象为中心的开发模式。
本章节我们将详细介绍Python的面向对象编程,不过在此之前我们先简单了解一下面向对象技术相关概念。
类[Class]
: 描述具有相同的属性和方法的对象的集合, 其中对象是类的实例。方法
:类中定义的函数。属性
:类变量或者实例变量用于处理类及其实例对象的相关的数据。类变量
:类变量在整个实例化的对象中是公用的,类变量定义在类中且在函数体之外,注:类变量通常不作为实例变量使用
。局部变量
:定义在方法中的变量,只作用于当前实例的类。实例变量
:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。封装
: 对外部隐藏对象的工作细节,例如,采用get_name()
方法来获取对象的名字,set_name()
方法来设置对象的名字。继承(Inheritance)
:即子类继承父类的属性和方法,可以重用父类的代码,继承也允许把一个派生类(derived class)的对象作为一个基类对象对待。例如,Dog 类的对象派生自 Animal类。方法重写(Method Overriding)
:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,此过程叫方法的覆盖
(override
)也称为重写
。多态(Polymorphism)
: 允许不同类的对象可以通过相同的接口调用方法,从而实现相同的操作。例如,在 Dog类和Cat类中eat()方法,输出可以是不同的信息。重用(Reusability)
:指的是通过类的继承、组合和模块化等技术,使得代码可以被多个不同的程序或不同的部分重复使用,从而减少代码重复,提升开发效率和代码的可维护性。实例化
:创建一个类的实例,类的具体对象。对象(Object)
:通过类定义的数据结构实例,对象包括两个属性
(数据成员类变量和实例变量)和方法
(由函数构成)。
weiyigeek.top-面向对象编程中的概念图
温馨提示:作者后续实践主要在 Ubuntu 24.04 TLS Python 3.12 Jupyter Notebook 环境中运行,若要配置为作者的学习环境,可参考《#AIGC学习之路》专栏中的流程,此外便于看友一起学习Python系列笔记,访问《#Python学习之路》专栏从前往后按照时间进行学习。
温馨提示:若各位看友在其他平台看到此篇文章,一定要关注公众号【全栈工程师修炼指南】进行持续学习!我们一同学习,一起进步,关注后回复【加群】哟!
0x01 Python 面向对象编程
描述: Python 本身就是一门面向对象编程语言(无处不对象
),它支持面向对象的程序设计方法, 在Python中的类提供了面向对象编程的所有基本功能,例如:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法
。
1.类和对象
Python 中一切皆为对象,类和对象是面向对象编程的基石,类对象支持两种操作属性引用和实例化。
类和对象是什么关系呢?
类和对象的关系就如同模具和用这个模具制作出的物品之间的关系, 一个类为它的全部对象给出了一个统一的定义, 而他的每个对象则是符合这种定义的一个实体,因此类和对象的关系就是抽象和具体的关系;
其基础语法如下:
代码语言:javascript复制# 定义类,类名首字母大写,分隔单词首字母也是大写
class ClassName:
<statement-1>
......
<statement-N>
# 属性(类变量)
classVar = 1024
# 构造方法
def __init__(self, arguments...):
# 实例变量
self.data = arguments[0]
# 自定方法,采用驼峰命名法
def customMethod(self):
print(self.data)
pass
# 实例化类,即类对象
# 类对象创建后,类命名空间中所有的命名都是有效属性名。
object = ClassName([arguments...])
# 访问类中的属性
object.attribute
# 访问类中的方法
object.customMethod()
类中特殊属性
__private_attrs
属性:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问,例如:内部调用self.__private_attrs
__doc__
属性:类文档字符串__class__
属性:类名__bases__
属性:返回基类元组,可以在运行时修改这个属性来实现动态继承。
类中特殊方法
__private_method()
: 定义私有方法,只能在类的内部调用,例如:self.__private_methods()
__init__()
:构造函数,在生成对象时调用__iter__()
: 定义迭代容器类型数据时必须实现的方法(在迭代器与生成器文章中讲解过)__next__()
: 定义迭代容器类型数据时必须实现的方法(在迭代器与生成器文章中讲解过)__del__()
:析构函数,释放对象时使用
注:类中 self 参数的作用是绑定方法(代表的是类的实例
),有了它我们就可以在类的内部进行调用类中属性和方法了,所以类中方法中第一个参数名称,通常设置为 self
, 也可以使用 this,但是最好还是按照约定使用 self。。
简单示例
- 一个简单的类与对象定义,实例化示例
# 定义类
class MyClassTest:
"""
MyClassTest 类:简单的类与对象定义,实例化示例
"""
count = 0 # 静态属性
public_attrs = 1024 # 公共属性
__private_attrs = 2048 # 私有属性
def __init__(self, name, url):
MyClassTest.count = MyClassTest.count 1 # 类名.属性名的形式引用 (每实例化一次 1)
self.name = name # 实例变量
self.url = url # 实例变量
def customPrint(self):
print('公共 customPrint 方法') # 公共方法
print("当前对象的地址: ",self)
print("当前类的名称: ",self.__class__)
print( self.name, self.url)
print('公共 customPrint 方 -> 调用私有 __privatePrint 方法') # 公共方法
self.__privatePrint() #
def __privatePrint(self): # 私有方法
print('私有 __privatePrint 方法')
def getCount(self):
return MyClassTest.count #类的静态属性应用(计算该类被实例化的次数)
# 实例化类,生成类对象
obj = MyClassTest("全栈工程师修炼指南","weiyigeek.top")
obj1 = MyClassTest("全栈工程师修炼指南","weiyigeek.top")
# 获取类中的属性
print("obj.__doc__ : ",obj.__doc__)
print("obj.__class__ : ",obj.__class__)
print("obj.public_attrs: ",obj.public_attrs)
# 调用类中的方法
obj.customPrint()
count = obj.getCount()
print('类 {},实例化 {} 次'.format(obj.__class__,count))
# 使用如下方法可以直接访问类中私有属性和方法(特别注意)
print("外部获取私有 _private_attrs 变量 = ", obj._MyClassTest__private_attrs) # 外部直接调用私有属性
obj._MyClassTest__privatePrint() # 外部直接调用私有方法
执行结果:
代码语言:javascript复制# 1.获取类中的属性
obj.__doc__ :
MyClassTest 类:简单的类与对象定义,实例化示例
obj.__class__ : <class '__main__.MyClassTest'>
obj.public_attrs: 1024
# 2.调用类中的方法
公共 customPrint 方法
当前对象的地址: <__main__.MyClassTest object at 0x766f5f51ae10>
当前类的名称: <class '__main__.MyClassTest'>
全栈工程师修炼指南 weiyigeek.top
公共 customPrint 方 -> 调用私有 __privatePrint 方法
私有 __privatePrint 方法
类 <class '__main__.MyClassTest'>,实例化 2 次
外部获取私有 _private_attrs 变量= 2048
私有 __privatePrint 方法
特别注意
- 定义类时,其类名
首字母大写
,如果类名中包含下划线,则下划线左边的字符大写,如:_ClassName
- 定义类中方法时,其方法名
遵守驼峰命名法
,如果方法名中包含下划线,则下划线左边的字符大写,如:_methodName
- python 采用一种叫“name mangling”技术, 将以双下划线开头的变量名巧妙的改了个名字而已, 所以在外部调用类中的私有属性或方法时,可使用
obj._MyClassTest__privatePrint()
进行调用但在开发中通常不建议这样使用,因为私有属性或方法通常是为了保护内部实现细节,外部直接调用会破坏封装性。
2.类封装
在 Python 中,封装是面向对象编程(OOP)的一个基本概念,它允许我们将数据(属性)和行为(方法)绑定在一起,并限制对某些组件的直接访问。
使用封装的主要好处是,有助于将对象的内部状态与外部接口隔离开来,增强代码的安全性、可维护性和灵活性。在实际开发中,善于利用封装可以显著提高代码质量。
示例演示:
- 示例1.定义一个类,简单演示类的封装特性
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
class MyClass:
name = '程序员' # 公共属性
msg = 'I Love Python'
__score = 0 # 私有属性,类外部无法直接进行访问
def __init__(self,name,msg,score): # 构造函数
self.name = name
self.msg = msg
self.__score = score
def get_name(self): # 公共方法,获取name属性
return self.__name
def set_name(self, name): # 公共方法,设置name属性
self.__name = name
def get_score(self): # 公共方法,获取私有属性
return self.__score
def set_score(self, score): # 公共方法,设置私有属性
if 0 < score < 120:
self.__score = score
obj = MyClass("全栈工程师修炼指南","人生苦短,及时Python",256) #实例化
print("姓名:",obj.get_name()) # 获取name属性
print("分数:",obj.get_score()) # 获取score属性
obj.set_score(120) # 设置score属性值
print("设置后的分数:",obj.get_score()) # 获取修改后的score属性
############ 执行结果 ##############
姓名: 全栈工程师修炼指南
分数: 256
设置后的分数: 64
- 示例2.定义一个矩形类,获取面积、周长,主要演示类的封装特性。
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
class Rectangle:
length = 5
width = 6
#设置长宽
def setRect(self,length,width):
self.length = length
self.width = width
#获取长宽
def getRect(self):
print("矩形的长宽为:%d , %d" %(self.length,self.width))
#获取面积
def getArea(self):
print("矩形面积 =",self.width * self.length)
#获取周长
def getGirth(self):
print("矩形周长 =",2 * (self.width self.length))
rect = Rectangle()
rect.setRect(15,16)
rect.getRect()
rect.getArea()
rect.getGirth()
############ 执行结果 ##############
矩形的长宽为:15 , 16
矩形面积 = 240
矩形周长 = 62
特别注意:
- 利用继承和组合机制来进行扩展时候,在类名/属性名(用名词)/方法名(用动词)的命名建议要规范化
- 私有属性就是在属性或方法名字前边加上双下划线,从外部是无法直接访问到并会显示AttributeError错误
- 当你把类定义完的时候,类定义就变成类对象,可以直接通过“类名.属性”或者“类名.方法名()”引用或使用相关的属性或方法。
- 类中属性名与方法名一定不要一致,否则属性会覆盖方法,导致BUG的发生;
3.类继承
描述: Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义,使用继承可以很方便地复用父类的代码。
Q&A: 面向对象继承机制给好处是?
答:如果一个类 A 继承自另一个类 B,就把这个 A 称为 B 的子类,把 B 称为 A 的父类、基类或超类。 所以说,继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码(偷懒)。 在子类继承父类的同时, 可以重新定义某些属性, 并重写
overwrite
某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能,另外为子类追加新的属性和方法也是常见的做法。
在 Python 类继承中, 可以实现单继承、多重继承、动态继承几种方式,其中多重继承是 Python 中比较高级的特性。
- 单继承:一个类只继承一个父类,简单且常见。
- 多重继承:一个类可以继承多个父类,可以组合多个父类的功能,但继承关系复杂。
- 动态继承:在运行时改变继承关系,适用于高级应用场景,例如插件系统或测试模拟,但需谨慎使用。
特别注意: 多重继承使用不当会导致重复调用(也叫钻石继承、菱形继承)的问题,导致代码执行效率低。
语法格式
代码语言:javascript复制class 子类名(基类,父类,或者超类名称)
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
....
<statement-N>
温馨提示: 圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
weiyigeek.top-类中继承的搜索
简单示例
- 示例1.单继承是指一个类只继承一个父类,例如
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# 父类
class Person:
name = '' # 基本属性
__UID = 0 # 私有属性
# 构造函数
def __init__(self, *args):
self.name = args[0]
self.__UID = args[1]
# 类方法
def get_person_info(self):
print("人员信息: %s , %d" %(self.name,self.__UID))
# 子类
class Student(Person):
grade = ''
def __init__(self, *args):
# 调用父类的构造函数
Person.__init__(self,args[0],args[1])
self.grade = args[2]
def get_student_info(self):
print("姓名: %s , 年级: %s ,身份证号:%d " %(self.name,self.grade,self._Person__UID))
# 实例化类,生成对象
xs = Student('经天纬地',512301198010284307,'2018级')
# 调用父类的方法
xs.get_person_info()
# 调用自身类方法
xs.get_student_info()
# 执行结果:
人员信息: 经天纬地 , 512301198010284307
姓名: 经天纬地 , 年级: 2018级 ,身份证号:512301198010284307
- 示例2.多重继承是指一个类可以继承多个父类。这种方式可以让一个类具备多个父类的特性,但也可能导致复杂的继承关系,例如:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# 例如,类A,B分别表示不同的功能单元,C为A,B功能的组合,这样类C就拥有了类A, B的功能。
class A:
nameA = "a"
def __init__(self,*args):
self.nameA = args[0]
print("Class A", args[0])
def get_a(self):
print(self.nameA," -> ",self.__class__)
class B:
nameB = "b"
def __init__(self,*args):
self.nameB = args[0]
print("Class B", args[0])
def get_b(self):
print(self.nameB," -> ",self.__class__)
# 例子中,C 类同时继承了 A 和 B 类,并可以使用这两个父类中的方法。
class C(A, B):
name = ""
def __init__(self,*args):
# 调用 A 类构造函数
A.__init__(self,args[0])
# 调用 B 类构造函数
B.__init__(self,args[1])
self.name = args[2]
print("Class C", args[2])
def get_c(self):
print(self.name," -> ",self.__class__)
# 实例化
c = C("父类 A","父类 B","类 C")
# 调用多重继承的方法
c.get_a()
c.get_b()
c.get_c()
############### 执行结果 ##############
Class A 父类 A
Class B 父类 B
Class C 类 C
父类 A -> <class '__main__.C'>
父类 B -> <class '__main__.C'>
类 C -> <class '__main__.C'>
- 案例3:动态继承,即继承的基类是动态的(有时候是 A,有时候是 B),例如:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
class MyClass:
def my_method(self):
return "My Method"
class A:
def method_a(self):
return "A method"
class B:
def method_b(self):
return "B method"
class C(A):
pass
# 方式1.修改类的 __bases__ 属性,来动态修改继承关系
C.__bases__ = (B,)
c = C()
print(c.method_b()) # 输出: B method
# 方式2.使用 type 函数动态创建类
def create_dynamic_class(base_class):
return type('DynamicClass', (base_class,), {})
DynamicClass = create_dynamic_class(B)
d = DynamicClass()
print(d.method_b()) # 输出: B method
继承的另类实现:组合
什么是组合(组成)?
答:Python 继承机制很有用,但容易把代码复杂化以及依赖隐含继承。因此经常的时候,我们可以使用组合来代替。在Python里组合其实很简单,直接在类定义中把需要的类放进去实例化就可以了。 简单的说: 组合用于“有一个”的场景中,继承用于“是一个”的场景中
演示类的组合示例:
代码语言:javascript复制#乌龟类
class Turtle:
def __init__(self, x):
self.num = x
# 鱼类
class Fish:
def __init__(self, x):
self.num = x
# 水池类
class Pool:
def __init__(self, x, y):
self.turtle = Turtle(x) # 组合乌龟类进来 (关键点)
self.fish = Fish(y) # 组合鱼类进来 (关键点)
def print_num(self):
print("水池里总共有乌龟 %d 只,小鱼 %d 条!" % (self.turtle.num, self.fish.num))
# 实例化类
pool = Pool(1, 10)
# 调用方法
pool.print_num() # 结果: 水池里总共有乌龟 1 只,小鱼 10 条!
4.类重写
描述:在 Python 继承机制中, 子类可以重写父类的属性或方法, 来达到当父类方法功能不足时可自定义扩展的目的。
Python 严格要求方法需要有实例才能被调用, 这种限制其实就是Python所谓得绑定
概念;
#!/usr/bin/python3
# coding=utf-8
import random as r
class Animal:
def __init__(self):
self.x = r.randint(0,10)
self.y = r.randint(0,10)
def eat(self):
print("前往 Fishc 位置坐标({},{}), Shark 动物开始进食".format(self.x,self.y))
class Fish:
def __init__(self):
self.x = r.randint(0,10)
self.y = r.randint(0,10)
def move(self):
self.x -= 1
print("Fish 位置坐标是:",self.x,self.y)
class Shark(Animal,Fish):
def __init__(self):
super().__init__() # 方法1:设置super() 指代 父类
#Fish.__init__(self) # 方法2: 调用父类构造方法
self.hungry = True
# 覆盖(重写) Animal 父类的 eat() 方法
def eat(self):
if self.hungry:
print("饿了,要吃饭@!")
self.hungry = False
else:
print("太撑了,吃不下了")
# 实例化类
obj = Shark()
# 调用父类方法
obj.move()
obj.eat()
# 使用 super 函数,用子类对象调用父类已被覆盖的方法。
super(Shark,obj).eat()
obj.eat()
obj.move()
######### 执行结果 ########
Fish 位置坐标是: 8 5
饿了,要吃饭@!
前往 Fishc 位置坐标(8,5), Shark 动物开始进食
太撑了,吃不下了
Fish 位置坐标是: 7 5
特别注意
super()
函数是用于调用父类(超类)的一个方法。- 当子类与父类定义
相同
的属性性或方法时, Python 不会删除父类的相关属性或方法而是将父类属性或方法覆盖;子类对象调用的时候会调用到覆盖后的新属性或方法,但父类的仍然还在,只是子类对象“看不到”
5.类多态
在 Python 编程中,多态(Polymorphism)
是指一种对象能够以多种形式出现的能力。具体来说,多态允许不同类的对象可以通过相同的接口调用方法,从而实现相同的操作,提高了代码的灵活性和可扩展性。在 Python 中,多态广泛应用于函数和方法的设计中,使得代码更加直观、易读和维护。
多态通常通过以下几种方式实现:
- 方法重写(Method Overriding):子类重写父类的方法,并在运行时根据对象的实际类型调用相应的方法。
- 鸭子类型(Duck Typing):在 Python 中,如果一个对象“看起来像鸭子,叫起来像鸭子”,那么它就可以被视为鸭子,即无需显式继承,只要实现了所需的方法即可。
1.方法重写示例
方法重写是多态最常见的实现方式, 子类可以重写父类的方法,并在运行时调用子类的方法。
代码语言:javascript复制class Animal:
def sound(self):
return "Some sound"
class Dog(Animal):
def sound(self):
return "Bark"
class Cat(Animal):
def sound(self):
return "Meow"
def make_sound(animal):
print(animal.sound())
dog = Dog()
cat = Cat()
make_sound(dog) # 输出: Bark
make_sound(cat) # 输出: Meow
在这个示例中,make_sound
函数接受一个 Animal
类型的参数,但实际上传入的是 Dog
和 Cat
对象。在运行时,Python 调用的是 Dog
和 Cat
类各自实现的 sound
方法。
2.鸭子类型示例
在 Python 中,多态性不仅依赖于继承,还依赖于对象的行为, 这种特性称为鸭子类型(Duck Typing),注意与上例中的方法重写的区别
。
class Dog:
def sound(self):
return "Bark"
class Cat:
def sound(self):
return "Meow"
class Bird:
def sound(self):
return "Chirp"
def make_sound(animal):
print(animal.sound())
dog = Dog()
cat = Cat()
bird = Bird()
make_sound(dog) # 输出: Bark
make_sound(cat) # 输出: Meow
make_sound(bird) # 输出: Chirp
在这个示例中,Dog
、Cat
和 Bird
类并没有继承同一个父类,但由于它们都实现了 sound
方法,因此它们可以被传入 make_sound
函数。这就是鸭子类型的一个例子:如果一个对象实现了某个方法,它就可以被当作该方法的类型处理。
6.类重用
在 Python 编程中,类的重用性(Reusability)指的是通过设计和实现类,使其可以在不同的上下文或项目中反复使用,从而避免代码重复,提升开发效率和代码的可维护性。重用性是软件工程中的一个重要原则,能够大大减少开发工作量和维护成本。
Python 中实现类重用性的方式主要有以下几种:
继承(Inheritance):通过继承,可以让一个类(子类)获得另一个类(父类)的属性和方法,从而实现代码重用。
代码语言:javascript复制class Animal:
def __init__(self, name):
self.name = name
def sound(self):
return "Some sound"
class Dog(Animal):
def sound(self):
return "Bark"
class Cat(Animal):
def sound(self):
return "Meow"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.name) # 输出: Buddy
print(dog.sound()) # 输出: Bark
print(cat.name) # 输出: Whiskers
print(cat.sound()) # 输出: Meow
在这个例子中,Dog
和 Cat
类继承了 Animal
类,重用了 Animal
类的 __init__
方法,并实现了各自的 sound
方法。
2.组合(Composition):通过将一个类的实例作为另一个类的属性来实现代码重用,这种方式也称为“has-a”关系。
代码语言:javascript复制class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self, brand):
self.brand = brand
self.engine = Engine() # 组合
def start(self):
return f"{self.brand} car: {self.engine.start()}"
car = Car("Toyota")
print(car.start()) # 输出: Toyota car: Engine started
代码语言:javascript复制在这个例子中,Car 类通过组合使用了 Engine 类,从而重用了 Engine 类的 start 方法。
3.模块化(Modularity):将相关的类和函数组织到模块中,可以在不同的程序中重用这些模块。
代码语言:javascript复制# utils.py
class MathUtils:
@staticmethod
def add(a, b):
return a b
@staticmethod
def subtract(a, b):
return a - b
# main.py
from utils import MathUtils
print(MathUtils.add(5, 3)) # 输出: 8
print(MathUtils.subtract(5, 3)) # 输出: 2
在这个例子中,MathUtils
类被组织到一个单独的模块 utils.py
中,可以在其他程序中重复使用。
总结:类的重用性是面向对象编程的重要特性之一,通过继承、组合、模块化等技术,可以有效地减少代码重复,提高开发效率和代码的可维护性。在 Python 编程中,注重代码的重用性,可以使你的程序更加简洁、易读和高效。
7.类魔术方法
Python 中的类中的魔术方法(magic methods),也称为特殊方法或双下方法(dunder methods),是一组特殊的方法,它们以双下划线(__
)开头和结尾。这些方法允许我们定制类的行为,包括对象的创建、表示、运算符重载等。
简单的说,魔术方法使得 Python 的面向对象编程更加灵活和强大,可以自定义类的各种行为,从而实现运算符重载、容器类型模拟等高级功能。在实际应用中,这些魔术方法可以帮助我们编写更加直观、易读和可维护的代码。
下面是一些常见的类魔术方法及其用途:
__init__(self, ...)
:初始化方法,在创建对象时自动调用。__del__(self)
:析构方法,在对象被垃圾回收时自动调用。__new__(cls, ...)
:创建对象时自动调用,用于自定义对象的创建过程。__str__(self)
:定义当使用str()
或print()
函数输出对象时的字符串表示。__repr__(self)
:定义对象的官方字符串表示,通常用于调试。__call__(self, ...)
:定义当对象被调用时自动执行。__add__(self, other)
:定义加法运算符__sub__(self, other)
:定义减法运算符-
的行为。__mul__(self, other)
:定义乘法运算符*
的行为。__mul__(self, other)
:定义乘法运算符*
的行为。__truediv__(self, other)
:定义真除法运算符/
的行为。__mod__(self, other)
:定义取模(求余数)运算符%
的行为。__pow__(self, other)
:定义幂运算符**
的行为。__cmp__(self, other)
:定义比较运算符<
、>
和==
的行为。__getattr__(self, name)
:在访问不存在的属性时调用。__setattr__(self, name, value)
:在设置属性值时调用。__delattr__(self, name)
:在删除属性时调用。__len__(self)
:定义对象的长度,使用len()
函数时调用。__getitem__(self, key)
:定义按键获取项目的行为,使用obj[key]
时调用。__setitem__(self, key, value)
:定义按键设置项目的行为,使用obj[key] = value
时调用。__delitem__(self, key)
:定义按键删除项目的行为,使用del obj[key]
时调用。
1.对象的初始化与销毁
__init__(self, ...)
:初始化方法,在创建对象时自动调用,用于初始化对象的属性。
class MyClass:
def __init__(self, value):
self.value = value
obj = MyClass(10)
__del__(self)
:析构方法,在对象被垃圾回收时自动调用,用于清理资源。
class MyClass:
def __del__(self):
print("Object is being deleted")
2.对象的表示
__str__(self)
:定义当使用 str()
或 print()
函数输出对象时的字符串表示。
class MyClass:
def __str__(self):
return f"MyClass with value {self.value}"
obj = MyClass(10)
print(obj) # 输出: MyClass with value 10
__repr__(self)
:定义对象的官方字符串表示,通常用于调试,可以使用 repr()
函数调用。
class MyClass:
def __repr__(self):
return f"MyClass({self.value})"
obj = MyClass(10)
print(repr(obj)) # 输出: MyClass(10)
3.运算符重载
__add__(self, other)
:定义加法运算符
的行为。
class MyClass:
def __init__(self, value):
self.value = value
def __add__(self, other):
return MyClass(self.value other.value)
obj1 = MyClass(10)
obj2 = MyClass(20)
result = obj1 obj2
print(result.value) # 输出: 30
__sub__(self, other)
:定义减法运算符 -
的行为。
class MyClass:
def __init__(self, value):
self.value = value
def __sub__(self, other):
return MyClass(self.value - other.value)
__mul__(self, other)
:定义乘法运算符 *
的行为。
class MyClass:
def __init__(self, value):
self.value = value
def __mul__(self, other):
return MyClass(self.value * other.value)
__truediv__(self, other)
:定义真除法运算符 /
的行为。
class MyClass:
def __init__(self, value):
self.value = value
def __truediv__(self, other):
return MyClass(self.value / other.value)
4.属性访问
__getattr__(self, name)
:在访问不存在的属性时调用。
class MyClass:
def __getattr__(self, name):
return f"{name} attribute not found"
obj = MyClass()
print(obj.some_attribute) # 输出: some_attribute attribute not found
__setattr__(self, name, value)
:在设置属性值时调用。
class MyClass:
def __setattr__(self, name, value):
print(f"Setting {name} to {value}")
self.__dict__[name] = value
obj = MyClass()
obj.attr = 10 # 输出: Setting attr to 10
__delattr__(self, name)
:在删除属性时调用。
class MyClass:
def __delattr__(self, name):
print(f"Deleting {name}")
del self.__dict__[name]
obj = MyClass()
obj.attr = 10
del obj.attr # 输出: Deleting attr
5.容器类型实现
__len__(self)
:定义对象的长度,使用 len()
函数时调用。
class MyClass:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
obj = MyClass([1, 2, 3])
print(len(obj)) # 输出: 3
__getitem__(self, key)
:定义按键获取项目的行为,使用 obj[key]
时调用。
class MyClass:
def __init__(self, items):
self.items = items
def __getitem__(self, key):
return self.items[key]
obj = MyClass([1, 2, 3])
print(obj[1]) # 输出: 2
__setitem__(self, key, value)
:定义按键设置项目的行为,使用 obj[key] = value
时调用。
class MyClass:
def __init__(self, items):
self.items = items
def __setitem__(self, key, value):
self.items[key] = value
obj = MyClass([1, 2, 3])
obj[1] = 10
print(obj.items) # 输出: [1, 10, 3]
__delitem__(self, key)
:定义按键删除项目的行为,使用 del obj[key]
时调用。
class MyClass:
def __init__(self, items):
self.items = items
def __delitem__(self, key):
del self.items[key]
obj = MyClass([1, 2, 3])
del obj[1]
print(obj.items) # 输出: [1, 3]
6.综合案例
示例.常规魔术方法的使用:
- 案例1:按照以下要求定义一个游乐园门票的类,并尝试计算2个成人 1个小孩平日票价, 面向对象编程的难点在于思维的转换。
# 平日票价100元
# 周末票价为平日的120%
# 儿童半票
class Ticket:
def __init__(self, weekend=False, child=False):
self.exp = 100
if weekend: #根据星期天/儿童来计算
self.inc = 1.2
else:
self.inc = 1
if child: #儿童的价格
self.discount = 0.5
else:
self.discount = 1
def calcPrice(self, num):
return self.exp * self.inc * self.discount * num #这里关键点
adult = Ticket()
child = Ticket(child=True)
print("2个成人 1个小孩平日票价为:%.2f" % (adult.calcPrice(2) child.calcPrice(1)))
############### 执行结果 #################
# 2个成人 1个小孩平日票价为:250.00
- 案例2.当实例化一个对象,count 变量 1, 当销毁一个对象变量自动-1 (注:首次执行)
class Count:
count = 0
def __init__(self): # 实例化对象时触发该魔术方法
Count.count = 1
def __del__(self): # 销毁对象时触发该魔术方法
Count.count -= 1
def getCount(self):
print("当前 count 的 %d 值" %Count.count)
a = Count()
b = Count()
c = Count()
print("Count 类实例化次数:",Count.count)
d = a # d 对 a 得引用
e = a # d 对 a 得引用
print("Count 类实例化次数:",Count.count)
del d #注意:这里不会触发del (只有在所有得引用被删除后才能触发del)
del e # 同样不能触发
del a # 触发 del 魔术方法
print("Count 类实例化次数:",Count.count)
############### 执行结果 #################
# Count 类实例化次数: 3
# Count 类实例化次数: 3
# Count 类实例化次数: 2
- 案例3.CapStr 类继承一个不可变得字符串类,使用
__new__
魔术方法,将字符串全部变成大写。
class CapStr(str):
def __new__(cls,string):
string = string.upper() # 将不可变类型的字符串,全部字母变成大写
return str.__new__(cls,string) # 返回修改后得字符给对象
a = CapStr('I love Study Python3!')
print(a)
############### 执行结果 #################
# I LOVE STUDY PYTHON3!
- 案例4.魔术方法
__str__
与__repr__
的区别。
#!/usr/bin/python
class Demo:
def __str__(self):
return '我是__str__魔术方法,需要print()输出'
class Demo1:
def __repr__(self):
return '2 - 我是__repr__魔术方法,直接对象输出'
a = Demo()
print("1 -",a)
b = Demo1()
print(b) #在>>> b 可以直接输出
################ 执行结果 #################
# 1 - 我是__str__魔术方法,需要print()输出
# 2 - 我是__repr__魔术方法,直接对象输出
示例.类关于计算的魔术方法案例
描述:在 Py2.2
以前类和类型是分开的(实际是类和属性的封装),但是在之后作者进行了统一(将Python类型转换成为工厂函数),例如: 工厂函数其实就是一个类对象,当你调用他们的时候,事实上就是创建一个相应的实例对象。
#实际工程函数就是类对象
>>> type(len)
<class 'builtin_function_or_method'> #内置函数
>>> type(int) #类型都是类对象
<class 'type'>
>>> type(tuple)
<class 'type'>
>>> class C:
... pass
...
>>> type(C) #类定义
<class 'type'>
# Py2.2 以前
# int('123') #实际是调用了int并返回一个整形值
# py2.2 以后
a = int('123') #其实就是一个实例化过后的对象
示例1.使用魔术方法进行四则运算,实现类似于C 的运算符重载
代码语言:javascript复制#!/usr/bin/python
# 继承 int 类
class Newint(int):
def __add__(self, value):
return super().__add__(value) # 方法1.super是超类指代父类
def __sub__(self, value):
return int(self) - int(value) # 方法2.必须要强制类型转换,如过向以下则会报错递归循环异常
#return self - value # RecursionError: maximum recursion depth exceeded in comparison
def __and__(self, value): #该魔术方法 = &
return super().__and__(value)
a = Newint(8)
b = Newint(6)
print("a b =",a b)
print("a - b =",a - b)
print("a & b =",a & b)
# 执行结果:
a b = 14
a - b = 2
a & b = 0
案例2. 类的反算法运算魔术方法
代码语言:javascript复制#!/usr/bin/python
# 案例1:反运算符
class Newint(int):
def __radd__(self, value): #反运算
return int.__sub__(value,self) #方法1:执行为减,value self 顺序会影响到 谁是减数 / 被减数
def __rsub__(self, value): #反运算
return super().__add__(value) #方法2:执行为减
def __rmul__(self, value):
return super().__truediv__(value)
a = Newint(5)
b = Newint(3)
print(a b) # 由于对象 a 可以支持 / - , 所以不会触发反运算,输出 8
print(a - b) # 由于对象 a 可以支持 / - , 所以不会触发反运算,输出 2
# 由于 1 是非对象 则采用 b 的处理方法
print("1 b =",1 b) # 1 - 3 => -2 由于改变了value,self顺序
print("1 - b =",1 - b) # 触发反运算 => 3 1 = 4
print("1 * b =",5 * b) # 触发反运算 => 3 / 5 = 0.6
# 案例2:一元运算符
class OneInt(int):
def __pos__(self): #定义负号行为 -x
return super().__pos__() # -(self)
a = OneInt(-1)
print("-(a) =",-a) #此时触发一元运算符 -(-1)
######### 执行结果 ########
# 8
# 2
# 1 b = -2
# 1 - b = 4
# 1 * b = 0.6
# -(a) = 1
特别注意:
__init__
方法不应当返回除了 None 以外的任何对象。__add__
方法中不应直接return self other会导致无限递归(深坑)。__radd__
方法中反运算需要注意 运算符支持前后顺序。
示例.类属性访问魔术方法
案例1.通过类的属性来设置与调用方法;
代码语言:javascript复制#!/usr/bin/python
# 案例1:
class AttrView:
def __getattribute__(self, name):
print("调用 getattribute 魔法方法")
return super().__getattribute__(name) #super()自动获取基类,这个类没有继承类默认是object类
def __getattr__(self,name):
print('调用 getattr 魔法方法')
def __setattr__(self,name,value):
print("调用 setattr 魔法方法")
super().__setattr__(name,value)
def __delattr__(self, name):
print('调用 delattr 魔法方法')
super().__delattr__(name)
demo = AttrView()
demo.x # 尝试不存在属性的时候触发 调用 getattribute / getattr 这两个魔法方法
setattr(demo,'x',1) # 设置属性 调用 setattr 魔法方法
demo.y = 1 # 设置属性 调用 setattr 魔法方法
demo.y # 获取属性 调用 getattribute 魔法方法
delattr(demo,'y') # 删除属性 调用 delattr 魔法方法
# 案例2:
class Rectangle:
def __init__(self, width = 0, height = 0):
self.width = width #会触发__setattr__魔术方法
self.height = height
def __setattr__(self, name, value):
if name == 'square': #正方形
self.height = value
self.width = value
else:
super.__setattr__(self, name, value) # 方法1:防止无限递归错误 (建议采用基类的setattr方法)
#self.__dict__[name] = value # 方法2
def getArea(self):
return self.width * self.height
def getPeri(self):
return (2 * (self.width) 2 * (self.height))
r1 = Rectangle(4,5)
print("矩形面积:",r1.getArea())
r1.square = 10 #建立一个属性表明是正方形
print("正方形面积: ",r1.getArea())
print("正方形周长:",r1.getPeri())
print("__dict__",r1.__dict__) #将类的全部属性放返回字典类型
########## 执行结果 ####################
# 矩形面积: 20
# 正方形面积: 100
# 正方形周长: 40
# __dict__ {'width': 10, 'height': 10}
示例.定制序列的魔术方法
描述:协议(Protocols)与其他的编程语言中的接口很相似,它规定您那些方法必须要定义;然而在Python中的协议就显得不那么正式;事实上更新是一种指南;
要求:编写一个不可改变的自定义列表,要求记录每个元素被访问的次数;
代码语言:javascript复制#!/usr/bin/python3
# -*- coding:utf-8 -*-
#功能:容器序列定制类型协议()
class Countnum:
def __init__(self,*args):
self.value = [x for x in args] # 列表表达式
self.count = {}.fromkeys(range(len(self.value)),0)
def __len__(self):
return len(self.value)
def __getitem__(self,index):
self.count[index] = 1 # 访问次数 1
return self.value[index] # 返回下标的值
a = Countnum(1,3,5,7,9)
b = Countnum(2,4,6,8,10)
print(a[1],b[1])
print(a[1],b[1])
print("两个对象数列之和:",a[3] b[3])
print("A对象访问的次数:",a.count)
print("B对象访问的次数:",b.count)
############ 执行结果 ################
# $ python demo3.23.py
# 3 4
# 3 4
# 两个对象数列之和: 15
# A对象访问的次数: {0: 0, 1: 2, 2: 0, 3: 1, 4: 0}
# B对象访问的次数: {0: 0, 1: 2, 2: 0, 3: 1, 4: 0}
8.类描述符
描述:描述符就是将某种特殊类型的类的实例指派给另外一个类的属性,比如property() 是一个比较奇葩的BIF,它的作用把方法当作属性来访问,从而提供更加友好访问方式;
描述符就是一个类,一个至少实现 __get__
()、__set__
() 或 __delete__
() 三个特殊魔术方法中的任意一个的类。
#!/usr/bin/python
#类属性 - 描述符
#定义一个类,为了实现原生的property原理必须使用下面的三个魔术方法
#案例1
class Decriptor:
def __get__(self,instance,owner):
print("getting ... ",self, instance, owner) #参数分别代表 (Decriptor本身类 , 类对象test , Test类本身)
def __set__(self,instance,value):
print("setting ... ",self, instance, value)
def __delete__(self,instance):
print("deleting ...",self,instance)
class Test:
x = Decriptor() #Decriptor() 类的实例 / 又叫属性x的描述符
test = Test()
test.x
test.x = 'ceshi'
del test.x
############ 执行结果 #######
# getting ... <__main__.Decriptor object at 0x000002443D18E908> <__main__.Test object at 0x000002443D18E940> <class '__main__.Test'>
# setting ... <__main__.Decriptor object at 0x000002443D18E908> <__main__.Test object at 0x000002443D18E940> ceshi
# deleting ... <__main__.Decriptor object at 0x000002443D18E908> <__main__.Test object at 0x000002443D18E940>
#案例2:自定义property
class MyProperty:
def __init__(self, fget=None, fset=None,fdel=None): #其他类的三个方法
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self,instance,owner):
return self.fget(instance) #传入实例对象的方法
def __set__(self,instance,value):
self.fset(instance,value)
def __delete__(self,instance):
self.fdel(instance)
class C:
def __init__(self):
self._x = None
def getX(self):
return self._x
def setX(self, value):
self._x = value
def delX(self):
print("delete 销毁属性!")
del self._x
#x 对象的描述符 (传入类里面的三个方法)
x = MyProperty(getX,setX,delX) #类实例
c = C()
c.x = 'Property'
print(c.x,c._x)
del c.x
######################
# Property Property
# delete 销毁属性!
9.类修饰符(装饰器)
描述:在 Python 中,类修饰符(Class Decorators)是用于修饰类的函数。它们可以用来修改类的定义或者扩展类的功能。类修饰符的使用方式类似于函数修饰符,只不过它们应用于类上。修饰符
是一个很著名的设计模式,常见的应用场景包括日志记录、方法添加、数据验证等。通过合理使用类修饰符,可以使代码更加简洁、可维护和可扩展。
实际上,修饰符就是一种优雅的封装,可在模块或类定义内的函数进行修饰; 一个修饰符就是一个函数, 它将被修饰的函数(紧邻的下一行)将传递做为参数,并返回修饰后的同名函数或其它可调用的东西。
基本语法:
代码语言:javascript复制def class_decorator(cls):
# 修改或扩展类的定义
return cls
@class_decorator
class MyClass:
pass
在 Python 类中,修饰符的语法与函数修饰符的语法类似,只是修饰符在类定义的前一行,除开之外还有三个内置修饰符,可将类中定义的方法变成静态方法( staticmethod ),
类方法 (classmethod)
和 类属性 (property)
。
@property
:属性修饰符@classmethod
:类方法修饰符@staticmethod
:静态方法修饰符,注意与之相邻的类方法中参数不能带self。
其中静态方法
的优点是,不会绑定到实例对象上,换而言之就是节省开销,这也意味着并不需要 self 参数,因此即使是使用对象去访问,self 参数也不会传进去。
基础示例
- 示例1.例子中,log_class 修饰符通过创建一个新类 Wrapped 来扩展原始类 MyClass,在实例化时打印日志信息。
#!/usr/bin/python
# coding=utf-8
def log_class(cls):
class Wrapped(cls):
def __init__(self, *args, **kwargs):
print(*args)
print(f"Creating instance of {cls.__name__}")
super().__init__(*args, **kwargs)
return Wrapped
@log_class
class MyClass:
def __init__(self, value):
self.value = value
instance = MyClass("公众号:全栈工程师修炼指南")
# 输出:
# 公众号:全栈工程师修炼指南
# Creating instance of MyClass
- 示例2.使用多个修饰符来修饰同一个类,修饰符的应用顺序是自下而上的。
# 例子中,add_method 修饰符向 MyClass 类添加了一个新的方法 new_method 。
def decorator1(cls):
print("Applying decorator1")
return cls
def decorator2(cls):
print("Applying decorator2")
return cls
@decorator1
@decorator2
class MyClass:
pass
# 输出:
# Applying decorator2
# Applying decorator1
- 示例3.给类添加一个新的方法。
def add_method(cls):
def new_method(self):
return f"New method in {self.__class__.__name__}"
cls.new_method = new_method
return cls
@add_method
class MyClass:
def __init__(self, value):
self.value = value
instance = MyClass(10)
print(instance.new_method())
# 输出: New method in MyClass
- 示例4.使用类修饰符,可以定义一个装饰器来打印函数执行时间。
class timeslong1:
def __init__(self,func):
self.f = func #传入的实际是f() 函数
def __call__(self):
start = time.perf_counter()
print("It's time starting ! ")
self.f()
print("It's time ending ! ")
end = time.perf_counter()
print("It's used : %s ." % (end - start))
@timeslong1 #将下面的函数或者类作为自己的参数
def f():
y = 0
for i in range(3):
y = y i 1
print(y)
return y
f() #调用时候触发@修饰符
######### 执行结果.START########
# It's time starting !
# 1
# 3
# 6
# It's time ending !
# It's used : 0.00160638099999999 .
######### 执行结果.END########
- 示例 5.在类初始化时进行数据验证。
# 例子中,validate 修饰符修改了 MyClass 的 __init__ 方法,在初始化时对传入的参数进行数据类型验证
def validate(cls):
original_init = cls.__init__
def new_init(self, *args, **kwargs):
for key, value in kwargs.items():
if not isinstance(value, (int, float)):
raise ValueError(f"Expected int or float, got {type(value).__name__}")
original_init(self, *args, **kwargs)
cls.__init__ = new_init
return cls
@validate
class MyClass:
def __init__(self, x, y):
self.x = x
self.y = y
try:
instance = MyClass(x=10, y="20")
except ValueError as e:
print(e) # 输出: Expected int or float, got str
- 示例 6.类中三种内置修饰符,实例演示
#!/usr/bin/python3
# 案例1.类方法、静态方法效果示例。
class Hello:
def __init__(self):
pass
# 方式1:正是因为设置静态方法和类方法过于讨人吐槽,因此 Python 的作者才开发出了函数修饰符的形式替代。
def foo(cls):
print("旧方法:使用 classmethod 设置为函数为类方法,调用类方法 foo()")
foo = classmethod(foo) # 将 foo() 方法设置为类方法
# 方式2.类方法修饰符
@classmethod
def print_hello(cls):
print("类方法修饰符:调用类方法 print_hello()",)
# 方式3.静态方法修饰符 (注意这里的巨坑 self )
@staticmethod
def static_hello(arg):
return "静态方法修饰符:调用静态方法 static_hello Value =" str(arg)
# 直接使用 类名.方法名() 调用类方法
Hello.foo()
Hello.print_hello()
print(Hello.static_hello(1)," 对象:",Hello.static_hello)
############### 执行结果 ################
# 旧方法:使用 classmethod 设置为函数为类方法,调用类方法 foo()
# 类方法修饰符:调用类方法 print_hello()
# 静态方法修饰符:调用静态方法 static_hello Value =1 对象: <function Hello.static_hello at 0x766f5d147f60>
# >>> c1 = Hello()
# >>> c2 = Hello()
# # 静态方法只在内存中生成一个,节省开销
# >>> c1.static_hello is C.static_hello #True
# >>> c1.nostatic is C.nostatic # False
# >>> c1.static_hello # <function Hello.static_hello at 0x000001F2DB73C950>
# >>> c2.static _hello # <function Hello.static_hello at 0x000001F2DB73C950>
# >>> Hello.static_hello # <function Hello.static_hello at 0x000001F2DB73C950>
# # 普通方法每个实例对象都拥有独立的一个,开销较大
# >>> c1.nostatic # <bound method Hello.nostatic of <__main__.C object at 0x03010590>>
# >>> c2.nostatic # <bound method Hello.nostatic of <__main__.C object at 0x032809D0>>
# >>> Hello.nostatic # <function Hello.nostatic at 0x0328D2B8>
# 案例2:内置属性修饰符效果示例
class C:
def __init__(self, size=10):
print("初始化类属性",size)
self.size = size
# 属性修饰符,在类中定义一个属性,并绑定到类中
@property #关键点 类属性 / 绑定的属性是x
def x(self):
return self.size
@x.setter
def x(self, value):
print("设置类属性 x",value)
self.size = value
@x.deleter
def x(self):
print("删除类属性 x")
del self.size
demo = C()
print("获取属性的值:",demo.x) # 获取属性的值: 10
demo.x = 1024
print("获取更改后属性的值:",demo.x) # 获取更改后属性的值: 1024
del demo.x
############## 执行结果 ###############
# 初始化类属性 10
# 获取属性的值: 10
# 设置类属性 x 1024
# 获取更改后属性的值: 1024
# 删除类属性 x
如果此篇文章对你有帮助,请你将它转发给更多的人!