【python】进阶之面相对象(四)【重点】

4 面相对象【重中之中】

面向对象不是什么语法关键字,也不是什么函数方法,

而是一种思想,具备这种思想,才能写Python,

换言之:

如果不具备这种思想,就写不好Python !!!

如果不具备这种思想,就写不好Python !!!

如果不具备这种思想,就写不好Python !!!

通过本文可以对Python 面向对象有写启蒙,但是具体要根据项目实际场景应用才能深刻体会。

4.1 一切皆对象【重点】

  • Python是一门面向对象语言
  • Python中一切皆对象
  • 无法深刻理解面向对象就无法用好Python
  • 自定义的类实例化对象,其实像基本数据类型(int\string\list\dict等)、函数、类都是对象,都可以按照对象的方式来操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 自定义Student类 并实例化对象jack
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

def say_hello(self):
print(f"My name is {self.name}")


jack = Student("jack", 18)
print(type(jack)) # 使用type查看变量类型 <class '__main__.Student'>
jack.say_hello()
print(jack.age)



# 普通变量也都是对应类的实例化对象,比如列表变量可以使用 pop()、append()等方法。
ll = [] # 等价于 ll = list(), list就是python 内置类
ll.append(123)


# 函数也是
def foo():
pass

print(type(foo)) #函数foo是function的实例化对象 <class 'function'>
  • type查看对象类型
  • 通过dir函数查看对象下面有哪些属性和方法
  • 通过id()查看变量的内存地址
1
2
3
4
5
6
7
8
9
10
11
num1 = 400		
num2 = 500

print(num1 is num2) # False, 内存地址不一样,开辟两块不同的内存地址
n1 = 100
n2 = 100
print(n1 is n2) # True, 小整数池优化,通用一块内存地址。

stu1 = Student("jack", 18)
stu2 = Student("jack", 18)
print(stu1 is stu2) # False, 内存地址不一样,实例化对象都开辟一块新的内存地址。

4.2 面向对象的封装思想【重点】

  • 基本数据类型int/str/bool的功能单一,于是有了容器型数据类型list/tuple/dict/set等
  • 独立的代码块使用起来冗余,于是又了函数,便于特定功能代码块的组织管理、重复使用
  • 面向对象:将程序进一步整合,对象封装了数据属性和方法属性(数据和功能)即对象也是“容器”
  • 进一步,许多同类型的对象,是不是也被抽象成了类,所以类也是“容器”,封装了同类型对象共有的数据和功能,可以重复使用

4.3 绑定方法和非绑定方法【有用】

  • python中,类内部定义的函数分为两大类:绑定方法和非绑定方法
  • 绑定方法有两个:绑定给对象的方法,绑定给类的方法;绑定给谁的方法,谁在调用的时候就不用传第一个参数,比如说self就是绑定给对象的方法,实例化对象调用的时候就不用传第一个参,cls就是绑定给类的方法,类使用的时候就不需要传第一个参
  • 非绑定方法:类型普通函数,谁都可以使用,遵循普通函数的传参,目的只是为了封装在一起,如:静态方法。就是谁都可以调用且调用的时候不需要传第一个参
1
2
3
4
5
6
7
8
9
10
11
12
class Student():
def __init__(self,name,age):
self.name=name
self.age=age
def edit_name(self,new_name):
self.name = new_name
@classmethod
def get_student(cls,name,age):
return cls(name,age) # 相当于Student(name,age)
@staticmethod
def add(a,b):
return a+b # 这个方法没有用的类的任何属性方法,是一个静态方法,加装饰器

4.4 类装饰器property【了解】

python中的property就是一个类装饰器,有两个用途:

  • 用途一:将函数属性伪装成数据属性
  • 用途二:统一数据属性的查、改、删除操作【把数据转成对象去操作】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 用途一:
# 类中的某个数据值,其实是通过拿到类中的某个属性然后计算出来的值,
# 但是用户不想通过类的方法去拿到这个值,那就加上property装饰器
# 然后就可以通过类似于对象.属性值的方式来通过对象.方法名这种方式拿到这个值

class People():
def __init__(self,name,w,h):
self.__name=name
self.w=w
self.h=h

@property
def bmi(self):
return self.w / (self.h**2)
obj1 = People('jack'66,1.75)
print(obj1.bmi)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 用途二:在修改和删除时做逻辑判断
# 当name 遇到查询时,触发被property装饰的函数的执行,
# 当name 遇到赋值操作,即 = 时触发被property.setter装饰的函数的执行
# 当name 遇到删除操作,即 del 时触发property.deleter装饰的函数的执行
class Poeple:
def __init__(self, name, w, h):
self.__name = name
self.w = w
self.h = h

@property
def name(self):
return self.__name

@name.setter # 修改属性的装饰器,当出发修改时,就会调用这个方法
def name(self, value):
if type(value) is not str:
print('必须传入str类型')
return
self.__name = value

@name.deleter # 删除属性的装饰器,当del删除某个属性的时候,触发这个方法
def name(self):
print('不能删')


obj1 = Poeple('jack', 66, 1.75)
print(obj1.name)
obj1.name = 'aaa'
del obj1.name

4.5 cached_property【了解】

  • 将一个类方法转换为特征属性,一次性计算该特征属性的值,然后将其缓存为实例生命周期内的普通属性。
  • 类似于对 property 但增加了缓存功能。对于不可变的高计算资源消耗的实例特征属性来说该函数非常有用
  • 内置包functools下面的类装饰器cached_property

cached_property 是一个 Python 装饰器,它可以用于定义一个缓存的属性,这意味着属性的值在第一次访问后会被计算,并将结果缓存起来,后续的访问会直接返回缓存的值,从而避免重复计算。

cached_property 装饰器可以在需要计算代价较高的属性时使用,以提高性能和效率。

在使用 cached_property 装饰器之前,你需要先安装一个名为 cached-property 的第三方库,它提供了这个装饰器的实现。

这个用的少

4.6 属性查找顺序【了解】

属性或者方法的查找顺序的原则

  • 先从对象自身身上找,有则使用;没有的话,再从上找,有则使用;类上也没有的话就报错则报错AttributeError

示例1:查看对象或类有哪些属性或方法可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Student:
school = 'PKU' # 类属性

def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
self.course = None

def get_info(self): # 绑定给对象的方法
print('学生信息:名字:%s 年龄:%s 性别:%s' % (
self.name,
self.age,
self.gender
))

def select_course(self, new_class): # 绑定给对象的方法
print('正在选课')
self.course = new_class


print(Student.__dict__) # 查看类下的属性和方法
print(Student.school) # .的方式访问类属性/方法。本质是 Student.__dict__["school"]
print(Student.get_info)
print(Student.__dict__["school"]) # 访问类属性/方法
print(Student.__dict__["get_info"])


stu1 = Student("jack", 18, "男")
print(stu1.name)
print(stu1.get_info)
print(stu1.__dict__) # 访问对象的属性
stu1.haha = "123" # 增加一个数据属性
stu1.add = lambda x: print(x) # 增加一个普通函数属性,不是对象的绑定方法哦

4.7 隐藏属性【有用】

  • 如果类的设计者不想某些属性被访问,就可以将该属性给隐藏起来。
  • 隐藏属性可以隐藏类中的公有属性和对象的私有属性,对象访问会报错,
  • 实现方法:使用双下划线开头命名的属性将会被隐藏
  • 另:单下划线开头的属性和方法类似于保护属性,类内可以访问,类外也可以访问但不会提示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass:
def __init__(self):
self.public_attr = 42
self._protected_attr = 23
self.__private_attr = "secret"

obj = MyClass()

print(obj.public_attr) # 可以直接访问
print(obj._protected_attr) # 可以直接访问,但是视为受保护
# print(obj.__private_attr) # 不能直接访问,会报错

print(obj._MyClass__private_attr) # 使用名称修饰的方式访问私有属性

示例2:在类外部无法直接访问双下滑线开头的属性,在类内部可以访问到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Foo:
...

def __f1(self): # 隐藏对象的私有绑定函数
print('from test')

def f2(self): # f2没有被隐藏,可以被访问到
print(self.age)
print(self.__name)
self.__f1()


obj = Foo('jack', 18)
obj.f2() # 不会报错,可以访问 __name, __f1

示例3:在类定义阶段,双下划先开头的属性会发生变形

1
2
3
4
# 在类定义阶段,双下划先开头的属性会发生变形,变为 _Foo__x, _Foo__f1, _Foo__name, 所以在在类外无法直接通过过 .__x 的方式访问。
# 但是可以通过变形后的 _Foo__x访问。但这是没有意义的。
# 所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。
# 类似的,python中开发人员一般通过约定的方式,任务_开头的变量是内部使用的变量,__开头的变量是被保护的变量。

示例4:只在定义阶段发生形变

1
2
3
4
5
6
7
8
9
10
# 之所以在类内部可以直接通过__x 访问,是因为__开头的属性会在检查类体代码语法时统一发生变形(类定义阶段)
# 这种变形操作只在检查类体语法的时候发生一次,之后再定义的__开头的属性都不会变形,所以可以直接__y访问到

class Foo:
__x = 1 # 隐藏类的公有数据属性
...


Foo.__y = 2 # 增加类Foo的数据属性
print(Foo.__y) # 可以访问到

4.8 开发接口【了解】

  • 定义属性的目的是为了被使用,所以隐藏属性的目的不是单纯的隐藏,隐藏式为了更好的使用。
  • 想要这些属性被使用,那就必须提供一个对外的接口,(没有被隐藏的属性)
  • 隐藏数据属性:将数据隐藏起来就限制了类外部对类内数据的直接操作,然后类内应该提供相应的接口(函数方法)来允许外部间接的操作数据,接口之上呢可以附加额外的逻辑来对数据的操作进行严格的判断,比如说加上各种 if 啊,如果不符合就raise抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Student:

def __init__(self, name):
self.__name = name

def get_name(self):
print(self.__name) # 通过该接口就可以间接地访问到名字属性

@property
def name(self):
return self.__name

def set_name(self, new_name):
# 通过改接口判断用户修改的新名字是否合法;非法则修改,不合法就不修改
if type(new_name) is not str:
raise TypeError("名字必须是字符串类型")
self.__name = new_name


stu = Student("jack")
stu.set_name("111")
stu.get_name()
print(stu.name)

4.9 python多继承【概念】

  • 继承是面向对象思想的另一个特性。它的存在是为了解决类与类之间代码重复的问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 不使用继承
class Student:
school = 'PKU'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex

def select_course(self):
print(f'学生:{self.name}正在选课。。。')


class Teacher:
school = 'PKU'
def __init__(self, name, age, sex, level):
self.name = name
self.age = age
self.sex = sex
self.level = level

def score(self):
print(f'老师:{self.name}正在打分。。。')


# 不使用继承
class PkuPeople:
school = 'PKU'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex


class Student(PkuPeople):
def select_course(self):
print(f'学生:{self.name}正在选课。。。')


class Teacher(PkuPeople):
def __init__(self, name, age, sex, level):
super().__init__(name, age, sex)
self.level = level

def score(self):
print(f'老师:{self.name}正在打分。。。')
  • 多继承的优点:同时继承多个父类属性和方法,功能强大。

  • 多继承缺点:代码可读性变差。

  • 通过类的mro()方法查看多继承的查找顺序。

4.10 深度优先和广度优先【知道就行】

1
2
3
4
5
6
7
8
9
# python2中区分经典类和新式类:
- 经典类:没有继承object类的子类,以及该子类的子类子子类。。。
- 新式类:继承了object类的子类,以及该子类的子类子子类。。。

# python3中全部默认继承object,所以都是新式类。
- object类提供了一些常用内置方法的实现,如用来在打印对象时返回字符串的内置方法__str__
- 通过类的内置属性__bases__可以查看类继承的所有父类


  • 继承的菱形结构

  • 概念一:经典类和新式类

  • 概念二:深度优先和广度优先

image-20220508171910552

如上图,继承关系成菱形

4.11 Mixin混合机制【有点用】

  • 允许将某个单一功能封装在独立的类中,然后通过一个类多重继承这些功能类,混合在一起,实现代码的复用和模块化
  • 单一职责,一个类只关注单一功能
  • 多重组合,通过多继承将多类混合在一起
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Mixin 类
class LoggingMixin:
def log(self, message):
print(f"Log: {message}")

class EmailMixin:
def send_email(self, subject, body):
print(f"Sending email to {self.email}: {subject}, {body}")

# 具有 LoggingMixin 和 EmailMixin 功能的类
class User(LoggingMixin, EmailMixin):
def __init__(self, username, email):
self.username = username
self.email = email

# 创建 User 对象并使用 Mixin 功能
user = User("alice", "alice@example.com")
user.log("User created")
user.send_email("Welcome", "Welcome to our website!")

  • Mixin不是单一功能,只是一种编程思维,通常被定义为混合的类,类名在命名的时候都会在后面加一个Minxin来表示这个类是混合类

Minx使用规范

  • 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀。
  • 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin。
  • 它不依赖于子类的实现;子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。
  • 通常Mixin的类放在子类括号内的右边(表示非核心功能)

4.12 派生和组合

派生:在使用父类原有的方法的基础上,增加新的内容(用super( )方法重写父类方法)

子类可以原封不动的使用父类的属性/方法,也可以重写父类的属性/方法,还可以在使用父类的属性/方法的同时,添加新的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 方式1:使用只用父类的方法,需要传self参数。
class People:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender


class Student(People):
def __init__(self, name, age, gender, code):
People.__init__(self, name, age, gender)
self.code = code


stu = Student("jack", 18, "男", 10111)
print(stu.__dict__)


# 方式2:使用super(), 按照MOR列表的顺序往下找
class Student(People):
def __init__(self, name, age, gender, code):
super().__init__(name, age, gender) # 用super()重写了父类属性
self.code = code
  • 组合:对象的某个属性,是另一个类的实例化对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student:
def __init__(name, age):
self.name = name
self.age = age

class Coursr:
pass

stu = Student('jack', 18)
course_obj = Course()
stu.course = course_obj

# 继承是一种“是”的关系,比如老师是人、学生是人。
# 组合则是一种“有”的关系,比如老师有生日,老师有多门课程。

4.13 限制子类必须实现的方法【重点】【抽象概念】–设计模式的一种

在父类中定义的方法,需要子类必须实现,此时就有两种限制方法

方式1:使用模块abc,(即抽象类abstract class的缩写)

就是加ab的装饰器的方法,然后你在继承的时候,必须去定义这个方法,不然实例化的时候就会出错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import abc
import abc

# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,抽象类本身不能被实例化
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
def talk(self): # 抽象方法中无需实现具体的功能
pass

class Cat(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
def talk(self): # 必须定义talk方法
pass

cat=Cat() # 若子类中没有一个定义talk的方法则会抛出异常TypeError,无法实例化

方式2:使用 NotImplementedError

1
2
3
4
5
class Animal:
def talk(self):
raise NotImplementedError("该方法必须被实现")

# 如果子类没有实现talk方法,子类调用talk时候使用父类的talk,此时直接抛出异常 NotImplementedError

4.14 isinstance

当我们需要获取一个对象的类型是,可以使用`type()

当我们需要判断一个对象是否是指定类型时可以使用isinstance函数快速判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class People:
def __init__(self, name):
self.name = name


p = People("jack")
print(isinstance(p, People)) # 判断p 是否是People类型

a = 10
print(isinstance(a, int))
print(isinstance(a, (str, int))) # 可以是多个备选类型,以元组的形式做第二个参数
除此之外,还有一个函数可以判断类与类之间的父子关系,`issubclass`
class Animal:
def __init__(self, name):
self.name = name
class People(Animal):
pass
print(issubclass(People, Animal)) # True
print(issubclass(Animal, People)) # False
print(issubclass(People, (list, Animal))) # True
print(issubclass(People, People)) # True

4.15 反射机制【重点】

  • 首先: 反射机制是什么?反射机制
    首先,python是一门动态语言,python的反射机制就是指,可以在程序运行的时候获取程序的属性方法,而不需要在编码时明确知道这些对象、方法名、或属性名的具体信息,反射使得你可以在程序运行时动态地获取、操作和探索对象的属性和方法。python的反射机制核心4点:
1
2
3
4
5
6
hasattr(object, "x")  # 判断对象是否有这个属性,如果有 就返回布尔值True 或 False
getattr(object, name, default=None) # 获取一个对象的name属性,如果name属性不存在的话,返回None
setattr(object, 'x属性名', 'y属性名') # 更新对象x属性的值,就是object.x = 'y' 如果x属性不存在,那就新增一个y属性
delattr(object, 'y属性名') # 删除对象的一个属性y,如果属性y不存在,就会报错


  • 1、getattr( object, name,default=None):
  • 2、hasattr():
  • 3、setattr():
  • 4、delattr():
  • 5、dir(): 这个常用,这个就时获取对象所有属性
  • 6、exec():和eval():函数

python中使用反射非常方便,仅需要使用4个内置函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 比如判断用户输入的一个字符串是不是一个对象的属性, 使用if-elif-else 判断较为繁琐。
class Ftp:
def get(self):
pass
def set(self):
pass
def delete(self):
pass

ftp = Ftp()
cmd = input('请输入指令:').strip()
if cmd == 'get':
ftp.get()
elif cmd == 'set':
ftp.set()
elif cmd == "delete":
ftp.delete()
else:
print('指令不存在')

hasattr(obj, 'x') # 判断对象是否有一个属性,返回布尔值
getattr(object, name, default=None) # 获取一个对象的name属性,如果name属性不存在的返回None
setattr(x, 'y', 'v') # 更新对象y属性的值, 等价于 x.y = 'v',当y不存在的新增
delattr(x, 'y') # 删除对象的一个属性, 等价于 del x.y 属性y不存在则报错
# 上面使用条件判断的例子使用反射可以简化如下
class Ftp:
...

ftp = Ftp()
cmd = input('请输入指令:').strip()
if hasattr(ftp, cmd):
getattr(ftp, cmd)()
else:
print('指令不存在')
  • 反射类的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Foo(object):

staticField = "HAHA"

def __init__(self):
self.name = 'jack'

def func(self):
return 'func'

@staticmethod
def bar():
return 'bar'

print getattr(Foo, 'staticField')
print getattr(Foo, 'func')
print getattr(Foo, 'bar')
  • 反射当前模块成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import sys


def s1():
print('s1')


def s2():
print('s2')


this_module = sys.modules[__name__]
print(hasattr(this_module, 's1'))
getattr(this_module, 's2')
  • 补充:反射的底层原理
1
2
print(dir(obj))
print(obj.__dict__[dir(obj)[index]])

作用呢?

动态调用和访问对象的属性,getattr() setattr() hasattr()这些都可以随时修改和获取对象的属性

动态获取token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class ApiClient:
def __init__(self):
self.token = None

def login(self, username, password):
# 假设这里调用登录接口,获取返回的 token,并将其存储到 self.token 中
response = self.send_request('/login', {'username': username, 'password': password})
self.token = response['token']

def send_request(self, endpoint, data):
# 发送接口请求的逻辑,返回响应数据
pass

def request_with_token(self, endpoint, data=None):
if self.token:
headers = {'Authorization': f'Bearer {self.token}'}
else:
headers = None
return self.send_request(endpoint, data, headers)


# 在测试中使用反射获取 token 并进行接口请求
def test_api():
client = ApiClient()
client.login('your_username', 'your_password')

# 使用反射获取 token
token = getattr(client, 'token', None)
assert token is not None, "Token not obtained"

# 使用 token 发送其他接口请求
response = client.request_with_token('/some_endpoint', {'param': 'value'})
assert response['status'] == 'success', "Request failed"

# ... 其他测试逻辑

1
2
3
4
5
6
7
8
9
10
11
12
# 那接口自动化的过程中,还有什么其他的方式拿token吗
# 在接口自动化过程中,除了使用反射机制外,还有一些其他的方式可以获取 token,具体取决于接口的设计和实际情况。以下是一些常见的获取 token 的方式:

# 固定 Token: 在开发环境中,可以事先创建一个特定的用户账号,为其生成一个固定的 token,用于接口测试。这样每次测试可以直接使用这个固定的 token。

# 环境变量: 将 token 存储为环境变量,测试时从环境变量中读取 token 值。这样可以在不同环境中灵活切换和管理 token。

# 动态生成: 在每次测试运行前,通过自动化脚本执行登录操作,获取实时的 token。登录成功后,从接口响应中提取 token 并存储到变量中,供后续接口请求使用。

# Token 中心: 在一些复杂的系统中,可能会有专门的 token 中心用于管理用户的身份验证信息。测试时,可以调用 token 中心的接口获取有效的 token。

# JWT(JSON Web Token): 如果接口使用了 JWT 进行身份验证,可以根据 JWT 的生成规则生成合法的 token。这需要了解 JWT 的具体生成方式。

4.16 单例模式【重点】–设计模式的一种

  • 首先,单例模式是什么? 是软件的一种设计模式,
  • 目的:无论调用多少次产生的实例对象,都是指向同一个内存地址,仅仅只有一个实力(一个对象
  • 方式: 首先实现单例模式的手段有很多终端鹅,但总的原则是什么,是包装你定义的一个类,只要实例化一个对象,下一次再实例化对象的时候,就直接返回你已经实例化过的这个对象,不再做实例化的操作,
    所以这里关键的一点就是,你该如何去判断这个类是否已经实例化过一个对象
  • 这里介绍两类方式:
    • 一个是通过模块导入的方式;
    • 二个事通过一个魔法方法去判断方式
  • 应用的场景呢?什么时候用单例模式去创建一个类呢?
    • 网站计数器,确保全局只有一个计数器实例化对象,用来记录网站的总访问次数。

    • **配置管理器:配置信息的管理,确保你整套代码中,只有一个配置管理器的实例,用于统一管理配置信息,比如说同意发送请求,所有的请求都是通过这个实例化对象去访问的,

      **

    • 日志记录器:全局代码运行的时候确保只有一个日志记录器的实例化对象,用来记录这套代码运行时的日志记录

    • 数据库连接池:保证整套代码运行的过程中,只有一个数据库连接池的实例,方便管理对数据库的同意连接和同意关闭连接池

  • 注意一下:单例模式虽然可以在很多特定场景下体统便利,但不可以过度依赖单例模式导致代码的可测试性和可维护性降低

通过模块导入的方式怎么实现:

1
2
3
4
5
6
7
8
9
# singleton.py
class Singleton:
def __init__(self):
print("Singleton instance created")


singleton_instance = Singleton()
# 在singleton.py 文件里面定义了一个类Singleton,
# 通过singleton_instance = Singleton()

在另一个文件中导入这个模块

1
2
3
4
5
6
7
8
9
10
11
# main.py
import singleton

# 创建第一个实例,会输出"Singleton instance created"
instance1 = singleton.singleton_instance

# 创建第二个实例,不会再次输出"Singleton instance created"
instance2 = singleton.singleton_instance

print(instance1 is instance2) # 输出:True,说明两个实例是同一个实例

通过类绑定方法的方式来实现单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 通过在类方法中控制实例的创建和返回,可以确保在程序中只有一个实例被创建。
class Singleton:
_instance = None

def __init__(self):
if Singleton._instance is not None:
raise ValueError(
"Singleton instance already exists") # 第二次实例化的时候,_instance不为空 抛出异常,不打印Singleton instance created
Singleton._instance = self
print("Singleton instance created")

@classmethod
def get_instance(cls):
if cls._instance is None: # 第一次实例化的时候,类的_instance为空,走到了上面的方法了,打印Singleton instance created
cls._instance = cls() #
return cls._instance


# 创建第一个实例,会输出"Singleton instance created"
instance1 = Singleton.get_instance()

# 创建第二个实例,不会再次输出"Singleton instance created"
instance2 = Singleton.get_instance()

print(instance1 is instance2) # 输出:True,说明两个实例是同一个实例
# 原理:类的绑定方法是第二种实例化对象的方式,
# 第一次实例化的对象保存成类的数据属性 _instance,
# 第二次再实例化时,在get_singleton中判断已经有了实例对象,直接返回类的数据属性 _instance

补充:这种方式实现的单例模式有一个明显的bug;bug的根源在于如果用户不通过绑定类的方法实例化对象,而是直接通过类名加括号实例化对象,那这样不再是单利模式了。


【python】进阶之面相对象(四)【重点】
http://example.com/2024/03/18/604python进阶之面相对象(四)/
作者
Wangxiaowang
发布于
2024年3月18日
许可协议