Python基础语法
◀▶ Python
变量命名规范
★ 类属性命名规范
# 类属性通常采用大写字母、下划线分隔的方式命名,遵循以下规范:
1. 如果类属性是常量,通常使用全大写的字母表示,多个单词之间用下划线分隔,例如:MAX_SIZE。
2. 如果类属性表示一个布尔值或状态,通常使用is或has开头,例如:is_running、has_finished。
3. 如果类属性表示一个私有属性,通常在属性名前加上一个下划线,例如:_count。
4. 如果类属性表示一个类方法或静态方法,通常使用驼峰式命名法,例如:getMaxSize()。
5. 如果类属性表示一个类变量或实例变量,通常使用小写字母、下划线分隔的方式命名,例如:instance_count。
★ 私有化命名规范
命名格式 | 说明 |
---|---|
xx | 公有变量; 没有任何前缀的变量被视为"公有"的变量. 在 Python 中,并没有像其他编程语言那样显式定义"公有变量"。Python 中的变量默认是可见的 |
_x | 单前置下划线,使用"from somemodule import *" 的方式从一个包导入所有资源, _x不会被导入 但通过 "from somemodule import _x"的方式是可以导入的; 对象属性名以单下划线开头,表示该属性是“受保护的”,通常,这种命名方式用于防止属性被外部直接访问;这不是语法上的要求,而是一种编程的约定,所以实质上依然可以通过 对象._x 的方式访问该属性;一般 在使用IDE软件编写代码是不会提示该属性 |
__xx | 双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(本质是名字重整为"_类名__xx"所以访问不到); 一般用于定义类的私有属性 |
__xx__ |
双前后下划线,用户名字空间的魔法对象或属性 |
xx_ | 单后置下划线,用于避免与Python关键词的冲突 |
◀▶ 环境变量
-
__name__
模块是对象,并且每个模块都有一个内置属性__name__。 当一个模块被直接运行的时候,该模块__name__的值就等于缺省的'__main__'。 如果一个模块被import ,那么这个被引入模块__name__的值就等于该模块名,也就是文件名去掉py扩展名的部分。 也就是说__name__的值表明了当前py文件调用的方式,因此可以用if __name__ == '__main__'来判断是否是在直接运行该.py文件。
-
sys.path
当我们导入一个模块时:import xxx,默认情况下python解析器会搜索当前目录、已安装的内置模块和第三方模块,搜索路径存放在sys模块的path中(sys.path 返回的是一个列表! ): 当我们要添加自己的搜索目录时,可以通过列表的append()方法; 对于模块和自己写的脚本不在同一个目录下,在脚本开头加sys.path.append('模块地址')或sys.path.insert(0,'模块地址')
◀▶ 正则表达式
★ 字符匹配
-
单字符匹配
字符 功能 . 匹配任意1个字符(除了\n) [ ] 匹配[ ]中列举的字符 \d 匹配数字,即0-9 \D 匹配非数字,即不是数字 \s 匹配空白,即 空格,tab键 \S 匹配非空白 \w 匹配单词字符,即a-z、A-Z、0-9、_ \W 匹配非单词字符 -
多字符匹配
字符 功能 * 匹配前一个字符出现0次或者无限次,即可有可无 + 匹配前一个字符出现1次或者无限次,即至少有1次 ? 匹配前一个字符出现1次或者0次,即要么有1次,要么没有 匹配前一个字符出现m次 匹配前一个字符出现从m到n次 -
匹配开头结尾
字符 功能 ^ 匹配字符串开头 $ 匹配字符串结尾 -
匹配分组
字符 功能 | 匹配左右任意一个表达式 (ab) 将括号中字符作为一个分组 \num
引用分组num匹配到的字符串 (?P)
分组起别名 (?P=name) 引用别名为name分组匹配到的字符串 -
基本使用
-
案例1
import re ret = re.match("([^-]*)-(\d+)","010-12345678") print(ret.group()) print(ret.group(1)) print(ret.group(2)) # 运行结果: '010-12345678' '010' '12345678'
-
案例2
import re labels = ["<html><h1>www.itcast.cn</h1></html>", "<html><h1>www.itcast.cn</h2></html>"] for label in labels: ret = re.match(r"<(\w*)><(\w*)>.*</\2></\1>", label) if ret: print("%s 是符合要求的标签" % ret.group()) else: print("%s 不符合要求" % label) # 运行结果: <html><h1>www.itcast.cn</h1></html> 是符合要求的标签 <html><h1>www.itcast.cn</h2></html> 不符合要求
-
案例3
import re ret = re.match(r"<(?P<name1>\w*)><(?P<name2>\w*)>.*</(?P=name2)></(?P=name1)>", "<html><h1>www.itcast.cn</h1></html>") print(ret.group()) # 运行结果: '<html><h1>www.itcast.cn</h1></html>'
-
★ re
高级用法
-
语法规则
- match(pattern,string): 匹配字符串的开头
pattern: 正则表达式
string: 要进行匹配的字符串
返回一个匹配对象 - search(pattern, string): 匹配字符串的任意位置
pattern: 正则表达式
string: 要进行匹配的字符串
返回一个匹配对象 - findall(pattern, string): 匹配所有符合条件的内容
pattern: 正则表达式
string:要进行匹配的字符串
返回所有符合条件的内容的列表 - sub(pattern, repl, string, count=0): 替换匹配到的内容
pattern: 正则表达式
repl:要替换成的内容
string:要进行替换的字符串
count:要替换的次数,默认为0,表示无限次
返回替换后的字符串 - split(pattern, string[,maxsplit]): 将匹配到的内容作为分割字符进行字符串的分割
pattern:正则表达式
string:要进行分割的字符串
maxsplit:最大分割次数
返回分割后的字符串列表
- match(pattern,string): 匹配字符串的开头
-
案例演示
-
search
使用#coding=utf-8 import re ret = re.search(r"\d+", "阅读次数为 9999") ret.group() 运行结果: '9999'
-
findall
使用#coding=utf-8 import re ret = re.findall(r"\d+", "python = 9999, c = 7890, c++ = 12345") print(ret) 运行结果: ['9999', '7890', '12345']
-
sub
使用#coding=utf-8 eg1: import re ret = re.sub(r"\d+", '998', "python = 997") print(ret) 运行结果: python = 998 eg2: #coding=utf-8 import re def add(temp): strNum = temp.group() num = int(strNum) + 1 return str(num) ret = re.sub(r"\d+", add, "python = 997") print(ret) ret = re.sub(r"\d+", add, "python = 99") print(ret) 运行结果: python = 998 python = 100
-
-
贪婪与非贪婪
-
基本概念
Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符; 非贪婪则相反,总是尝试匹配尽可能少的字符。在"*","?","+","{m,n}"后面加上?,使贪婪变成非贪婪。
-
案例演示
>>> re.match(r"aa(\d+)","aa2343ddd").group(1) '2343' >>> re.match(r"aa(\d+?)","aa2343ddd").group(1) '2' >>> re.match(r"aa(\d+)ddd","aa2343ddd").group(1) '2343' >>> re.match(r"aa(\d+?)ddd","aa2343ddd").group(1) '2343'
-
-
r
的作用-
使用演示
>>> mm = "c:\\a\\b\\c" >>> mm 'c:\\a\\b\\c' >>> print(mm) c:\a\b\c >>> re.match("c:\\\\",mm).group() 'c:\\' >>> ret = re.match("c:\\\\",mm).group() >>> print(ret) c:\ >>> ret = re.match("c:\\\\a",mm).group() >>> print(ret) c:\a >>> ret = re.match(r"c:\\a",mm).group() >>> print(ret) c:\a >>> ret = re.match(r"c:\a",mm).group() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'NoneType' object has no attribute 'group' >>>
-
使用总结
Python中字符串前面加上 r 表示原生字符串, 与大多数编程语言相同,正则表达式里使用"\"作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符"\",那么使用编程语言表示的正则表达式里将需要4个反斜杠"\\":前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。 Python里的原生字符串很好地解决了这个问题,有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。
-
◀▶ 函数
★ 参数的传入顺序
必备参数 => 默认参数 => 不定长参数 => 关键字参数
-
案例
# 函数传入参数顺序 必备 默认 不定长 def double_sum(x, y=88, *args, **kwargs): print(x) print(y) print(args) print(kwargs) double_sum(10, 20, 30, 40, name=10)
-
执行结果
10 20 (30, 40) {'name': 10}
★ 参数传入的特殊情况
如果很多个值都是不定长参数,那么这种情况下,可以将缺省参数放到 *args的后面, 但如果有**kwargs的话,**kwargs必须是最后的(python3环境下)
-
案例
def sum_nums_3(a, *args, b=22, c=33, **kwargs): print(a) print(b) print(c) print(args) print(kwargs) sum_nums_3(100, 200, 300, 400, 500, 600, 700, b=1, c=2, mm=800, nn=900)
-
执行结果
100 1 2 (200, 300, 400, 500, 600, 700) {'mm': 800, 'nn': 900}
★ 函数/类方法 形式参数为缺省参数的情况
-
背景介绍
函数/类方法 的形式参数已设置缺省参数,且每次调用函数未对形式参数的缺省值进行重新赋值 形式参数的缺省参数为可变参数;在每次调用该函数时,该形式参数不会被重新赋予函数定义时设置的缺省值,而是延续该函数上次被调用结束后的值,即该形式参数每次被调用后不会释放内存.
-
代码示例
def sum(a, b, c=[1]): print("c的初始值:%s" %c) print("c的初始地址:",id(c)) c += [1] print("c加一后的地址:",id(c)) print("求和值为:%s"% ([a]+[b]+[c])) print("求和结束后C的值%s\n"%c) sum(2,3) sum(4,5)
-
执行结果
c的初始值:[1] c的初始地址: 2308199479560 c加一后的地址: 2308199479560 求和值为:[2, 3, [1, 1]] 求和结束后C的值[1, 1] c的初始值:[1, 1] c的初始地址: 2308199479560 c加一后的地址: 2308199479560 求和值为:[4, 5, [1, 1, 1]] 求和结束后C的值[1, 1, 1]
-
总结
函数的调用可以理解为 将函数对象浅拷贝一份到内存空间,当函数执行完成后cpu将浅拷贝的数据销毁,如果函数体内有可变类型数据变量,直接修改该可变数据变量(即内存级修改,非重新赋值)会导致下次调用该函数时,此变量的值是前一次调用后的值
★ 参数描述的语法格式说明
apply_async(func[, args[, kwds]])
Process([group [, target [, name [, args [, kwargs]]]]])
"(" : 表示是必传参数,
"[" : 表示可选参数,即已配置缺省参数值
"[, 参数名称" : 表示非第一参数,使用该参数需从第一个依次填写参数,或者使用参数名称赋值(常用) args=(1,)
◀▶ 面向对象
★ 静态方法、实例方法、类方法
项目 | 操作对象 | 调用方式 |
---|---|---|
静态方法 | 既不操作类也不操作实例对象 | 类或实例对象 |
实例方法 | 操作实例属性 | 实例对象 |
类方法 | 操作类属性 | 类或实例对象 |
★ python
私有方法和私有属性理解
-
规律总结
1.私有的属性,不能通过对象直接访问,但是可以通过方法访问; 本质是私有属性名被重整(在私有属性名前追加"_类名",而使用方法访问时会自动补全这个前缀) 2.私有的方法,不能通过对象直接访问(私有方法名重整) 3.私有的属性、方法,不会被子类继承,也不能被直接访问
-
说明
1.私有化本质上就是名字重整 2.一般情况下,私有的属性、方法都是不对外公布的,往往用来做内部的事情,起到安全的作用; 如果子类需要用到父类的私有属性和方法, 可以在父类中定义公有方法,在子类中间接的访问父类的私有方法、属性
★ 类的常见魔法方法
-
__doc__
类的描述信息class Foo: """ 描述类信息,这是用于看片的神器 """ def func(self): pass print(Foo.__doc__) """ <程序执行结果>: 描述类信息,这是用于看片的神器 """
-
__module__
当前操作的对象在那个模块 -
__class__
当前操作的对象在那个模块from test import Person obj = Person() print(obj.__module__) print(obj.__class__) """ <程序执行结果>: test <class 'test.Person'> """
-
__del__
当对象在内存中被释放时自动触发执行class Foo: def __del__(self): print(f"{self} 对象被删除") f1 = Foo() del f1 """ <程序执行结果>: <__main__.Foo object at 0x7f96e8125d90> 对象被删除 """
-
__init__
初始化对象时触发执行 -
__call__
对象后面加括号时触发执行class Foo: def __init__(self): print('__init__ 被调用') def __call__(self, *args, **kwargs): print('__call__ 被调用') f1 = Foo() print("--------------") f1() """ <程序执行结果>: __init__ 被调用 -------------- __call__ 被调用 """
-
__dict__
查看对象的属性时触发执行class Foo: COUNT = 0 def __init__(self, name): self.name = name def say_hello(self): print(f"hello {self.name} ") f1 = Foo("demo") # 获取对象f1的所有属性 print(f1.__dict__) # 获取对象f1类的所有属性 print(f1.__class__.__dict__) """ <程序执行结果>: {'name': 'demo'} {'__module__': '__main__', 'COUNT': 0, '__init__': <function Foo.__init__ at 0x7ff3c00e5c20>, 'say_hello': <function Foo.say_hello at 0x7ff3e00ca440>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None} """
-
__str__
如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。class Foo1: pass class Foo2: def __str__(self): return 'Foo2' f1 = Foo1() f2 = Foo2() print(f1) print(f2) """ <程序执行结果>: <__main__.Foo1 object at 0x7fe2b000f2d0> Foo2 """
-
__xxitem__
对象索引操作class Foo(object): def __getitem__(self, key): print('__getitem__', key) def __setitem__(self, key, value): print('__setitem__', key, value) def __delitem__(self, key): print('__delitem__', key) obj = Foo() # 对象获取操作,触发 __getitem__ result = obj['k1'] # 对象设置操作,触发 __setitem__ obj['k2'] = 'laowang' # 对象删除操作,触发 __delitem__ del obj['k1'] """ <程序执行结果>: __getitem__ k1 __setitem__ k2 laowang __delitem__ k1 """
-
slice
对象切片操作""" Python3 的官方文档可知,Python3中的切片的魔术方法不再是Python2中的getslice(), setslice()和delslice(),而是借助 slice 类整合到了getitem(),setitem()和 delitem()中。 """ class Foo(object): def __getitem__(self, index): if isinstance(index, slice): print(f"【slice-get】>> start: {index.start}, stop: {index.stop}, step: {index.step}") def __setitem__(self, index, value): if isinstance(index, slice): print(f"【slice-set】>> start: {index.start}, stop: {index.stop}, step: {index.step}") print(f"【slice-set】>> The value is:", value) def __delitem__(self, index): if isinstance(index, slice): print(f"【slice-del】>> start: {index.start}, stop: {index.stop}, step: {index.step}") obj = Foo() # 对象切片获取操作 obj[-1:1] # 对象切片修改操作 obj[-1:10:1] = [11, 22, 33, 44] # 对象切片删除操作 del obj[-1:10:2] """ <程序执行结果>: 【slice-get】>> start: -1, stop: 1, step: None 【slice-set】>> start: -1, stop: 10, step: 1 【slice-set】>> The value is: [11, 22, 33, 44] 【slice-del】>> start: -1, stop: 10, step: 2 """
★ property
属性
-
基本定义
一种用起来像是使用的实例属性一样的特殊属性,可以对应于某个方法
-
property
属性的两种方式- 装饰器 => 在方法上应用装饰器
- 类属性 => 在类中定义值为property对象的类属性
-
装饰器方式
-
代码示例
class Goods(object): def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 @property def price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price # 新式类独有 @price.setter def price(self, value): self.original_price = value # 新式类独有 @price.deleter def price(self): del self.original_price obj = Goods() obj.price # 获取商品价格 obj.price = 200 # 修改商品原价 del obj.price # 删除商品原价
-
示例说明
1.定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数 2.经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法 3.新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法;我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
-
实例应用
class Pager: def __init__(self, current_page): # 用户当前请求的页码(第一页、第二页...) self.current_page = current_page # 每页默认显示10条数据 self.per_items = 10 @property def start(self): val = (self.current_page - 1) * self.per_items return val @property def end(self): val = self.current_page * self.per_items return val p = Pager(1) p.start # 就是起始值,即:m p.end # 就是结束值,即:n # Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。
-
-
类属性方式
-
代码示例
class Goods(object): def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 def get_price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price def set_price(self, value): self.original_price = value def del_price(self): del self.original_price PRICE = property(get_price, set_price, del_price, '价格属性描述...') obj = Goods() obj.PRICE # 获取商品价格 obj.PRICE = 200 # 修改商品原价 del obj.PRICE # 删除商品原价
-
示例说明
1.当使用类属性的方式创建property属性时,经典类 和 新式类 无区别 2.property方法中有个四个参数 第一个参数是方法名,调用 对象.属性 时自动触发执行方法 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息 3.由于 类属性方式 创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
-
★ __dict__
与 dir()
Python下万物皆对象,每个对象都有多个属性(attribute),Python对属性有一套统一的管理方案。
1. dir(对象名)是一个函数,返回的是list;
2. __dict__是一个属性,返回的是dictionary,键为属性名(这里的属性包含方法),值为属性值;
3. dir()用来寻找一个对象的所有属性,包括__dict__中的属性,__dict__是dir()的子集;
4. __dir__是魔法方法, 实例.__dir__() 与 dir(实例) 等效
* 并不是所有对象都拥有__dict__属性。许多内建类型就没有__dict__属性,如list,此时就需要用dir()来列出对象的所有属性。
* 实例的__dict__仅存储与该实例相关的实例属性(不含实例方法,因为实例方法是保存在类里面的),正是因为实例的__dict__属性,每个实例的实例属性才会互不影响。
*类的__dict__存储所有实例共享的变量和函数(类属性,方法等),类的__dict__并不包含其父类的属性。
*dir(对象名)函数会自动寻找一个对象的所有属性和方法(包含私有方法),包括__dict__中的属性。
*__dict__是dir()的子集,dir()包含__dict__中的属性。
◀▶ 模块导入
★ 方式一:from xx import yy
-
导入方式
from info import redis_store
-
使用说明
【理解】: 相当于在本地定义一个变量redis_store 指向info模块中redis_store的值,对本地的redis_stone直接进行赋值,只会修改本地变量redis_store的指向,并不会修改info模块中redis_store的值;这里如果info模块中redis_store是可变类型数据(如list()),在本地中通过from info import redis_store导入后直接对其指向的值进行修改(如redis_store.append()),才会修改info模块中redis_store的值
★ 方式二:import xx
-
导入方式
import info info.redis_store
-
使用说明
【理解】: 使用 import info,相当于在本地定义一个变量info 指向模块info,但是它保持着自已的名字空间,这就是为什么你需要使用模块名来访问它的函数或属性: module.function 的原因 说明 导包或者导入包内变量,python解释器会将整个文件运行一次
★ 方式三:__import__("xx")
-
导入方式
__import__("xx")
-
使用说明
【理解】:返回模块名叫info的模块(模块也是对象)
★ 方式四:import_module(module_path)
-
文件结构
imp │───demo.py │ └───apps └───api └───urls.py
-
代码实例
-
urls.py
urlpatterns = [] app_name = "api"
-
demo.py
# 1. 基础导入模式 from apps.api import urls print(urls.urlpatterns, urls.app_name) # 2. 字符串导入模式 import importlib module_path = 'apps.api.urls' module = importlib.import_module(module_path) print(module.urlpatterns, module.app_name) """ 执行结果: [] api [] api """
-
★ reload
重复导入
-
背景说明
模块被导入后,"import module/from xx import yy" 不能重新导入模块,重新导入需要使用"reload", 也就是说如果对被导入的包中数据进行修改,即便使用import重新导入,正在运行的代码使用的仍然是原来包中的数据。(导包实际上是将硬盘中的数据加载到内存,reload 可以将修改的包数据重新加载到内存中)
-
代码实例
-
test.py
a = 10
-
main1.py
import test import time while True: time.sleep(1) import test print(test.a) """ 修改test.py 文件中变量a 的值并保存 <程序执行结果>: 10 10 10 10 """
-
main2.py
import importlib import test import time while True: time.sleep(1) # 重新导入test importlib.reload(test) print(test.a) """ 修改test.py 文件中变量a 的值并保存 <程序执行结果>: 10 10 10 10 100 100 2000 """
-
★ pyc
文件作用说明
在这个过程中这些字节码都是在内存中的,众所周知Python的运行性能不如编译性语言(比如C语言,JAVA …),所以Python在程序执行结束后会把字节码写入到硬盘中,保存为.pyc文件,目的是下一次再执行 `python xxx.py`程序时,Python会先在目录下找`xxx.pyc`文件来执行,因为.pyc文件里保存的是字节码,所以就节省了Python解析器把`xxx.py`翻译成字节码的时间,所以就提高了性能。
- 详细参考 什么是pyc文件
◀▶ 编码
★ unicode
编码
-
示例
# 将字符串 "你" 进行 unicode 编码 print("你".encode("unicode_escape"))
-
执行结果
b'\\u4f60'
★ 字符与 unicode
编码互转
-
示例
# ord: 它以一个字符(长度为1的字符串)作为参数,返回对应的 Unicode 数值 # chr: 它接受一个整数参数,并返回一个对应的字符。它的作用是将整数转换为相应的Unicode字符 print(ord("你")) print(chr(20320))
-
执行结果
20320 你
◀▶ 进制转换
★ 基本进制转换
-
转换语法
bin(): 将整数转换为二进制字符串。 oct(): 将整数转换为八进制字符串。 hex(): 将整数转换为十六进制字符串。 int(): 将字符串表示的其他进制数转换为十进制整数。
-
示例
# 将一个数字从十进制转换为八进制 print(oct(42)) # 将一个数字从十进制转换为十六进制: print(hex(42)) # 将十六进制的 "4f60" 转换为 十进制 print(int("4f60", 16)) # 将八进制的 "11" 转换为 十进制 print(int("11", 8))
-
执行结果
0o52 0x2a 20320 9
◀▶ 单例模式
★ 单例模式基本介绍
单例模式是一种设计模式,用于确保一个类只有一个实例,并提供全局访问点以获取该实例。它是一种创建型模式,通常用于需要严格控制某个类的实例数量的情况。单例模式确保一个类在整个应用程序生命周期中只有一个实例,因此可以节省系统资源,同时提供了一个集中的访问点,以便在需要时获取该实例。
★ 实现单例的几种方式:
- 模块导入
- 添加装饰器(类装饰器和函数装饰器)
- 重写new方法
- 元类继承
★ 方式一: 模块导入
-
文件结构
单例 ├───file1.py ├───file2.py ├───readme ├───单例实现1_模块导入.py └───模块导入实现单例测试.py
-
单例实现1_模块导入.py
""" 模块导入实现单例模式步骤: 1. 在模块中定义类 2. 实例化类并返回 3. 在其他文件中导入实例对象使用, 每个文件导入的对象实际是同一个 """ class Singleton: def __init__(self, name): self.name = name def do_something(self): pass singleton = Singleton('模块单例') # 在其他py文件中 # from my_singleton import singleton
-
file1.py
from 单例实现1_模块导入 import singleton print(singleton)
-
file2.py
from 单例实现1_模块导入 import singleton print(singleton)
-
模块导入实现单例测试.py
import file1 import file2 print(file1.singleton is file2.singleton)
-
执行结果
<单例实现1_模块导入.Singleton object at 0x0000021B2B81F400> <单例实现1_模块导入.Singleton object at 0x0000021B2B81F400> True
★ 方式二: 装饰器
-
单例实现2_装饰器.py
# -------------------函数装饰器--------------------------- def Singleton1(cls): instance = {} def _singleton_wrapper(*args, **kargs): if cls not in instance: instance[cls] = cls(*args, **kargs) return instance[cls] return _singleton_wrapper # -------------------类装饰器--------------------------- class Singleton2: def __init__(self, cls): self.cls = cls self._instance = None def __call__(self, *args, **kwargs): if not self._instance: self._instance = self.cls(*args, **kwargs) return self._instance # SingletonTest = Singleton1(SingletonTest) =_singleton_wrapper # SingletonTest = Singleton2(SingletonTest) = Singleton2实例对象 @Singleton1 class SingletonTest(object): def __init__(self, name): print(">>> 初始化 <<<") self.name = name s1 = SingletonTest('s1') s2 = SingletonTest('s2') print(s1, s2) print(s1 is s2)
-
执行结果
>>> 初始化 <<< <__main__.SingletonTest object at 0x000001E6A2FF73D0> <__main__.SingletonTest object at 0x000001E6A2FF73D0> True
★ 方式三: 重写new方法
-
单例实现3_重写new方法.py
class Singleton(object): def __new__(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): Singleton._init_flag = True Singleton._instance = super().__new__(cls) return Singleton._instance def __init__(self, name): if not hasattr(Singleton, "_init"): Singleton._init = True print(">>> 初始化 <<<") self.name = name s1 = Singleton('s1') s2 = Singleton('s2') print(s1, s2) print(s1 is s2)
-
执行结果
>>> 初始化 <<< <__main__.Singleton object at 0x0000016663140760> <__main__.Singleton object at 0x0000016663140760> True
★ 方式四: 元类继承
-
单例实现4_元类继承.py
class Singleton(type): def __call__(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): # cls 是 Singleton 创建的类 Singleton._instance = cls.__new__(cls, *args, **kwargs) cls.__init__(Singleton._instance, *args, **kwargs) return Singleton._instance class SingletonTest(metaclass=Singleton): pass class A(SingletonTest): def __init__(self, name): print(">>> 初始化 <<<") self.name = name s1 = A('s1') s2 = A('s2') print(s1, s2) print(s1 is s2)
-
执行结果
>>> 初始化 <<< <__main__.A object at 0x000001687C79D5E0> <__main__.A object at 0x000001687C79D5E0> True
◀▶ 深浅拷贝
★ 浅拷贝
-
使用方式
import copy # 浅拷贝 copy.copy()
-
拷贝原则
- 对可变类型对象进行浅拷贝, 只做顶层拷贝
- 对不可变类型对象进行浅拷贝, 那么不拷贝
★ 深拷贝
-
使用方式
import copy # 深拷贝 copy.deepcopy()
-
拷贝原则
- 对可变类型对象进行深拷贝, 除了顶层拷贝,还会对该对象的子元素进行深拷贝
- 对不可变类型对象进行深拷贝(递归思维,以递归出口为最终 是否执行拷贝操作的依据)
- 如果该对象存在可变类型数据的子元素, 那么会顶层拷贝, 并且对其子元素进行深拷贝
- 如果该对象不存在可变数据对象, 那么不拷贝
★ 深浅拷贝总结
浅拷贝是顶层拷贝,深拷贝是递归拷贝; 对于不可变数据,如果该对象不存在可变数据对象, 那么深浅拷均不拷贝