【问题标题】:Preserve function context in Python在 Python 中保留函数上下文
【发布时间】:2017-11-17 13:49:35
【问题描述】:

我在 Python 中有一个函数,当第一次被调用时,它会将文件的内容读取到列表中并检查一个元素是否在该列表中。

def is_in_file(element, path):
    with open(path, 'r') as f:
        lines = [line.strip() for line in f.readlines()]
    return element in lines

但是,当再次调用该函数时,不应再次读取文件的内容;函数应该记住第一次调用时 lines 的值。

有没有办法在再次调用函数时保留函数的上下文?我不想让lines 全局化,以免乱扔上述命名空间。我想这与使用生成器和yield 语句非常相似...

【问题讨论】:

  • 似乎是class 的典型用例。

标签: python python-3.x function


【解决方案1】:

Dirty hack:将变量添加到函数对象并在那里存储值。

def is_in_file(element, path):
    if not hasattr(is_in_file, "__lines__"):
        with open(path, 'r') as f:
            setattr(is_in_file, "__lines__", [line.strip() for line in f.readlines()])
    return element in is_in_file.__lines__

【讨论】:

  • 有趣的想法。但肯定很老套:-)
  • 鉴于函数签名无法更改或封装在类中,为什么这很hacky?它相当于 c 中的静态变量,这几乎是 OP 想要的
  • 如果函数被不同的文件连续调用,这不起作用;它总是根据打开的第一个文件的内容返回。
  • 为什么用setattr 而不是简单的点符号?
【解决方案2】:

我的观点是,正确的方法是将其封装在一个类中。路径在实例创建时设置,方法调用使用行列表。这样你甚至可以同时拥有不同的文件

class finder:
   def __init__(self, path):
       with open(path, 'r') as f:
           self.lines = [line.strip() for line in f]
   def is_in_file(self, element):
       return element in lines

这并不是您所要求的,而是更多的 OO。

【讨论】:

  • 从长远来看,这是更好的解决方案。
  • 你甚至可以给那个类一个__call__ 方法,让它感觉像一个函数。
【解决方案3】:

您可以将这些行保存在使用可变默认值声明的关键字参数中:

def is_in_file(element, path, lines=[]):
    if lines:
        return element in lines
    with open(path, 'r') as f:
        lines += [line.strip() for line in f.readlines()]
    return element in lines

警告: 您必须确保仅使用一个文件调用此函数;如果你用第二个文件调用它,它不会打开它并继续根据打开的第一个文件返回值。

更灵活的解决方案:

更灵活的解决方案可能是使用行字典,其中每个新文件都可以打开一次并存储,使用路径作为键;然后,您可以使用不同的文件调用该函数,并在记忆内容的同时获得正确的结果。

def is_in_file(element, path, all_lines={}):
    try:
        return element in all_lines[path]
    except KeyError:
        with open(path, 'r') as f:
            all_lines[path] = [line.strip() for line in f.readlines()]
        return element in lines

OO解决方案:

创建一个类来封装文件的内容,就像@SergeBallesta 建议的那样;虽然它不能完全满足您的要求,但从长远来看,它可能是更好的解决方案。

【讨论】:

    【解决方案4】:

    使用 functools.lru_cache 装饰器设置一个辅助函数,该函数只读取任何给定文件一次,然后存储结果。

    from functools import lru_cache
    
    @lru_cache(maxsize=1)
    def read_once(path):
        with open(path) as f:
            print('reading {} ...'.format(path))
            return [line.strip() for line in f]
    
    def in_file(element, path):
        return element in read_once(path)
    

    演示:

    >>> in_file('3', 'file.txt')
    reading file.txt ...
    True
    >>> in_file('3', 'file.txt')
    True
    >>> in_file('3', 'anotherfile.txt')
    reading anotherfile.txt ...
    False
    >>> in_file('3', 'anotherfile.txt')
    False
    

    这具有很大的优势,in_file 不必每次都使用相同的文件名来调用。

    如果您希望在任何给定时间缓存多个文件,您可以将maxsize 参数调整为更大的数字。

    最后:如果您只对成员资格测试感兴趣,请考虑read_once 的返回值。

    【讨论】:

      【解决方案5】:

      这个答案提出了一个类似于 Serge Ballesta 的想法的类。

      不同之处在于它完全像一个函数,因为我们使用它的__call__ 方法而不是点符号来进行搜索。

      此外,您可以添加任意数量的可搜索文件。

      设置:

      class in_file:
          def __init__(self):
              self.files = {}
      
          def add_path(self, path):
              with open(path) as f:
                  self.files[path] = {line.strip() for line in f}
      
          def __call__(self, element, path):
              if path not in self.files:
                  self.add_path(path)
              return element in self.files[path]
      
      in_file = in_file()
      

      用法

      $ cat file1.txt 
      1
      2
      3
      $ cat file2.txt 
      hello
      $ python3 -i demo.py 
      >>> in_file('1', 'file1.txt')
      True
      >>> in_file('hello', 'file1.txt')
      False
      >>> in_file('hello', 'file2.txt')
      True
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-23
        • 1970-01-01
        • 1970-01-01
        • 2022-12-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多