包装和解包 Python 函数的正确方法?
Proper way to wrap and unwrap a Python function?
我正在为 Python print 函数编写包装器,但我的问题更笼统 - 包装了一个函数,打开它的正确方法是什么?
这可行,但我有两个顾虑:
class Iprint():
def __init__(self, tab=4, level=0):
''' Indented printer class.
tab controls number of spaces per indentation level (equiv. to tabstops)
level is the indentation level (0=none)'''
global print
self.tab = tab
self.level = level
self.old_print = print
print = self.print
def print(self, *args, end="\n", **kwargs):
indent = self.tab * self.level
self.old_print(" "*indent, end="", **kwargs)
self.old_print(*args, end=end, **kwargs)
indent = Iprint()
indent.level = 3
print("this should be indented")
print = indent.old_print
print("this shouldn't be indented")
我的两个担忧:
如果 Iprint()
class 有第二次实例化会怎样?这看起来很尴尬,也许是我应该避免的事情 - 但是如何呢?
倒数第二行print = indent.old_print
“展开”打印函数,将其返回到它的原始函数。这看起来也很尴尬-如果忘记了怎么办?
我可以用 __exit__
方法来完成,但我认为这会将它的使用限制在 with
块中。有没有更好的方法?
执行此操作的 Pythonic 方法是什么?
(我还应该提到,我预计会有 嵌套 包装器,我认为这使得正确地执行此操作更加重要...)
我想我已经解决了这个问题 - 至少让我自己满意。在这里,我调用了 class T(用于测试):
class T():
old_print = None
def __init__(self, tab=4, level=0):
''' Indented printer class.
tab controls number of spaces per indentation level (equiv. to tabstops)
level is the indentation level (0=none)'''
T.tab = tab
T.level = level
self.__enter__()
def print(self, *args, end="\n", **kwargs):
indent = T.tab * T.level
T.old_print(" "*indent, end="", **kwargs)
T.old_print(*args, end=end, **kwargs)
def close(self):
if T.old_print is not None:
global print
print = T.old_print
T.old_print = None
def __enter__(self):
if T.old_print is None:
global print
T.old_print = print
print = self.print
def __exit__(self, exception_type, exception_value, exception_traceback):
self.close()
print("this should NOT be indented")
i = T(level=1)
print("level 1")
i2 = T(level=2)
print("level 2")
i.close()
print("this should not be indented")
i3 = T(level=3)
print("level 3")
i2.close()
print("not indented")
with i:
print("i")
print("after i")
with T(level=3):
print("T(level=3)")
print("after T(level=3)")
它会默默地强制 class 的单个(功能)实例,而不管 T()
被调用了多少次,正如 @MichaelButscher 所建议的(谢谢;这是迄今为止最有帮助的评论)。
它与 WITH
块一起工作干净,如果不使用 WITH
块,您可以手动调用关闭方法。
输出如预期的那样:
this should NOT be indented
level 1
level 2
this should not be indented
level 3
not indented
i
after i
T(level=3)
after T(level=3)
你在这里真正想做的似乎是找到一种方法以“pythonic”方式覆盖内置 print
函数。
虽然有办法做到这一点,但我还是要小心一点。 "pythonic code"的规则之一是
Explicit is better than implicit.
覆盖print
本质上是一种隐式解决方案,允许自定义打印功能来解决您的需求会更“pythonic”。
但是,假设我们正在讨论一个用例,其中可用的最佳选项是覆盖 print
。例如,假设您想缩进 help()
函数的输出。
您可以直接覆盖print
,但是您运行有可能导致您看不到的意外更改。
例如:
def function_that_prints():
log_file = open("log_file.txt", "a")
print("This should be indented")
print("internally logging something", file = log_file)
log_file.close()
indent = Iprint()
indent.level = 3
function_that_prints() # now this internal log_file.txt has been corrupted
print = indent.old_print
这很糟糕,因为您可能只是想更改屏幕上打印的输出,而不是可能使用或不使用打印的内部位置。
相反,您应该只覆盖 stdout,而不是 print.
Python 现在包含一个名为 contextlib.redirect_stdout()
的实用程序,记录在 here.
实现可能如下所示:
import io
import sys
import contextlib
class StreamIndenter(io.TextIOBase):
# io.TextIOBase provides some base functions, such as writelines()
def __init__(self, tab = 4, level = 1, newline = "\n", stream = sys.stdout):
"""Printer that adds an indent at the start of each line"""
self.tab = tab
self.level = level
self.stream = stream
self.newline = newline
self.linestart = True
def write(self, buf, *args, **kwargs):
if self.closed:
raise ValueError("write to closed file")
if not buf:
# Quietly ignore printing nothing
# prevents an issue with print(end='')
return
indent = " " * (self.tab * self.level)
if self.linestart:
# The previous line has ended. Indent this one
self.stream.write(indent)
# Memorize if this ends with a newline
if buf.endswith(self.newline):
self.linestart = True
# Don't replace the last newline, as the indent would double
buf = buf[:-len(self.newline)]
self.stream.write(buf.replace(self.newline, self.newline + indent))
self.stream.write(self.newline)
else:
# Does not end on a newline
self.linestart = False
self.stream.write(buf.replace(self.newline, self.newline + indent))
# Pass some calls to internal stream
@property
def writable(self):
return self.stream.writable
@property
def encoding(self):
return self.stream.encoding
@property
def name(self):
return self.stream.name
with contextlib.redirect_stdout(StreamIndenter()) as indent:
indent.level = 2
print("this should be indented")
print("this shouldn't be indented")
以这种方式覆盖打印既不会破坏 print
的其他用途,又允许正确处理更复杂的用法。
例如:
with contextlib.redirect_stdout(StreamIndenter()) as indent:
indent.level = 2
print("this should be indented")
indent.level = 3
print("more indented")
indent.level = 2
for c in "hello world\n": print(c, end='')
print()
print("\n", end='')
print(end = '')
print("this shouldn't be indented")
格式正确为:
this should be indented
more indented
hello world
this shouldn't be indented
我正在为 Python print 函数编写包装器,但我的问题更笼统 - 包装了一个函数,打开它的正确方法是什么?
这可行,但我有两个顾虑:
class Iprint():
def __init__(self, tab=4, level=0):
''' Indented printer class.
tab controls number of spaces per indentation level (equiv. to tabstops)
level is the indentation level (0=none)'''
global print
self.tab = tab
self.level = level
self.old_print = print
print = self.print
def print(self, *args, end="\n", **kwargs):
indent = self.tab * self.level
self.old_print(" "*indent, end="", **kwargs)
self.old_print(*args, end=end, **kwargs)
indent = Iprint()
indent.level = 3
print("this should be indented")
print = indent.old_print
print("this shouldn't be indented")
我的两个担忧:
如果
Iprint()
class 有第二次实例化会怎样?这看起来很尴尬,也许是我应该避免的事情 - 但是如何呢?倒数第二行
print = indent.old_print
“展开”打印函数,将其返回到它的原始函数。这看起来也很尴尬-如果忘记了怎么办?
我可以用 __exit__
方法来完成,但我认为这会将它的使用限制在 with
块中。有没有更好的方法?
执行此操作的 Pythonic 方法是什么?
(我还应该提到,我预计会有 嵌套 包装器,我认为这使得正确地执行此操作更加重要...)
我想我已经解决了这个问题 - 至少让我自己满意。在这里,我调用了 class T(用于测试):
class T():
old_print = None
def __init__(self, tab=4, level=0):
''' Indented printer class.
tab controls number of spaces per indentation level (equiv. to tabstops)
level is the indentation level (0=none)'''
T.tab = tab
T.level = level
self.__enter__()
def print(self, *args, end="\n", **kwargs):
indent = T.tab * T.level
T.old_print(" "*indent, end="", **kwargs)
T.old_print(*args, end=end, **kwargs)
def close(self):
if T.old_print is not None:
global print
print = T.old_print
T.old_print = None
def __enter__(self):
if T.old_print is None:
global print
T.old_print = print
print = self.print
def __exit__(self, exception_type, exception_value, exception_traceback):
self.close()
print("this should NOT be indented")
i = T(level=1)
print("level 1")
i2 = T(level=2)
print("level 2")
i.close()
print("this should not be indented")
i3 = T(level=3)
print("level 3")
i2.close()
print("not indented")
with i:
print("i")
print("after i")
with T(level=3):
print("T(level=3)")
print("after T(level=3)")
它会默默地强制 class 的单个(功能)实例,而不管 T()
被调用了多少次,正如 @MichaelButscher 所建议的(谢谢;这是迄今为止最有帮助的评论)。
它与 WITH
块一起工作干净,如果不使用 WITH
块,您可以手动调用关闭方法。
输出如预期的那样:
this should NOT be indented
level 1
level 2
this should not be indented
level 3
not indented
i
after i
T(level=3)
after T(level=3)
你在这里真正想做的似乎是找到一种方法以“pythonic”方式覆盖内置 print
函数。
虽然有办法做到这一点,但我还是要小心一点。 "pythonic code"的规则之一是
Explicit is better than implicit.
覆盖print
本质上是一种隐式解决方案,允许自定义打印功能来解决您的需求会更“pythonic”。
但是,假设我们正在讨论一个用例,其中可用的最佳选项是覆盖 print
。例如,假设您想缩进 help()
函数的输出。
您可以直接覆盖print
,但是您运行有可能导致您看不到的意外更改。
例如:
def function_that_prints():
log_file = open("log_file.txt", "a")
print("This should be indented")
print("internally logging something", file = log_file)
log_file.close()
indent = Iprint()
indent.level = 3
function_that_prints() # now this internal log_file.txt has been corrupted
print = indent.old_print
这很糟糕,因为您可能只是想更改屏幕上打印的输出,而不是可能使用或不使用打印的内部位置。 相反,您应该只覆盖 stdout,而不是 print.
Python 现在包含一个名为 contextlib.redirect_stdout()
的实用程序,记录在 here.
实现可能如下所示:
import io
import sys
import contextlib
class StreamIndenter(io.TextIOBase):
# io.TextIOBase provides some base functions, such as writelines()
def __init__(self, tab = 4, level = 1, newline = "\n", stream = sys.stdout):
"""Printer that adds an indent at the start of each line"""
self.tab = tab
self.level = level
self.stream = stream
self.newline = newline
self.linestart = True
def write(self, buf, *args, **kwargs):
if self.closed:
raise ValueError("write to closed file")
if not buf:
# Quietly ignore printing nothing
# prevents an issue with print(end='')
return
indent = " " * (self.tab * self.level)
if self.linestart:
# The previous line has ended. Indent this one
self.stream.write(indent)
# Memorize if this ends with a newline
if buf.endswith(self.newline):
self.linestart = True
# Don't replace the last newline, as the indent would double
buf = buf[:-len(self.newline)]
self.stream.write(buf.replace(self.newline, self.newline + indent))
self.stream.write(self.newline)
else:
# Does not end on a newline
self.linestart = False
self.stream.write(buf.replace(self.newline, self.newline + indent))
# Pass some calls to internal stream
@property
def writable(self):
return self.stream.writable
@property
def encoding(self):
return self.stream.encoding
@property
def name(self):
return self.stream.name
with contextlib.redirect_stdout(StreamIndenter()) as indent:
indent.level = 2
print("this should be indented")
print("this shouldn't be indented")
以这种方式覆盖打印既不会破坏 print
的其他用途,又允许正确处理更复杂的用法。
例如:
with contextlib.redirect_stdout(StreamIndenter()) as indent:
indent.level = 2
print("this should be indented")
indent.level = 3
print("more indented")
indent.level = 2
for c in "hello world\n": print(c, end='')
print()
print("\n", end='')
print(end = '')
print("this shouldn't be indented")
格式正确为:
this should be indented
more indented
hello world
this shouldn't be indented