不懂性能优化,再强的计算机也白玩

不懂性能优化,再强的计算机也白玩

 

Python 的优秀有目共睹,不过说的性能,还真比不了 Java、C、Go,有没有提升性能的技巧或方法呢?今天我们一起学习下提升 Python 性能的方式方法,那还等啥,来吧

局部变量更好
记得刚开始学习 C 语言时,对先定义再使用,感到很痛苦,经常因为声明问题编译不通

现在用 Python,变量随用随定义,爽到不行

不过我却养成了先定义在使用的习惯,例如:

a = None  # 可以不写
if some_condition:
a = 10
else:
a = 0
虽然 a = None 可以不写,但是还是习惯性的写处理做变量声明,类似 C 中的 int a;

这样的习惯,促使我在写代码之前,会先考虑如何将会用到的变量,从而,对变量的使用范围做出了严格的限定,能是局部的,*不全局

而这个习惯提高程序的性能同时,还有其他好处

局部变量查找的速度更快,因为 Python 是从代码块里,向外查找变量的,里面找不到,才会去外面找,*后才是全局变量,其他语言也一样

局部变量更节省内存,当代码块被执行,代码块中声明的局部变量所占用的内存就会被释放

让代码更简洁,更易懂,比如可以用局部变量为冗长的命名空间中的变量起别名,如 ls = os.linesp,后面就在可以用 ls 简洁表示 os.linesp 了

函数虽好 尽量少调
函数是个伟大的发明,将可以被重复使用的过程集中起来,方便反复调用,而且函数的出现,使递归得以实现

不过,调用函数的时间成本比一般语句高的多,这是因为,函数的调用需要计算机做更多的调度协调工作

因此,应该尽量减少调用函数,特别是在大的循环中,更要注意

下面,列出几个典型例子,在这些情况下,可以不用调用函数

使用 isinstance 代替 type,因为 Python 是面向对象语言,支持对象的继承,isinstance 可以直接检测对象的基类,不会像 type 一样对对象做全面的检测,会比 isinstance 做更多的函数调用

避免在循环判断中,调用函数

# 每次循环都需要计算 a 的长度
while i < len(a):
statement

# 先计算出 a 的程度,避免每次循环计算
length = len(a)
while i < length:
statement
如果模块 X 中有个 Y 对象或函数,那么*后这样引入 from X import Y,而不是 import X,后者每次使用 Y 时,需要通过 X.Y 的方式,比直接使用 Y 多了一次函数调用

映射优于判断
在《编程珠玑 第二版》 *章开篇中,描述了一个需求,需要对记录了一千多万行 7 位数据的文件中的数据排序,而且需要在很短时间内,在只使用 1M 内存的条件下完成

对于使用着现代计算机的我们来说,简直不可思议

一方面,现在的计算机动辄好几 G,几核,性能超强

另一方面,随便一个编程语言都有内置的高效排序算法

但在当时,计算机*大内存才不过几兆(M)!

你可能不会相信,就在当时的条件下,能在数十秒内完成吧

核心原理就是借用索引,来表示数值,比如 1000 是否存在,就看数组中索引为 1000 的值是否为 1,不存在则为 0,*后只需要遍历一遍数组(书上实际应该的是字符串,一个字节索引表示一个数字),就能得到数据排序了

显而易见,相比判断,索引效率更高

例如,应该尽量避免*种写法,而用第二种:

# 判断并赋值
if a == 1:
b = 10
elif a == 2:
b = 20

# 利用索引,直接存值,性能更好
d = {1:10,2:20,…}
b = d[a]
迭代器的性能更优
Python 中有很多迭代器,方便我们做各种循环

对于可以支持迭代器的对象,使用迭代器获取元素,比用索引获取元素的效率更高

例如:

a = [1, 2, 3]

for i in range(len(a)):  # 使用索引获取元素
print(a[i])

for item in a:  # 使用迭代器获取元素
print(item)
上面代码中,直接使用迭代器的效率更高

如果*开始接触的语言是 Python,应该比较习惯直接使用迭代器,如果从其他语言转过来,可能更习惯使用索引

另外,如果需要在循环中得到每个元素的索引,可以通过一个索引计数器来实现:

a = [‘a’, ‘b’, ‘c’]
index = 0  # 初始化索引
for item in a:
print(index, item)
index += 1  # 递增索引值
延迟享受是美德
曾经有个著名的心理学实验 —— 棉花糖实验,测试一群小孩子的延迟满足能力,*终的结论是:延迟满足能力强的孩子,未来成功的机率更高

这虽然是对人的测试,但对计算机也适用,不过,背后的逻辑有些不同

对计算机而言,没必要将还用不到的内容加载到内存里,内存就好比我们的工作太,如果放了太的的东西,查找就比较困难,从而影响工作效率

Python 中提供多种延迟加载的功能,其中生成器是个典型的应用

以 list 容器为例,在使用该容器迭代一组数据时,必须事先将所有数据存储到容器中,才能开始迭代

而 生成器 却不同,它可以实现在迭代的同时生成元素,也就是不是一次性生成所有元素,只有在使用时才会生成

下面是个数字 生成器 的例子

def initNum():
for i in range(10):
yield i

gen = initNum()  # 得到生成器

for item in gen: # 遍历生成器
print(item)

调用 initNum 会返回一个生成器,在 for 循环中,或者调用 next(等同于 gen.__next__()) 时才会生成下一个数字,在调用之前,不会生成所有的数据

生成器 占用的资源更少,意味着效率更高

先编译再调用
网络上有很多描述产品经理和研发直接矛盾的段子,让人啼笑皆非

*主要的原因是,需求的不确定性和研发需要的确定性是相互矛盾的

面对不到变动的需求,研发需要不断调整,因此效率不会高

相同的道理,计算机执行已经编译好的程序,比执行边解析边执行的程序效率高很多

例如,C 的程序运行效率会更高,因为需要对 C 代码,编译后才能运行

我们在享受 Python 这类动态语言带来的便利性同时,尽量让程序执行已经编译好的代码,以便提升性能

例如:

code = “for i in range(3):\n\tprint(i)”

exec(code)  # 直接执行

c = compile(code, “”, “exec”)
exec(c)  # 编译后再执行
更常见的是正则表达式

line = “name\t\tage\tschool”

reObj = re.compile(“\t+”)  # 编译
reObj.split(line)  # 使用编译后的性能更好
编译后不仅性能更好,而且在可以反复使用时,进一步提供效率

模块化
我们人类在不断理解这个世界的同时,产生了大量的信息和知识,一直无论哪个人,究其一生也无法掌握所有的知识

于是科学发展为分科之学,将知识分门别类,以便不同的人掌握了解他所关注的一点

知识是这样,我们的合作也是如此,没有一个可以做所有的事情,需要多人相互配合,各负其责

计算机是我们大脑的延伸,是用我们的思考方式、思维习惯制造的

面对大规模代码时,需要对代码进行分门别类,不仅方便我们人类查看,还能提升程序执行效率,可以在需要时,才去加载和执行模块和方法

例如:

# module1.py

def fun(alist):
for item in alist:
do some thing
# 测试用
a = [1, 2, 3]
fun(a)
定义了函数 fun,之后测试了下,如果其他代码引用了 fun: from module1 import fun

这测试用的代码就会被执行

好的方式是,将测试用代码封装起来:

# module1.py

def fun(alist):
for item in alist:
do some thing

if __name__ == “__main__”:
# 测试用
a = [1, 2, 3]
fun(a)
这样就可以避免测试用代码的无意义执行,从而提升运行效率

总结
虽然现在的计算机性能很强,编程语言提高的功能很多,为我们提高了*大的便利,但是良好的编程习惯和编码规范仍然是很重要的,首先代码更多的时候是写给人看的,另外再强的计算机性能,也解决不了思维懒惰者的低效代码,就好比 你永远叫不醒一个装睡的人 一样。

业精于勤,在日常的工作和编程中,多学多练多思考,会使编程水平的提升事半功倍,看似不起眼的小技巧,处处做好了,将带来巨大的差异,这就是与高手直接的差距

期望今天分享的一点小技巧能给您一丝启发,让您在通往高手的道路上更加顺畅,比心!

参考
https://www.tutorialdocs.com/article/7-habits-to-improve-python-programs.html

https://stackoverflow.com/questions/1549801/what-are-the-differences-between-type-and-isinstance

http://c.biancheng.net/view/2393.html

https://www.cnblogs.com/nomorewzx/p/4203829.html

https://book.douban.com/subject/3227098/

https://baike.baidu.com/item/棉花糖实验/15659236

记一次线上问题排查与解决

记一次线上问题排查与解决

 

*近开发中遇到个小问题,因为业务上的设计存在问题,导致数据库表总是被锁,而且是不定期的锁定,导致服务器运行异常,*后经过排查原因是多线程同时更新同一表中同一条记录导致问题。今天就来跟大家说说该如何避免这种问题。

问题描述
*近因为公司业务需要,产品设计了一套业务系统,据说会有很多内部和外部人员使用,拿到系统说明我们研发部门拼命加班赶时间,经历了两个月的后终于把系统上线运行。

刚开始用的人少,并没有出现什么问题,感觉系统还是很稳定,随着后来用的人越来越多,系统就开始出现一些莫名其妙的问题,其中就有某些业务信息在更新的时候总是报错,查日志就发现是表记录被锁定导致更新失败。

找到错误问题后我们就开始一遍遍的翻日志,各种分析查找到底是什么原因导致了表记录被锁。*后发现这个表的状态字段,存在多个接口方法同时更新的情况,而且经常是同时操作的。也就是数据库中存在多个会话同时操作同一表中同一行记录,从而导致表记录被锁。

问题分析
那定位到问题,要如何解决呢?这里我们先了解两个名词:

悲观锁(Pessimistic Lock):简单解释就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在修改数据的时候都会上锁,这样别人想修改这个数据就会等待一直到它能拿到锁。

乐观锁(Optimistic Lock):这个正好相反,就是很乐观,每次去修改数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。

通过这两种方式就能解决问题,不过选哪种比较好,我们来简单分析下:

悲观锁 通过 “select …… for update” 实现,就是在更新表前先对这条记录进行上锁,然后下面再执行更新语句。不过这种方式有些太重,毕竟加锁还是需要很大时间成本的,不符合业务的需要,直接pass掉。

乐观锁 相对就要轻量很多,它的主要实现就是通过在表中多增加一个记录版本的字段,比如 version 。然后每次查询记录要更新时,where 后面都要加上 version=? ,这样当你查询拿到 version 后,如果有其他会话更新了这个字段,那这个 version 就会和你现在拿的不一样,从而会使你这次的更新失效,需要重新获取*新 version 后再次执行 update 语句。

问题解决
好了,经过以上分析,已经有了比较清晰的解决思路,剩下就是码代码了:

/**
* 乐观锁更新
* @param id
* @return
*/
public boolean update(int id){
int cnt = 0;
while (cnt == 0) {
USER user = query(“SELECT * FROM table_user WHERE id = #{id}”, id);
cnt = update(“UPDATE table_user SET version=version + 1, status = 2 WHERE id=#{id} AND version=#{version}”, id, user.version());
if(cnt > 0){
// 返回更新成功
return true;
}
}
return false;
}

 

新年快乐”的多语言版本

新年快乐”的多语言版本

请允许我用21种编程语言,代表 Python技术团队祝大家2021年新年快乐!

Python
print(“Happy New Year!”)
java
System.out.println(“Happy New Year!”);
C
printf(“Happy New Year!”);
C++
cout<<“Happy New Year!”;
C#
System.Console.WriteLine(“Happy New Year!”)
Objective-C
NSLog(@“Happy New Year!”)
QBasic
Print “Happy New Year!”
Asp
Response.Write “Happy New Year!”
PHP
echo “Happy New Year!”;
JS
alert(“Happy New Year!”)
VBScript
MsgBox “Happy New Year!”
go
fmt.Println(“Happy New Year!”)
Delphi
ShowMessage(‘Happy New Year!’);
VB
Print “Happy New Year!”
VC
MessageBox(“Happy New Year!”);
shell
echo Happy New Year!
perl
print ‘Happy New Year!’
nodejs
console.log(“Happy New Year!”)
COBOL
DISPLAY ‘Happy New Year!
AS
Alert.show(“Happy New Year!”);
Foxpro
? 【Happy New Year!】

当代码遇到文学…

当代码遇到文学…

 

一提到程序员,大家的*印象都是性格内向,不善言语,衣服搭配全是格子衫加牛仔裤。实则不然,别看我们整天和机器打交道,一天下来可能也说不了几句话,但我们程序员群体也是人呀,也有七情六欲,也是懂浪漫的,今天派森酱就带你领略下程序员另类的浪漫。

想必大家都给自己的对象写过情诗吧,打开信封的那一刻,她激动又开心的样子一定可爱*了。今天咱们就来看看程序员用代码写给对象的情诗。

0x00
遇见你
生命才在增加
否则只是虚耗

while True:
if meet_you: life++
else: life–
0x01
在 75 亿茫茫人海中
看见你的*眼
就永远爱上了你

while random(0, 7_500_000_000) != you: continue
while True:
love(you)
0x02
自从遇见了你,就不停的想你

void missing_you() {}
for (time = meet_you; ;time++)
missing_you()
0x03
我就在这里,等风也等你

you.come() ? rain.stop() : I.wait()
0x04
爱你一万年

while year <= 10000:
print(“I Love You!”)
year++
0x05
生活中少不了意外
但只要我们共同面对
就能天长地久

try: our_life()
except: face_together()
finally: love_forever()
0x06
我向往明天的生活
可当你离开那一刻
它就停留在了昨天

MyLife = {“I”: “Yesterday”, “You”: “Tomorrow”}
del MyLife[“You”]
MyLife.values()
0x07
我是乘风破浪归来的舵手
却在你寂静深邃的眼波里
弃浆溺亡

Helmsman me = new Helmsman()
while(!me.meet(you)){me.navigate}
me.drown()
0x08
爱你到天荒地老

while(!world.destroy()){
me.loveYou()
}
0x09
夏雨雪,江水竭,冬雷震震,山无陵,天地合,乃敢与君*

if(summer.snow==True
&& river.water==None
&& winter.thunder==True
&& mountain.arris==None
&& sky.height==ground.height):
I.withyou=False
else:
I.withyou=True

弃繁就简!一行代码搞定 Python 日志!

弃繁就简!一行代码搞定 Python 日志!

 

写了这么多年的 Python ,我一直都是使用 Python 自带的 logging 模块来记录日志,每次需要写一些配置将日志输出到不同的位置,设置不同日志输出格式,或者将日志进行分文件和压缩等。这个日志模块没什么问题,直到我无意中发现了一个神器,我才发觉原来记日志可以这么简单的!这个神器就是 loguru 。

安装
这个库的安装方式很简单,直接使用 pip 就可以,我使用 Python 3 版本,安装命令如下:

pip3 install loguru
小试牛刀
安装完毕之后,我们就可以使用了,*简单的使用方式:

from loguru import logger

logger.debug(‘this is a debug message’)

无需任何配置,即取即用。上例是打印一条 debug 级别的日志,输出结果如下:

2021-03-16 22:17:23.640 | DEBUG    | __main__:<module>:8 – this is a debug message

这条输出日志信息包含了日期、时间、日志级别、日志代码行数以及日志内容信息。可以说*基本的内容都囊括了,当然你还可以打印 warning、info、error、critical、success 等级别。输出的日志在 console 中还带有高亮颜色,并且每个级别的日志颜色不一样,简直不要太酷!

日志文件
写文件
在loguru中,输出日志文件只需要一个 add() 函数即可:

logger.add(‘hello.log’)

logger.debug(‘i am in log file’)

这时候,在 console 中会正常打印日志信息,在同级目录下会生成一个日志文件 hello.log ,我们打开日志文件,可以看到内容如下:

2021-03-16 21:20:31.460 | DEBUG    | __main__:<module>:12 – i am in log file

当然,我们还可以加一些参数,来指定文件中日志输出的格式、级别:

log = logger.add(‘world.log’, format=”{time} | {level} | {message}”, level=”INFO”)

logger.debug(‘i am debug message’)
logger.info(‘i am info message’)

对应的文件输出信息如下:

2021-03-16T22:47:53.226998+0800 | INFO | i am info message

我们设置了文件只记录 info 级别的信息,所以 debug 级别的日志信息并没有写入日志文件。

我们也可以给日志文件名称加信息:

logger.add(‘hello_{time}.log’)

上面的代码运行后,会生成一个带时间的日志文件。

停止写入文件
当我们不再需要将日志写入文件时,我们随时可以停止:

id = logger.add(‘world.log’, format=”{time} | {level} | {message}”, level=”INFO”)
logger.info(‘this is a info message’)
logger.remove(id)
logger.info(‘this is another info message’)

add() 方法会返回一个日志文件的 id ,当我们需要停止写入信息时,我们使用 remove() 方法,传入 id ,即可。上面代码运行后,日志文件记录的信息如下:

2021-03-16T22:47:53.227389+0800 | INFO | this is a info message

在调用 remove() 方法后,其后面的日志信息并没有写入日志文件中。

滚动记录日志文件
我们可以配置 rotation 参数,来指定日志文件的生成方式,跟通常的日志记录一样,我们可以设置按照文件大小、时间、日期等来指定生成策略。

# 超过200M就新生成一个文件
logger.add(“size.log”, rotation=”200 MB”)
# 每天中午12点生成一个新文件
logger.add(“time.log”, rotation=”12:00″)
# 一周生成一个新文件
logger.add(“size.log”, rotation=”1 week”)

指定日志文件的有效期
我们还可以通过 retention 参数来指定日志文件的保留时长:

logger.add(“file.log”, retention=”30 days”)

通过上面的配置,可以指定日志文件*多保留30天,30天之前的日志文件就会被清理掉。

配置压缩文件
为了节省空间,我们可能存在压缩日志文件的需求,这个 loguru 也可以实现:

logger.add(“file.log”, compression=”zip”)

通过上面的配置,我们指定了日志文件的压缩格式为 zip 。

异常捕获
loguru 不仅可以记录日志,还可以捕获异常信息,这个 可以帮助我们更好地追溯错误原因。

在 loguru 模块中,我们通常有两种异常捕获方式:通过 catch 装饰器捕获和通过 exception 方法捕获。

catch 装饰器捕获异常
我们来看一个例子:

@logger.catch
def a_function(x):
return 1 / x

a_function(0)

输出信息如下:

021-03-16 23:10:28.124 | ERROR    | __main__:<module>:32 – An error has been caught in function ‘<module>’, process ‘MainProcess’ (25939), thread ‘MainThread’ (140735895298944):
Traceback (most recent call last):
File “/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/pydev/pydevconsole.py”, line 483, in <module>
pydevconsole.start_client(host, port)
│            │            │     └ 62146
│            │            └ ‘127.0.0.1’
│            └ <function start_client at 0x10fd596a8>
└ <module ‘pydevconsole’ from ‘/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/py…

……

> File “/Users/cxhuan/Documents/python_workspace/mypy/loguru/logurustudy.py”, line 32, in <module>
a_function(0)
└ <function a_function at 0x11021e620>
File “/Users/cxhuan/Documents/python_workspace/mypy/loguru/logurustudy.py”, line 30, in a_function
return 1 / x
└ 0
ZeroDivisionError: division by zero
上面的代码中,我特意造了一个 1 除以 0 的异常,我们可以看到日志输出信息非常详细,将每一步调用的错误信息都详细的列出来,并且还把参数的值也打印出来了,还有非常直观的指向性,简直是异常分析神器!

exception 方法捕获异常
我们直接看例子:

def b_function1(x):
try:
return 1 / x
except ZeroDivisionError:
logger.exception(“exception!!!”)

b_function1(0)

运行上面代码,输出信息如下:

2021-03-16 23:16:07.602 | ERROR    | __main__:b_function1:40 – exception!!!
Traceback (most recent call last):
File “/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/pydev/pydevconsole.py”, line 483, in <module>
pydevconsole.start_client(host, port)
│            │            │     └ 62254
│            │            └ ‘127.0.0.1’
│            └ <function start_client at 0x118d216a8>
└ <module ‘pydevconsole’ from ‘/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/py…
File “/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/pydev/pydevconsole.py”, line 411, in start_client
process_exec_queue(interpreter)
│                  └ <_pydev_bundle.pydev_ipython_console.InterpreterInterface object at 0x118d36240>
└ <function process_exec_queue at
0x118d21400>

……

File “/Users/cxhuan/Documents/python_workspace/mypy/loguru/logurustudy.py”, line 42, in <module>
b_function1(0)
└ <function b_function1 at 0x11913b598>
> File “/Users/cxhuan/Documents/python_workspace/mypy/loguru/logurustudy.py”, line 38, in b_function1
return 1 / x
└ 0
ZeroDivisionError: division by zero
同样地,也是很详细和直观地打印了错误详情。

开眼界!Python 遍历文件可以这样做!

开眼界!Python 遍历文件可以这样做!

 

Python 对于文件夹或者文件的遍历一般有两种操作方法,一种是至二级利用其封装好的 walk 方法操作:

import os
for root,dirs,files in os.walk(“/Users/cxhuan/Downloads/globtest/hello”):
for dir in dirs:
print(os.path.join(root, dir))
for file in files:
print(os.path.join(root, file))

上面代码运行结果如下:

/Users/cxhuan/Downloads/globtest/hello/world
/Users/cxhuan/Downloads/globtest/hello/.DS_Store
/Users/cxhuan/Downloads/globtest/hello/hello3.txt
/Users/cxhuan/Downloads/globtest/hello/hello2.txt
/Users/cxhuan/Downloads/globtest/hello/hello1.txt
/Users/cxhuan/Downloads/globtest/hello/world/world1.txt
/Users/cxhuan/Downloads/globtest/hello/world/world3.txt
/Users/cxhuan/Downloads/globtest/hello/world/world2.txt

上述程序,将 os.walk 读取到的所有路径 root 、目录名 dirs 与文件名 files ,也就是三个文件数组利用 foreach 循环输出。join方法就是将其路径与目录名或者文件名连接起来,组成一个完整的目录。

另一种是用递归的思路,写成下面的形式:

import os
files = list()
def dirAll(pathname):
if os.path.exists(pathname):
filelist = os.listdir(pathname)
for f in filelist:
f = os.path.join(pathname, f)
if os.path.isdir(f):
dirAll(f)
else:
dirname = os.path.dirname(f)
baseName = os.path.basename(f)
if dirname.endswith(os.sep):
files.append(dirname+baseName)
else:
files.append(dirname+os.sep+baseName)

dirAll(“/Users/cxhuan/Downloads/globtest/hello”)
for f in files:
print(f)

运行上面代码,得到的结果和上面一样。

这两种方法都没问题,就是写起来比较麻烦,特别是第二种,一不小心还有可能写出 bug 。

今天我们来介绍第三种方法——利用 glob 模块来遍历文件。

简介
glob 是 python 自带的一个操作文件的模块,以简洁实用著称。由于这个模块的功能比较简单,所以也很容易上手和使用。它主要用来查找符合特定规则的文件路径。使用这个模块来查找文件,只需要用到*、? 和 [] 这三个匹配符:

* : 匹配0个或多个字符;
? : 匹配单个字符;
[] :匹配指定范围内的字符,如:[0-9]匹配数字。
glob.glob 方法
glob.glob 方法主要返回所有匹配的文件路径列表。它只有一个参数 pathname ,定义了文件路径匹配规则,这里可以是*对路径,也可以是相对路径。

使用 * 匹配
我们可以用 * 匹配零个或者多个字符。

输出目录下的子目录或者文件:

for p1 in glob.glob(‘/Users/cxhuan/Downloads/globtest/*’):
print(p1)

运行上面代码,会将 globtest 文件夹下仅有的目录输出出来,输出内容如下:

/Users/cxhuan/Downloads/globtest/hello

我们也可以通过制定层级来遍历文件或者文件夹:

for p in glob.glob(‘/Users/cxhuan/Downloads/globtest/*/*’):
print(p)

上面的代码会遍历 globtest 文件夹以及子文件夹,将所有的文件或文件夹路径打印出来:

/Users/cxhuan/Downloads/globtest/hello/world
/Users/cxhuan/Downloads/globtest/hello/hello3.txt
/Users/cxhuan/Downloads/globtest/hello/hello2.txt
/Users/cxhuan/Downloads/globtest/hello/hello1.txt
我们也可以对文件或者文件夹进行过滤:

for p in glob.glob(‘/Users/cxhuan/Downloads/globtest/hello/*3.txt’):
print(p)

上面代码值匹配 hello 目录下的文件名末尾为 ‘3’ 的 txt 文件,运行结果如下:

/Users/cxhuan/Downloads/globtest/hello/hello3.txt
使用 ? 匹配
我们可以用问号(?)匹配任何单个的字符。

for p in glob.glob(‘/Users/cxhuan/Downloads/globtest/hello/hello?.txt’):
print(p)

上面的代码输出 hello 目录下的以 ‘hello’ 开头的 txt 文件,输出结果如下:

/Users/cxhuan/Downloads/globtest/hello/hello3.txt
/Users/cxhuan/Downloads/globtest/hello/hello2.txt
/Users/cxhuan/Downloads/globtest/hello/hello1.txt

使用 [] 匹配
我们可以使用 [] 来匹配一个范围:

for p in glob.glob(‘/Users/cxhuan/Downloads/globtest/hello/*[0-2].*’):
print(p)

我们想要得到 hello 目录下的文件名结尾数字的范围为 0到2的文件,运行上面代码,获得的输出为:

/Users/cxhuan/Downloads/globtest/hello/hello2.txt
/Users/cxhuan/Downloads/globtest/hello/hello1.txt
glob.iglob 方法
python 的 glob 方法可以对文件夹下所有文件进行遍历,并返回一个 list 列表。而 iglob 方法一次只获取一个匹配路径。下面是一个简单的例子来说明二者的区别:

p = glob.glob(‘/Users/cxhuan/Downloads/globtest/hello/hello?.*’)
print(p)

print(‘———————-‘)

p = glob.iglob(‘/Users/cxhuan/Downloads/globtest/hello/hello?.*’)
print(p)

运行上面代码,结果返回是:

[‘/Users/cxhuan/Downloads/globtest/hello/hello3.txt’, ‘/Users/cxhuan/Downloads/globtest/hello/hello2.txt’, ‘/Users/cxhuan/Downloads/globtest/hello/hello1.txt’]
———————-
<generator object _iglob at 0x1040d8ac0>

从上面的结果我们可以很容易看到二者的区别,前者返回的是一个列表,后者返回的是一个可迭代对象。

我们针对这个可迭代对象做一下操作看看:

p = glob.iglob(‘/Users/cxhuan/Downloads/globtest/hello/hello?.*’)
print(p.__next__())
print(p.__next__())

运行结果如下:

/Users/cxhuan/Downloads/globtest/hello/hello3.txt
/Users/cxhuan/Downloads/globtest/hello/hello2.txt
我们可以看到,针对这个可迭代对象,我们一次可以获取到一个元素。这样做的好处是节省内存,试想如果一个路径下有大量的文件夹或者文件,我们使用这个迭代对象不用一次性全部获取到内存,而是可以慢慢获取。

这就是你需要的铁饭碗

这就是你需要的铁饭碗

【大神观摩】他半年把python 学到了能出书的程度
他是知名外企技术架构师,在业余时间半年自学Python,就撰写了两部Python技术书籍,他是如何做到的?5月14日(周四)晚8点邀请您一起直播观摩。

文 | 李晓飞

来源:Python 技术「ID: pythonall」

前两天看了 码农翻身 公号作者刘欣 的一篇文件 我去北京吃了顿散伙饭

讲述了曾经理论上看起来*不会倒的部门解散的事情

理论上说,只要IBM不倒,这个部门肯定不会倒

多么有讽刺意味,再强健的组织,再权威的承诺,都是靠不住的

同时让我想到技术人的35岁坎儿

今天就技术人职场,谈谈一些看法,期望对你有所启发

专业能力
无论做什么,对于一个职场人来说,专业能力是*为基础的立足之本

有了专业的能力,才能站稳脚跟,才有后面其他发展的基础和条件

我记得毕业之初,学校有场辩论赛,主题是先就业还是先择业,当时不知如何选择,现在看来,其实选择哪个不是*重要的,因为对于一个刚走向社会的毕业生而言,无论哪个领域,*重要的就是尽快练就自己的专业能力

那么对于一个计算机领域的人来说,如何培养这个专业能力呢

专业知识
首先是专业基础知识要扎实

不可否认,现在学校里学的东西比较落后,对于市场来说有点过时,但是任何领域都有这个领域*基本的知识构建,这些*基本的知识构建,无论如何是要学且学明白的

对于计算机而言,*基础的是 计算机导论,C语言编程,和数据结构,而这些都是在学校里会教的,如果没有学明白,那么利用平时时间应该仔细深入的补上,其中包含了对计算机领域的基础理论,对编程语言的基础知识,对数据信息的基本处理

而后面的职业生涯中,所以的技术知识,都是基于这些基本知识展开的

从事行业之初,需要认真阅读经典的专业书籍,如 代码大全2、深入理解操作系统、需求分析与系统设计, 人月神话等,不一而全

代码大全,详细说明了代码如何编写,以及遇到的问题如何处理,如何写出易于维护的代码

深入理解计算机系统,深入浅出的刨析了计算机的方方面面,从信息表示,到操作系统的组成,特别是对网络编程,多线程编程的讲述,用及其简单的篇幅,深入讨论了计算机的技术技术

需求分析与系统设计,从需求到系统实现,详细的定义个各个阶段,深入讨论了这个过程的转化方法,概念,以及实践操作,特别是每个阶段的要点,目前我们公司主要的设计方法,都是基于这本书的理论框架的

人月神话,描述了系统构建过程不是一般理解的线性的,投入更多的人力无论就可以提高构建速度,阐述了效率,成本,质量三者之间的关系,是软件工程的先驱之作,是了解软件工程,做项目管理的比读

通过认真阅读专业书籍,并在日常工作中多加应用,比较能强化自己的专业基础,并且可以提高专业化的速度

业务能力
然后是业务能力要强,如果说专业能力是对计算机本身的理解和认识,那么业务能力是对现实世界的理解和认识

技术是服务于现实的,无论是几十年前出现计算机,还是现在的互联网,都是基于解决现实业务问题而产生且发展壮大的

不过需求人员(或是产品经理)和技术人员总是相互PK,几乎成为了对立的两面

需求人员天马行空,技术人员步步为营

对于技术人员来说,用*简的*省的方式实现需求就行了,这并没什么错

但如果能进一步了解业务的背景,愿景,背后的原理和逻辑,将对需要解决的问题,设计的方案,采用的技术有更好的认知和理解,更能做出符合需求的有价值的系统或者产品

即:

技术上要专业,业务上要全面

短期来看,在解决问题时,更有了目的性,长期来看,为自己全面看待问题,掌握整体的业务流程奠定了基础,为自己走向更高一层铺平了道路

独挡一面
如果有了专业的技术,又能更好的理解业务需求,那么自己的能力就已经很大程度上得到了扩展

此时就具备了独挡一面的特性了

可以和需求甚至客户直接沟通,不需要中间人协助

可以通过技术支持,方案设定,不需要太多的技术指导

而在工作中,能独挡一面,意味者效率更高,业务扩展更容易

独挡一面的人,更能给公司创造价值,那么相应的,这样的人,也会得到公司更多的青睐与机会

以人为本
技术人,一般都只关注技术,会有一种

只要技术强,走遍天下都不怕

的感觉或认识,不愿搭理不懂技术的人,三句不离本行,我之前就是这样,约同学去吃饭,讲的都是电视机开关按钮的设计原理 😉

不仅如此,还有很多人是因为感觉与人大交道难,或者不愿意更人交道,才做了技术,俗称 “技术宅”

只要能到达专业能力的要去,也无可厚非

不过我们所在的社会是以人为基础的社会,人才是现阶段社会的主宰,也就是说所谓的价值,是以对人的服务和帮助而产生的

人是价值的拥有者与支配者

前几天遇到一个博士后,他开始在银行做一个小主管,但因为不喜欢和人大交道,去了学校教书,想要一个环境单一的没有负责人事关系的地方,但后来才明白,其实无论做什么首先需要搞定的是人,那么做一个只有自己就可以做的事情,也离不开别人的支持与帮助,更不用说组织更多的力量做一件大事儿了,他现在早已没有了之前对与人大交道的畏惧,组织教师学生一起做更大的事儿了

那么对于技术为本的技术人来说,应该如何以人为本呢?

提供价值
为他人提供价值,并不是拍须遛马,或哗众取宠,而是利用自己的专业能力,为他人提供超常的服务与价值

对于一个客户,除了满足规定的服务之外,额外提供一些力所能及的帮助,例如帮助客户解决软件问题,打印机调试等

对于一个同事,除了提供分内的帮助之外,额外提供一些帮助,例如通过一份公司开发说明,帮助做个代码审查,对他遇到的问题做个分析等

对于一个不是自己分内的事情,如有必要提供支持,例如在项目策划中提供一些信息和建议,帮助其他项目做一些梳理工作等

主动做一些绩效不高甚至无绩效的对公司或者对大家有帮助的事情,例如做一个解决大家问题的小系统,整理分析一些数据等

也可以将自己的知识,经验等整理出来,分享给需要的人

这些看似简单的,或者与自己毫不相干的事情,却能给他人提供意想不到的帮助,让自己成为大家喜欢的,友好的,有用的人

扩展影响力
现在的社会关系,比起之前的树形结构,更趋于网络化和扁平化的结构,每个人就好像网络中的一个节点

在网络中的价值体现在与其他节点的链接数量上,更多的链接意味着更多的价值与影响力

更为重要的是,网络结构中,存在更多的可能性,不会让自己卡死在一个体系当中

如果是树形结构,如果某一个节点出了问题,信息将无法下达到其下的字节的们,而其下的子节点也无法与其上的其他节点通信

网络结构里就不一样了,像互联网一样,总是存在一个*优的达到通路,即使众多节点出现了故障,但总能实现互联互通

另外网络结构里,更容易开展自己的影响力,通过网络可以宣传出自己的能力,实践,乃至产品,会有更多的人知道和了解,无形中扩展了自己的影响力,为拥有更多的机会打下了基础

具体可以参考之前写的 打造个人品牌,我用 Python

总结
单纯的技术工作者,往往会有35岁的青春饭票限制,这是由于多种因素造成的

能掌握在我们手里的法宝,就是 专业能力 和 以人为本,努力提高自己的专业能力同时,打造自己的链接网络和影响力,会让自己更具优势

当逆境或灾难来临时,会多了一层保障,不会着急忙慌,手足无措,怨天尤人

其实,在这个时代里,命运是掌握在自己手里的,只不过很多时候我们没意识到,或者没有一个内在的或者外在的动力促使我们去做

提早认识到,并且坚定的的去做,永远不吃自己的散伙饭

iOS 设置导航透明的两种方法

方法一

在当前页面加入下面方法

– (void)viewWillAppear:(BOOL)animated{

[superviewWillAppear:animated];

[self.navigationController.navigationBarsetBackgroundImage:[UIImagenew]forBarMetrics:UIBarMetricsDefault];

//去掉导航栏底部的黑线

self.navigationController.navigationBar.shadowImage = [UIImage new];

}

如果仅设置当前页导航透明,需加入下面方法
– (void)viewWillDisappear:(BOOL)animated{

[superviewWillDisappear:animated];

[self.navigationController.navigationBarsetBackgroundImage:nilforBarMetrics:UIBarMetricsDefault];

[self.navigationController.navigationBarsetShadowImage:nil];

}

需要注意的是:如果返回上一页的操作有动画,则在上一页出现的时候,导航栏的颜色会逐渐变化

方法二(这个方法在iOS 11上会出现crash,iOS 11上[self.navigationController.navigationBar subviews]值为空)

– (void)viewWillAppear:(BOOL)animated{

[superviewWillAppear:animated];

[[self.navigationController.navigationBar subviews]objectAtIndex:0].alpha =0;

}

仅设置当前页面

– (void)viewWillDisappear:(BOOL)animated{

[super viewWillDisappear:animated];

[[self.navigationController.navigationBar subviews]objectAtIndex:0].alpha =1;

}

我这有一个很好的三方库:

@property (nonatomic, strong) UIColor *barColor; //NavigationBar background color, default is white
@property (nonatomic, strong) UIColor *tintColor; //NavigationBar subviews color
@property (nonatomic, strong) UIImage *backgroundImage; //default is nil
@property (nonatomic, assign) UIStatusBarStyle statusBarStyle; // default is UIStatusBarStyleDefault

@property (nonatomic, assign) float zeroAlphaOffset;//color will changed begin this offset, default is -64
@property (nonatomic, assign) float fullAlphaOffset;//color alpha will be 1 in this offset, default is 200
@property (nonatomic, assign) float minAlphaValue;//bar minAlpha, default is 0
@property (nonatomic, assign) float maxAlphaValue;//bar maxAlpha, default is 1

@property (nonatomic, strong) UIColor *fullAlphaTintColor;//if you set this property, the tintColor will changed in fullAlphaOffset
@property (nonatomic, assign) UIStatusBarStyle fullAlphaBarStyle;//if you set this property, the barStyle will changed in fullAlphaOffset

@property (nonatomic, assign) BOOL allChange;//if allchange = yes, the tintColor will changed with the barColor change, default is yes, if you only want to change barColor, set allChange = NO
@property (nonatomic, assign) BOOL reversal;//this will cause that if currentAlpha = 0.3,it will be 1 – 0.3 = 0.7
@property (nonatomic, assign) BOOL continues;//when continues = YES, bar color will changed whenever you scroll, if you set continues = NO,it only be changed in the fullAlphaOffset

+ (void)setBarColor:(UIColor *)color;
+ (void)setTintColor:(UIColor *)color;
+ (void)setBackgroundImage:(UIImage *)image;
+ (void)setStatusBarStyle:(UIStatusBarStyle)style;

+ (void)setZeroAlphaOffset:(float)offset;
+ (void)setFullAlphaOffset:(float)offset;
+ (void)setMaxAlphaValue:(float)value;
+ (void)setMinAlphaValue:(float)value;

+ (void)setFullAlphaTintColor:(UIColor *)color;
+ (void)setFullAlphaBarStyle:(UIStatusBarStyle)style;

+ (void)setAllChange:(BOOL)allChange;
+ (void)setReversal:(BOOL)reversal;
+ (void)setContinues:(BOOL)continues;

+ (void)managerWithController:(UIViewController *)viewController;//you should use this method to init MXNavigationManager

+ (void)changeAlphaWithCurrentOffset:(CGFloat)currentOffset;// implemention this method in @selectot(scrollView: scrollViewDidScroll)

+ (void)reStoreToSystemNavigationBar; //change the navigationBar to system style

#import “MXNavigationBarManager.h”

static const CGFloat kNavigationBarHeight = 64.0f;
static const CGFloat kDefaultFullOffset = 200.0f;
static const float kMaxAlphaValue = 0.995f;
static const float kMinAlphaValue = 0.0f;
static const float kDefaultAnimationTime = 0.35f;

#define SCREEN_RECT [UIScreen mainScreen].bounds
#define BACKGROUNDVIEW_FRAME CGRectMake(0, -20, CGRectGetWidth(SCREEN_RECT), kNavigationBarHeight)

@interface MXNavigationBarManager ()

@property (nonatomic, strong) UINavigationBar *selfNavigationBar;
@property (nonatomic, strong) UINavigationController *selfNavigationController;

@property (nonatomic, strong) UIImage *saveImage;
@property (nonatomic, strong) UIColor *saveColor;
@property (nonatomic, strong) UIColor *saveTintColor;
@property (nonatomic, strong) NSDictionary *saveTitleAttribute;
@property (nonatomic, assign) UIStatusBarStyle saveBarStyle;

@property (nonatomic, assign) BOOL setFull;
@property (nonatomic, assign) BOOL setZero;
@property (nonatomic, assign) BOOL setChange;

@end

@implementation MXNavigationBarManager

#pragma mark – property set
+ (void)setBarColor:(UIColor *)color {
[self sharedManager].barColor = color;
}

+ (void)setTintColor:(UIColor *)color {
[self sharedManager].tintColor = color;
[self sharedManager].selfNavigationBar.tintColor = color;
[self setTitleColorWithColor:color];
}

+ (void)setBackgroundImage:(UIImage *)image {
[[self sharedManager].selfNavigationBar setBackgroundImage:image
forBarMetrics:UIBarMetricsDefault];
}

+ (void)setStatusBarStyle:(UIStatusBarStyle)style {
[self sharedManager].statusBarStyle = style;
[[UIApplication sharedApplication] setStatusBarStyle:style];
}

+ (void)setZeroAlphaOffset:(float)offset {
[self sharedManager].zeroAlphaOffset = offset;
}

+ (void)setFullAlphaOffset:(float)offset {
[self sharedManager].fullAlphaOffset = offset;
}

+ (void)setMinAlphaValue:(float)value {
value = value < kMinAlphaValue ? kMinAlphaValue : value;
[self sharedManager].minAlphaValue = value;
}
+ (void)setMaxAlphaValue:(float)value {
value = value > kMaxAlphaValue ? kMaxAlphaValue : value;
[self sharedManager].maxAlphaValue = value;
}

+ (void)setFullAlphaTintColor:(UIColor *)color {
[self sharedManager].fullAlphaTintColor = color;
}

+ (void)setFullAlphaBarStyle:(UIStatusBarStyle)style {
[self sharedManager].fullAlphaBarStyle = style;
}

+ (void)setAllChange:(BOOL)allChange {
[self sharedManager].allChange = allChange;
}

+ (void)setReversal:(BOOL)reversal {
[self sharedManager].reversal = reversal;
}

+ (void)setContinues:(BOOL)continues {
[self sharedManager].continues = continues;
}

+ (void)reStoreToSystemNavigationBar {
[[self sharedManager].selfNavigationController setValue:[UINavigationBar new] forKey:@”navigationBar”];
}

#pragma mark – Public Method
+ (void)managerWithController:(UIViewController *)viewController {
UINavigationBar *navigationBar = viewController.navigationController.navigationBar;
[self sharedManager].selfNavigationController = viewController.navigationController;
[self sharedManager].selfNavigationBar = navigationBar;
[navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[navigationBar setShadowImage:[UIImage new]];
}

+ (void)changeAlphaWithCurrentOffset:(CGFloat)currentOffset {
MXNavigationBarManager *manager = [self sharedManager];

float currentAlpha = [self curretAlphaForOffset:currentOffset];

if (![manager.barColor isEqual:[UIColor clearColor]]) {
if (!manager.continues) {
if (currentAlpha == manager.minAlphaValue) {
[self setNavigationBarColorWithAlpha:manager.minAlphaValue];
} else if (currentAlpha == manager.maxAlphaValue) {
[UIView animateWithDuration:kDefaultAnimationTime animations:^{
[self setNavigationBarColorWithAlpha:manager.maxAlphaValue];
}];
manager.setChange = YES;
} else {
if (manager.setChange) {
[UIView animateWithDuration:kDefaultAnimationTime animations:^{
[self setNavigationBarColorWithAlpha:manager.minAlphaValue];
}];
manager.setChange = NO;
}
}
} else {
[self setNavigationBarColorWithAlpha:currentAlpha];
}
}

if (manager.allChange) [self changeTintColorWithOffset:currentAlpha];
}

#pragma mark – calculation
+ (float)curretAlphaForOffset:(CGFloat)offset {
MXNavigationBarManager *manager = [self sharedManager];
float currentAlpha = (offset – manager.zeroAlphaOffset) / (float)(manager.fullAlphaOffset – manager.zeroAlphaOffset);
currentAlpha = currentAlpha < manager.minAlphaValue ? manager.minAlphaValue : (currentAlpha > manager.maxAlphaValue ? manager.maxAlphaValue : currentAlpha);
currentAlpha = manager.reversal ? manager.maxAlphaValue + manager.minAlphaValue – currentAlpha : currentAlpha;
return currentAlpha;
}

+ (void)changeTintColorWithOffset:(float)currentAlpha {
MXNavigationBarManager *manager = [self sharedManager];
if (currentAlpha >= manager.maxAlphaValue && manager.fullAlphaTintColor != nil) {
if (manager.setFull) {
manager.setFull = NO;
manager.setZero = YES;
} else {
if (manager.reversal) {
manager.setFull = YES;
}
return;
}
manager.selfNavigationBar.tintColor = manager.fullAlphaTintColor;
[self setTitleColorWithColor:manager.fullAlphaTintColor];
[self setUIStatusBarStyle:manager.fullAlphaBarStyle];
} else if (manager.tintColor != nil) {
if (manager.setZero) {
manager.setZero = NO;
manager.setFull = YES;
} else {
return;
}
manager.selfNavigationBar.tintColor = manager.tintColor;
[self setTitleColorWithColor:manager.tintColor];
[self setUIStatusBarStyle:manager.statusBarStyle];
}
}

#pragma mark – private method
+ (MXNavigationBarManager *)sharedManager {
static MXNavigationBarManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[MXNavigationBarManager alloc] init];
[self initBaseData:manager];
});
return manager;
}

+ (void)initBaseData:(MXNavigationBarManager *)manager {
manager.maxAlphaValue = kMaxAlphaValue;
manager.minAlphaValue = kMinAlphaValue;
manager.fullAlphaOffset = kDefaultFullOffset;
manager.zeroAlphaOffset = -kNavigationBarHeight;
manager.setZero = YES;
manager.setFull = YES;
manager.allChange = YES;
manager.continues = YES;
}

+ (void)setTitleColorWithColor:(UIColor *)color {
NSMutableDictionary *textAttr = [NSMutableDictionary dictionaryWithDictionary:[self sharedManager].selfNavigationBar.titleTextAttributes];
[textAttr setObject:color forKey:NSForegroundColorAttributeName];
[self sharedManager].selfNavigationBar.titleTextAttributes = textAttr;
}

+ (void)setNavigationBarColorWithAlpha:(float)alpha {
MXNavigationBarManager *manager = [self sharedManager];
NSLog(@”alpha = %f”, alpha);
[self setBackgroundImage:[self imageWithColor:[manager.barColor colorWithAlphaComponent:alpha]]];
}

+ (void)setUIStatusBarStyle:(UIStatusBarStyle)style {
[[UIApplication sharedApplication] setStatusBarStyle:style];
}

+ (UIImage *)imageWithColor:(UIColor *)color {
CGRect rect = CGRectMake(0, 0, 1, 1);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[color setFill];
CGContextFillRect(context, rect);
UIImage *imgae = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return imgae;
}

 

iOS中atomic和nonatomic区别及内部实现

前言
iOS开发中使用@property声明属性时,经常用到atomic与nonatomic两个关键字

@property(nonatomic,strong)UIImage *icon;
@property(strong)UIImage *icon1;//不写默认atomic
atomic与nonatomicd的主要区别就是系统自动生成的getter/setter方法不一样

atomic系统自动生成的getter/setter方法会进行加锁操作
nonatomic系统自动生成的getter/setter方法不会进行加锁操作

atomic
系统生成的getter/setter方法会进行加锁操作,注意:这个锁仅仅保证了getter和setter存取方法的线程安全.

因为getter/setter方法有加锁的缘故,故在别的线程来读写这个属性之前,会先执行完当前操作.

例如: 线程1调用了某一属性的setter方法并进行到了一半,线程2调用其getter方法,那么会执行完setter操作后,在执行getter操作,线程2会获取到线程1 setter后的完整的值.

当几个线程同时调用同一属性的setter、getter方法时,会get到一个完整的值,但get到的值不可控.

例如: 线程1 调用getter 线程2 调用setter 线程3 调用setter 这3个线程并行同时开始,线程1会get到一个值,但是这个值不可控,可能是线程2,线程3 set之前的原始值,可能是线程2 set的值,也可能是线程3 set的值

atomic是线程安全的吗?

不是, 很多文章谈到atomic和nonatomic的区别时,都说atomic是线程安全,其实这个说法是不准确的. atomic只是对属性的getter/setter方法进行了加锁操作,这种安全仅仅是set/get 的读写安全,并非真正意义上的线程安全,因为线程安全还有读写之外的其他操作(比如:如果当一个线程正在get或set时,又有另一个线程同时在进行release操作,可能会直接crash)

nonatomic
系统生成的getter/setter方法没有加锁 线程不安全,但更快 当多个线程同时访问同一个属性,会出现无法预料的结果

内部实现
下面我们新建一个nonatomic和一个atomic变量,并用代码演示其内部实现

//interface
@property(nonatomic,strong)UIImage *icon;//nonatomic
@property(strong)UIImage *icon1;//atomic

nonatomic对象setter和getter方法的实现

//mrc 环境
//implementation
@synthesize icon = _icon;

//set
-(void)setIcon:(UIImage *)icon
{
if(_icon != icon)
{
[_icon release];
_icon = [icon retain];
}
}
//get
-(UIImage *)icon
{
return _icon;
}

atomic对象setter和getter方法的实现

//mrc 环境
//implementation
@synthesize icon1 = _icon1;

//set
-(void)setIcon1:(UIImage *)icon1
{
//同步代码块
@synchronized (self) {
if(_icon1 != icon1)
{
[_icon1 release];
_icon1 = [icon1 retain];
}
}
1
2
3
4
5
6
}
//get
-(UIImage *)icon1
{
UIImage *image = nil;
//同步代码块
@synchronized (self) {
image = [[_icon1 retain] autorelease];
}
return image;

}

总结
atomic只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的,因此在多线程编程时,线程安全还需要开发者自己来处理.

关于选择:atomic系统生成的getter、setter会保证get、set操作的安全性,但相对nonatomic来说,atomic要更耗费资源,且速度要慢,故在iPhone等小型设备上,如果没有多线程之间的通讯,使用nonatomic是更好的选择

Mac如何关闭开机启动项

1、首先进入“系统偏好设置”的 “用户与群组”选项。

2、打开用户与群组后可以看到如图所示的“登陆项”标签。

3、可以看到有应用程序是默认开机启动的,点击左下角的“+-” 即可添加或删除开机启动程序。如果机器有设置密码,先点击左下角锁的图标进行解锁。