Python 使有条件;加上;漂亮的
关于以下函数的正确“pythonic”方式的任何建议。我必须把它分成两个功能吗Python 使有条件;加上;漂亮的,python,Python,关于以下函数的正确“pythonic”方式的任何建议。我必须把它分成两个功能吗 def readSomething(fp=None): if fp: return fp.read(100) else: with open('default.txt', 'r') as fp: return fp.read(100) 我需要这样的函数,因为可以从另一个函数调用readSomething函数,该函数可能打开或不打开同一个文件
def readSomething(fp=None):
if fp:
return fp.read(100)
else:
with open('default.txt', 'r') as fp:
return fp.read(100)
我需要这样的函数,因为可以从另一个函数调用
readSomething
函数,该函数可能打开或不打开同一个文件
例如,在某些地方可以这样称呼:
def doSomethingWithSameFile():
with open('default.txt') as fp:
preample = fp.read(10)
more_data = readSomething(fb)
...
或者像其他地方那样:
def init():
data = readSomething()
...
我认为这不是正确的解决方案,但我认为这是你想要的
import contextlib
def readSomething(fp=None):
with contextlib.ExitStack() as stack:
if not fp:
fp = stack.enter_context(open('default.txt'))
return fp.read(100)
我得到的印象是,您将复制许多函数,如
readSomething()
,因此我建议将ExitStack代码放入装饰器中,并在需要此行为的地方包装函数
你也可以用一个装饰师。我不使用这种代码,因此下面的语法几乎肯定是不完整的,但总体思路是:
import functools
def fallback_to_default(fn):
@functools.wraps(fn)
def new_fn(fp=None, *args, **kwargs):
with contextlib.ExitStack() as stack:
if not fp:
fp = stack.enter_context(open('default.txt'))
return fn(fp, *args, **kwargs)
return new_fn
@fallback_to_default
def readSomething(fp=None):
return fp.read(100)
用通俗易懂的语言概括问题:
- 可能会向您传递一个打开的文件句柄,然后您想让它保持打开状态,因为关闭该资源是调用方的责任李>
- 您可能需要打开自己的资源,然后关闭它是您的责任李>
def readSomething(fp=None):
close_fp = False
if fp is None:
fp = open('default.txt')
close_fp = True
try:
return fp.read(100)
finally:
if close_fp:
fp.close()
<> >让它变得更“漂亮”,考虑改变接口,这样你就不必处理读数据和在相同的函数内做资源管理-重构,使你的函数有一个单一的责任。p> 这不会将
与
一起使用,并关闭默认值或作为参数传递的文件,
但也许这仍然是一种选择
def readSomething(fp=None):
if fp is None:
fp = open('default.txt')
return (fp.read(100), fp.close)
您可以定义一个自定义上下文管理器,该管理器仅在未向其传递任何内容的情况下才执行某些操作,但这可能有些过分:
class ContextOrNone(object):
def __init__(self, obj, fn, *args, **kwargs):
if obj is not None:
self.obj = obj
self.cleanup = False
else:
self.obj = fn(*args, **kwargs)
self.cleanup = True
def __enter__(self):
return self.obj
def __exit__(self, ex_type, ex_val, traceback):
if self.cleanup:
self.obj.__exit__(ex_type, ex_val, traceback)
或者,使用contextlib.contextmanager
:
from contextlib import contextmanager
@contextmanager
def ContextOrNone(obj, fn, *args, **kwargs):
was_none = obj is None
try:
if was_none:
obj = fn(*args, **kwargs)
yield obj
finally:
if was_none:
obj.__exit__()
一旦定义了它,就可以将readSomething
定义为:
def readSomething(fp=None):
with ContextOrNone(fp, open, 'default.txt', 'r') as fp:
return fp.read(100)
老实说,您的代码的最python版本可能就是您已经拥有的版本,只是稍微清理了一下:
def readSomething(fp=None):
if fp:
return fp.read(100)
with open('default.txt') as fp:
return fp.read(100)
这将保留您的原始意图和功能。它清晰易读。当然,有点重复。如果您的示例被简化为重复部分对您来说太怪异,那么将其提升到其自身的功能中:
def complicatedStuff(buf, sz):
# Obviously more code will go here.
return buf.read(sz)
def readSomething(fp=None):
if fp:
return complicatedStuff(fp, 100)
with open('default.txt') as fp:
return complicatedStuff(fp, 100)
仅仅为了避免一点点重复自己而跳过很多障碍,这不是Python的风格。您使用的是哪种Python版本?在3.4+…@wim 3.5中有一个更好的选择。还有什么选择?你想完成什么?在这种情况下,
else
没有做任何事情,因为if
返回。@TemporalWolf,你说得对,我可以去掉else
。然而,我主要关心的是如何避免双fp.read(100)
。更新问题,说明我为什么需要它。@TemporalWolf:fp.read(100)只是一个例子。它将被更多的逻辑所取代,我不想重复两次。谢谢。这确实比我之前的版本好得多,但双“fp.read(100)”仍然让我恼火。有什么补救办法吗?用调用realReadSomething(fp):返回fp.read(100)
来替换这两行fp.read(100)
?或者使用方法将文件返回给您。。。像getConfFileOrDefault(fp)
@Harvey那样,这并不能真正解决问题,因为您仍然需要写两次同一行,现在还有一个额外的函数可以跟踪。如果fp:,则可以使用这种精确的格式。。。通过hasattr
@TemporalWolf,我们无法获得简单性:hasattr比如果fp
更好的测试,例如readSomething(3)
。您忘了将fp
传递给ContextOrNone
这个解决方案可能更适合用contextmanager生成器样式编写。“这将是一个更短的尝试…最后,
用更少的样板文件。”哈维,我想。也添加了该变体,尽管它不是100%等效。这是执行OP要求的正确方法。这是他的规格,这是不必要的复杂。大概是因为他扩展了占位符代码return fp.read(100)
,这就减少了复杂性。它更冗长,也更枯燥,但是我不认为它更复杂——为什么你认为它更复杂?@wim你输入的更多的是应该做的事情,而不是你真正想要做的事情,让python为你做。OP的原始代码(没有else
)更易于阅读,完成了您所做的一切,并且可能更快。但逻辑的关键部分是重复的,因此这是不可接受的。就连OP似乎也意识到了这一点(否则他们不会问这个问题),但你仍然没有抓住重点。@Harvey OP发表声明说:“fp.read(100)
只是一个例子。它将被更多的逻辑所取代,我不想重复两次。”他们不想关闭传递的文件。但是默认文件应该关闭,就像他正在使用的那样,对吗?从问题中我不清楚,传递的文件必须保持打开状态。@user69453只有当fp
不真实时,他们才使用with
。