使用"进行有条件的" pretier

时间:2017-03-21 20:36:14

标签: 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 doSomethingWithSameFile():
    with open('default.txt') as fp:
         preample = fp.read(10)
         more_data = readSomething(fb)
         ...

或者像其他地方一样:

def init():
    data = readSomething()
    ...

5 个答案:

答案 0 :(得分:4)

我不认为这是正确的解决方案,但我认为这是你想要的。

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)

答案 1 :(得分:3)

用简明的语言概括这个问题:

  • 您可能会传递一个打开的文件句柄,然后您希望将其保持打开状态,因为调用者有责任关闭该资源。
  • 您可能需要打开自己的资源,然后您有责任关闭它。

这是在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()

为了使它更“漂亮”,考虑改变接口,这样你就不必同时处理读取数据和从同一个函数中进行资源管理 - 重构使你的函数只有一个责任。

答案 2 :(得分:3)

您可以定义一个自定义上下文管理器,只有在传递给它时才会执行某些操作,但它可能有点过分:

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)

答案 3 :(得分:2)

老实说,你的代码中最Pythonic版本可能就是你已经拥有的版本,除了稍微清理一下:

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)

为了避免重复自己一点点,不要让Pythonic跳过很多箍。

答案 4 :(得分:0)

这不使用with,并关闭默认值或作为参数传递的文件,  但也许它仍然是一种选择。

def readSomething(fp=None):
    if fp is None:
        fp = open('default.txt')
    return (fp.read(100), fp.close)