Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

Gray-Ice

个人博客兼个人网站

注: 本文所介绍的技术原理是通过类函数对函数进行状态保存的包装,对包装后的函数进行的调用或访问,除了__call__之外所得到的结果可能都不是预期的结果!

写PyQt5对按钮点击事件进行函数绑定时有时候会需要类似C++那样带有固定参数的匿名函数,这样就省去了声明多个函数的麻烦步骤。

比如要在一个按钮点击后更改Label,然后设置一个全局变量:

1
2
3
4
5
def on_button_clicked(self, name: str, behavior: Callable):
"""name表示label的名称,behavior表示要设置的函数"""
self.current_button_label.setText(name)
TextCore.set_text_function(behavior)

上面这个函数如果要绑定到多个按钮上,并且每个按钮的name和behavior都不一样,使用lambda就会容易出现问题,这时可以使用partial类来进行包装:

1
2
3
4
5
6
7
8
9
10
11
12
from functools import partial  # 引入partial

# 不要在意缩进以及self,未定义变量等问题。
for button in buttons:
name = button['name']
behavior = button['behavior']
qbtn = Q.QPushButton(self)
qbtn.setText(name)
# 使用functools.partial()来达到C++中匿名函数的效果
qbtn.clicked.connect(partial(self.on_button_clicked, name, behavior))
self.h_box.addWidget(qbtn)

最后来看一下partial的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# Purely functional, no descriptor behaviour
class partial:
"""New function with partial application of the given arguments
and keywords.
"""

__slots__ = "func", "args", "keywords", "__dict__", "__weakref__"

def __new__(cls, func, /, *args, **keywords):
if not callable(func):
raise TypeError("the first argument must be callable")

if hasattr(func, "func"):
args = func.args + args
keywords = {**func.keywords, **keywords}
func = func.func

self = super(partial, cls).__new__(cls)

self.func = func
self.args = args
self.keywords = keywords
return self

def __call__(self, /, *args, **keywords):
keywords = {**self.keywords, **keywords}
return self.func(*self.args, *args, **keywords)

@recursive_repr()
def __repr__(self):
qualname = type(self).__qualname__
args = [repr(self.func)]
args.extend(repr(x) for x in self.args)
args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
if type(self).__module__ == "functools":
return f"functools.{qualname}({', '.join(args)})"
return f"{qualname}({', '.join(args)})"

def __reduce__(self):
return type(self), (self.func,), (self.func, self.args,
self.keywords or None, self.__dict__ or None)

def __setstate__(self, state):
if not isinstance(state, tuple):
raise TypeError("argument to __setstate__ must be a tuple")
if len(state) != 4:
raise TypeError(f"expected 4 items in state, got {len(state)}")
func, args, kwds, namespace = state
if (not callable(func) or not isinstance(args, tuple) or
(kwds is not None and not isinstance(kwds, dict)) or
(namespace is not None and not isinstance(namespace, dict))):
raise TypeError("invalid partial state")

args = tuple(args) # just in case it's a subclass
if kwds is None:
kwds = {}
elif type(kwds) is not dict: # XXX does it need to be *exactly* dict?
kwds = dict(kwds)
if namespace is None:
namespace = {}

self.__dict__ = namespace
self.func = func
self.args = args
self.keywords = kwds

通过partial的源码可以看到,该类会将函数的参数保存起来,然后在类被调用的时候调用函数并传递保存的参数。

评论



愿火焰指引你