Python 使有条件;加上;漂亮的

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函数,该函数可能打开或不打开同一个文件

关于以下函数的正确“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 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)

用通俗易懂的语言概括问题:

  • 可能会向您传递一个打开的文件句柄,然后您想让它保持打开状态,因为关闭该资源是调用方的责任
  • 您可能需要打开自己的资源,然后关闭它是您的责任
这就是Python中接受不均匀参数类型的问题。你可以这样做,但有时会让你的代码更难看

上下文管理器只是try/finally的语法糖:

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