Python 有没有比通过变量更好的方法来确定循环块是否被执行(甚至一次)?
我知道python有Python 有没有比通过变量更好的方法来确定循环块是否被执行(甚至一次)?,python,for-loop,Python,For Loop,我知道python有else循环特性: for item in items: # loop block else: # will execute if there is no exception or break in loop block 由于这个特性,我想知道python中循环是否还有其他聪明的地方。现在,我想找到一种更好的方法(而不是通过变量)来确定是否执行了循环块(甚至一次): 如果项目没有在其他任何地方定义,您只需检查项目是否已分配: items = [] for i
else
循环特性:
for item in items:
# loop block
else:
# will execute if there is no exception or break in loop block
由于这个特性,我想知道python中循环是否还有其他聪明的地方。现在,我想找到一种更好的方法(而不是通过变量)来确定是否执行了循环块(甚至一次):
如果
项目
没有在其他任何地方定义,您只需检查项目
是否已分配:
items = []
for item in items:
pass
try:
del item
except NameError:
print("loop wasn't executed")
else:
print("loop was executed")
因此,如果items
为空,则不会执行循环,因此item
不会被定义,您会得到异常
delitem
调用确保第二次执行此代码时,item
不存在
(可以不使用另一个变量,但它仍然过于复杂:)有点神奇的方法:
emptysentinel = item = object()
for item in items:
# loop block
if item is not emptysentinel:
print("Big brother is watching you!")
这种方法正好提供了两个好处:
True
、False
或None
的变量进行直接真实性测试;这是一个字节码,执行直接C指针比较并存储True
或False
,然后进行相同的直接真实性测试)(与项目为非空时的成本类似,为空时的成本更低)
object
的开销非常低,仅使用16字节的内存,CPU时间可以忽略不计)empty = True
for item in items:
empty = False
# loop block
if not empty:
print("Big brother is watching you!")
是的,它必须一次又一次地存储False
,但是(至少在CPython 3上,只要您在函数范围内),这只是几个非常便宜的字节码:
LOAD_CONST
(从函数常量中提取False
的C级数组查找操作)STORE\u FAST
(一种C级数组存储操作,用于将刚加载的值推入分配给空变量的帧数组插槽)
加载常量
/存储快速
的成本是毫无意义的;在我的机器上,每个循环的成本增加了12-13纳秒。像首先创建清空continel
对象这样简单的事情的开销大约是90纳秒;在简单的micr中obenchmarks,基于emptysentel
的方法不会领先于基于empty
标记的方法,直到items
至少包含25个元素(如果将sentinel创建更改为emptysentel=item=[],则可以提前8个元素)
避免了加载全局函数和通过一般函数调用机制进行构造,但对哨兵使用空的列表会使意图更加不明显)
如果常见情况是项
为非空,则稍微修改(对于样式/最小目标try
块)版本的最快速度为:
for item in items:
pass
try:
del item
except NameError:
pass
else:
# Everything but the del should go here, not the try block, so you don't
# accidentally catch NameErrors from other sources
print("Big brother is watching you!")
它不需要初始化哨兵或标志,而且不引发异常的try
块非常便宜(当items
是一个元素的tuple
时,它比flag方法快一点,并且随着items
变大而变快;当items
为非空时,它也比sentinel方法快,但这是一个固定开销的问题;每个附加项的成本显然是相同的)
问题是,当项目
为空时,成本要高得多。每种方法的微基准(用打印
和循环块
替换为传递
)都有空项目
的成本,如下所示:
基于标志的:64.8纳秒
基于哨兵:159纳秒(如果使用[]
而不是对象()创建清空哨兵
,则为87.6纳秒)
尝试
/除了
/其他
基于:661 ns
因此,我将为您提供使用哪种方法的最终规则:
使用基于标志的方法
认真地说,使用基于标志的方法;如果项在相当长的时间内是空的,这是最快的,而非空的时间不会太长,更重要的是,这是显而易见的
真的吗?那么好吧:如果你的非空输入比较大,而且你真的需要速度,但是空输入仍然是半常见的,那么使用基于哨兵的方法;它与try
/一样可伸缩,除了,并且处理空输入时不会在病理上减慢速度
如果您的输入几乎总是非空的,您可以使用try
/方法,但
/else
方法除外,该方法始终是非空项
的最快方法,但代价是在项
为空时支付75个长循环的开销
旁注:另一个相对明显的方法(相当于我所熟悉的标志)是使用枚举;如果您想知道有多少项,这更有用,而不仅仅是“有没有项?”,但这并不可怕:
numitems = 0
for numitems, item in enumerate(items, 1):
# loop block
if numitems:
print("Big brother is watching you!")
缺点是它比所有其他方法都慢,但只有一种情况除外;它比try
/快,除了/else
当项为空时。它比基于标志的方法具有更高的每循环开销,比所有其他选项具有更高的固定开销。因此很明显,标志也是如此sed方法和标志更快,所以只需使用它们。另一种解决方案是检查局部变量()中的循环变量:
但它也有一些局限性:for item in items:
pass
try:
del item
except NameError:
pass
else:
# Everything but the del should go here, not the try block, so you don't
# accidentally catch NameErrors from other sources
print("Big brother is watching you!")
numitems = 0
for numitems, item in enumerate(items, 1):
# loop block
if numitems:
print("Big brother is watching you!")
for item in items:
# loop block
# loop block was executed
if 'item' in locals():
print("Big brother is watching you!")