基于flask 2.0.2
Local其实来源于ThreadLocal。ThreadLocal可以简单理解为用来管理不同线程中具有同一变量名、同一逻辑、但有不同具体内容的数据,使得数据只在当前线程有效。这是直观表现以及通常用法;更准确来说是管理不同线程的数据,实现thread-safe和thread-specific
这么说的原因是,我们写代码时通常只起一个变量名,但它们指代很多不同具体的个体,类似于我们起的名是一个类,代码运行时处理的是这个类的多个实例。例如A家有小孩代号为X, B家有小孩代号也为X,我们在获取它们家小孩名字时都访问了X,但X是两个不同的人。
flask的request就是基于上面的思想,后台接收到的请求都是叫request,但是是不同用户发出的request(相当于上面例子中的不同家庭的X), 即不同上下文中request具体内容不同
werkzeug实现自己Local的原因#
- flask需要处理线程和协程(如gevent)两种情况,而threading.ThreadLocal()只支持线程这一种情景
- 单纯的threading.ThreadLoacl()不能满足web框架的需求
contextvar的出现是因为协程的原因。其用来管理不同协程之间的数据,可以类比上面的ThreadLocal。且contextvars是用来取代ThreadLocal的,官方文档中描述:
在多并发环境中,有状态上下文管理器应该使用上下文变量,而不是 threading.local() 来防止他们的状态意外泄露到其他代码。
werkzeug.local.py包含内容#
function: get_ident()
class: Contextvar
function: release_local()
class: Local()
class: LocalStack()
class: LocalManager()
class: _ProxyLookup()
class: _ProxyIOp()
function: _l_to_r_op()
class: LocalProxy()
1. function: get_ident()
#
返回当前线程的标识,这个标识是唯一的,这意味着我们能唯一找到目标线程。
若使用了greenlet
,则get_ident()
实则为greenlet.getcurrent()
否则为threading.get_ident()
这个函数将在werkzeug 2.1
移除
2. Class: Contextvar
#
python3.7
加入了contextvars.ContextVar
即上下文变量,用于管理不同协程的数据。
local.py
中处理ContextVar
的逻辑为:
- 优先使用
contextvars.ContextVar
- 检查是否使用
greenlet
或evenlet
,且判断是否给contextvar
打补丁
- 若不能使用
contextvars.ContextVar
或者没有打好补丁,那么使用flask实现的一个简单的ContextVar
,其含有一个storage字典,用于存储键值对:key=上下文标识, value=上下文字典
,上下文标识在线程时为线程id,为协程时为协程id;上下文字典则用来存储对应上下文的数据,通过上下文标识获得该字典。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class ContextVar: # type: ignore
"""A fake ContextVar based on the previous greenlet/threading
ident function. Used on Python 3.6, eventlet, and old versions
of gevent.
"""
def __init__(self, _name: str) -> None:
self.storage: t.Dict[int, t.Dict[str, t.Any]] = {}
def get(self, default: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
return self.storage.get(_get_ident(), default)
def set(self, value: t.Dict[str, t.Any]) -> None:
self.storage[_get_ident()] = value
|
- 注意
get()
, set()
的用法,get({})
表示,若上下文变量取不到值,则返回default
值{}
3. function: release_local()
#
为Local()
或者LocalStack()
清楚当前上下文的数据,实际上是调用了Local()
和LocalStack()
各自的__release_local__()
方法, 请继续阅读下文的Local()
和LocalStack()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
def release_local(local: t.Union["Local", "LocalStack"]) -> None:
"""Releases the contents of the local for the current context.
This makes it possible to use locals without a manager.
Example::
>>> loc = Local()
>>> loc.foo = 42
>>> release_local(loc)
>>> hasattr(loc, 'foo')
False
With this function one can release :class:`Local` objects as well
as :class:`LocalStack` objects. However it is not possible to
release data held by proxies that way, one always has to retain
a reference to the underlying local object in order to be able
to release it.
.. versionadded:: 0.6.1
"""
local.__release_local__()
|
4. class: Local()
#
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
|
class Local:
__slots__ = ("_storage",)
def __init__(self) -> None:
object.__setattr__(self, "_storage", ContextVar("local_storage"))
@property
def __storage__(self) -> t.Dict[str, t.Any]:
warnings.warn(
"'__storage__' is deprecated and will be removed in Werkzeug 2.1.",
DeprecationWarning,
stacklevel=2,
)
return self._storage.get({}) # type: ignore
@property
def __ident_func__(self) -> t.Callable[[], int]:
warnings.warn(
"'__ident_func__' is deprecated and will be removed in"
" Werkzeug 2.1. It should not be used in Python 3.7+.",
DeprecationWarning,
stacklevel=2,
)
return _get_ident # type: ignore
@__ident_func__.setter
def __ident_func__(self, func: t.Callable[[], int]) -> None:
warnings.warn(
"'__ident_func__' is deprecated and will be removed in"
" Werkzeug 2.1. Setting it no longer has any effect.",
DeprecationWarning,
stacklevel=2,
)
def __iter__(self) -> t.Iterator[t.Tuple[int, t.Any]]:
return iter(self._storage.get({}).items())
def __call__(self, proxy: str) -> "LocalProxy":
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
def __release_local__(self) -> None:
self._storage.set({})
def __getattr__(self, name: str) -> t.Any:
values = self._storage.get({})
try:
return values[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name: str, value: t.Any) -> None:
values = self._storage.get({}).copy()
values[name] = value
self._storage.set(values)
def __delattr__(self, name: str) -> None:
values = self._storage.get({}).copy()
try:
del values[name]
self._storage.set(values)
except KeyError:
raise AttributeError(name)
|
__storage__
将被移除,改名为符合变量命名约定的_storage
_storage
是一个ContextVar
,即上下文变量,对_storage
的set()
和get()
操作,是对对应上下文变量键值对进行操作。
__ident_func__
即为上文提到的get_ident()
函数,用于获取当前线程的id
- 实现了迭代器协议,是对
_storage.items()
的迭代(即对_storage
字典键值对的迭代)
- 实现了
__call__(self, proxy)
, 新实例化一个LocalProxy(local=local对象, proxy=上下文变量名)
对象, 例如:
1
2
|
loc = Local()
req = loc("request")
|
其中req
是一个LocalProxy
实例,构造参数为loc
实例和变量名"request"
5. __release_local__
是用来释放当前上下文(线程或协程)字典的内容,重新设置为空字典{}。参考上文的ContextVar().set()
方法
6. __getattr__(), __setattr__(), __delattr__
都是获取当前上下文字典,然后对字典内容进行操作
5. class: LocalStack
#
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
67
68
69
70
71
72
73
74
75
76
77
78
79
|
class LocalStack:
"""This class works similar to a :class:`Local` but keeps a stack
of objects instead. This is best explained with an example::
>>> ls = LocalStack()
>>> ls.push(42)
>>> ls.top
42
>>> ls.push(23)
>>> ls.top
23
>>> ls.pop()
23
>>> ls.top
42
They can be force released by using a :class:`LocalManager` or with
the :func:`release_local` function but the correct way is to pop the
item from the stack after using. When the stack is empty it will
no longer be bound to the current context (and as such released).
By calling the stack without arguments it returns a proxy that resolves to
the topmost item on the stack.
.. versionadded:: 0.6.1
"""
def __init__(self) -> None:
self._local = Local()
def __release_local__(self) -> None:
self._local.__release_local__()
@property
def __ident_func__(self) -> t.Callable[[], int]:
return self._local.__ident_func__
@__ident_func__.setter
def __ident_func__(self, value: t.Callable[[], int]) -> None:
object.__setattr__(self._local, "__ident_func__", value)
def __call__(self) -> "LocalProxy":
def _lookup() -> t.Any:
rv = self.top
if rv is None:
raise RuntimeError("object unbound")
return rv
return LocalProxy(_lookup)
def push(self, obj: t.Any) -> t.List[t.Any]:
"""Pushes a new item to the stack"""
rv = getattr(self._local, "stack", []).copy()
rv.append(obj)
self._local.stack = rv
return rv # type: ignore
def pop(self) -> t.Any:
"""Removes the topmost item from the stack, will return the
old value or `None` if the stack was already empty.
"""
stack = getattr(self._local, "stack", None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()
@property
def top(self) -> t.Any:
"""The topmost item on the stack. If the stack is empty,
`None` is returned.
"""
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
|
LocalStack
是在Local
的基础上实现的一个栈,栈被存在上下文变量self._local
中,基于上文提到Local
的原理,即通过上下文标识符获取当前上下文字典,而栈则存在key为"stack"对应的value中。stack实际上是一个list。
- pop()时,若只有一个元素,则返回这个元素,并将整个
self._local
释放(调用release_local()
)
__call__()
内有一个闭包函数,其作为LocalProxy
的构造参数, __call__
逻辑为对LocalStack()
的无参数调用时,将用栈顶元素生成一个LocalProxy
6. class: LocalManager()
#
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
class LocalManager:
"""Local objects cannot manage themselves. For that you need a local
manager. You can pass a local manager multiple locals or add them
later y appending them to `manager.locals`. Every time the manager
cleans up, it will clean up all the data left in the locals for this
context.
.. versionchanged:: 2.0
``ident_func`` is deprecated and will be removed in Werkzeug
2.1.
.. versionchanged:: 0.6.1
The :func:`release_local` function can be used instead of a
manager.
.. versionchanged:: 0.7
The ``ident_func`` parameter was added.
"""
def __init__(
self,
locals: t.Optional[t.Iterable[t.Union[Local, LocalStack]]] = None,
ident_func: None = None,
) -> None:
if locals is None:
self.locals = []
elif isinstance(locals, Local):
self.locals = [locals]
else:
self.locals = list(locals)
if ident_func is not None:
warnings.warn(
"'ident_func' is deprecated and will be removed in"
" Werkzeug 2.1. Setting it no longer has any effect.",
DeprecationWarning,
stacklevel=2,
)
@property
def ident_func(self) -> t.Callable[[], int]:
warnings.warn(
"'ident_func' is deprecated and will be removed in Werkzeug 2.1.",
DeprecationWarning,
stacklevel=2,
)
return _get_ident # type: ignore
@ident_func.setter
def ident_func(self, func: t.Callable[[], int]) -> None:
warnings.warn(
"'ident_func' is deprecated and will be removedin Werkzeug"
" 2.1. Setting it no longer has any effect.",
DeprecationWarning,
stacklevel=2,
)
def get_ident(self) -> int:
"""Return the context identifier the local objects use internally for
this context. You cannot override this method to change the behavior
but use it to link other context local objects (such as SQLAlchemy's
scoped sessions) to the Werkzeug locals.
.. deprecated:: 2.0
Will be removed in Werkzeug 2.1.
.. versionchanged:: 0.7
You can pass a different ident function to the local manager that
will then be propagated to all the locals passed to the
constructor.
"""
warnings.warn(
"'get_ident' is deprecated and will be removed in Werkzeug 2.1.",
DeprecationWarning,
stacklevel=2,
)
return self.ident_func()
def cleanup(self) -> None:
"""Manually clean up the data in the locals for this context. Call
this at the end of the request or use `make_middleware()`.
"""
for local in self.locals:
release_local(local)
def make_middleware(self, app: "WSGIApplication") -> "WSGIApplication":
"""Wrap a WSGI application so that cleaning up happens after
request end.
"""
def application(
environ: "WSGIEnvironment", start_response: "StartResponse"
) -> t.Iterable[bytes]:
return ClosingIterator(app(environ, start_response), self.cleanup)
return application
def middleware(self, func: "WSGIApplication") -> "WSGIApplication":
"""Like `make_middleware` but for decorating functions.
Example usage::
@manager.middleware
def application(environ, start_response):
...
The difference to `make_middleware` is that the function passed
will have all the arguments copied from the inner application
(name, docstring, module).
"""
return update_wrapper(self.make_middleware(func), func)
def __repr__(self) -> str:
return f"<{type(self).__name__} storages: {len(self.locals)}>"
|
- 构造参数
locals
为一个可迭代对象,其元素为Local
或者LocalStack
cleanup()
对locals
中的每一个Local
或者LocalStack
调用release_local()
make_middleware()
对WSGIApplication
添加各种关闭动作,如关闭session
和清空Local
middleware()
用于装饰器,功能类似于make_middleware
7. class: _ProxyLookup()
#
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
67
68
69
70
71
72
73
74
75
76
|
class _ProxyLookup:
"""Descriptor that handles proxied attribute lookup for
:class:`LocalProxy`.
:param f: The built-in function this attribute is accessed through.
Instead of looking up the special method, the function call
is redone on the object.
:param fallback: Call this method if the proxy is unbound instead of
raising a :exc:`RuntimeError`.
:param class_value: Value to return when accessed from the class.
Used for ``__doc__`` so building docs still works.
"""
__slots__ = ("bind_f", "fallback", "class_value", "name")
def __init__(
self,
f: t.Optional[t.Callable] = None,
fallback: t.Optional[t.Callable] = None,
class_value: t.Optional[t.Any] = None,
) -> None:
bind_f: t.Optional[t.Callable[["LocalProxy", t.Any], t.Callable]]
if hasattr(f, "__get__"):
# A Python function, can be turned into a bound method.
def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable:
return f.__get__(obj, type(obj)) # type: ignore
elif f is not None:
# A C function, use partial to bind the first argument.
def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable:
return partial(f, obj) # type: ignore
else:
# Use getattr, which will produce a bound method.
bind_f = None
self.bind_f = bind_f
self.fallback = fallback
self.class_value = class_value
def __set_name__(self, owner: "LocalProxy", name: str) -> None:
self.name = name
def __get__(self, instance: "LocalProxy", owner: t.Optional[type] = None) -> t.Any:
if instance is None:
if self.class_value is not None:
return self.class_value
return self
try:
obj = instance._get_current_object()
except RuntimeError:
if self.fallback is None:
raise
return self.fallback.__get__(instance, owner) # type: ignore
if self.bind_f is not None:
return self.bind_f(instance, obj)
return getattr(obj, self.name)
def __repr__(self) -> str:
return f"proxy {self.name}"
def __call__(self, instance: "LocalProxy", *args: t.Any, **kwargs: t.Any) -> t.Any:
"""Support calling unbound methods from the class. For example,
this happens with ``copy.copy``, which does
``type(x).__copy__(x)``. ``type(x)`` can't be proxied, so it
returns the proxy type and descriptor.
"""
return self.__get__(instance, type(instance))(*args, **kwargs)
|
_ProxyLookup()
是一个描述器(descriptor), 用于实现LocalProxy
的property。描述器内容请取官方文档阅读那篇详细的文档。描述器相当于一个中间人,对LocalProxy
属性的访问都先经过描述器。参考flask老版本可以发现以前并没有显式使用描述器,而是使用了一堆lambda函数,现在将使用lambda函数的共同逻辑抽象出来,实现了_ProxyLookup()
。这是使用_ProxyLookup
的原因
__init__(f, fallback, class_val)
中的逻辑:先判断f是否有__get__()
, 若有,则在当前逻辑下可以判定其为函数,并将其与目标obj进行绑定。绑定并不是在当下进行,而是调用时进行(当LocalProxy访问用_ProxyLookup
实现的property时,即调用__get__时)。所以返回一个执行绑定的函数bind_f
__get__(instance, owner)
:LocalProxy中所有使用_ProxyLookup
实现的property,每次访问它们就会调用对应的__get__
。在__get__
中执行对函数的绑定。
8. class: _ProxyIOp()
#
继承了_ProxyLookup
, 与_ProxyLookup
类似,用于实现LocalProxy
的一系列property,具体的是用来实现一系列i_operate
操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
__iadd__ = _ProxyIOp(operator.iadd)
__isub__ = _ProxyIOp(operator.isub)
__imul__ = _ProxyIOp(operator.imul)
__imatmul__ = _ProxyIOp(operator.imatmul)
__itruediv__ = _ProxyIOp(operator.itruediv)
__ifloordiv__ = _ProxyIOp(operator.ifloordiv)
__imod__ = _ProxyIOp(operator.imod)
__ipow__ = _ProxyIOp(operator.ipow)
__ilshift__ = _ProxyIOp(operator.ilshift)
__irshift__ = _ProxyIOp(operator.irshift)
__iand__ = _ProxyIOp(operator.iand)
__ixor__ = _ProxyIOp(operator.ixor)
__ior__ = _ProxyIOp(operator.ior)
|
9. function: _l_to_r_op()
#
将左操作转换为右操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
__radd__ = _ProxyLookup(_l_to_r_op(operator.add))
__rsub__ = _ProxyLookup(_l_to_r_op(operator.sub))
__rmul__ = _ProxyLookup(_l_to_r_op(operator.mul))
__rmatmul__ = _ProxyLookup(_l_to_r_op(operator.matmul))
__rtruediv__ = _ProxyLookup(_l_to_r_op(operator.truediv))
__rfloordiv__ = _ProxyLookup(_l_to_r_op(operator.floordiv))
__rmod__ = _ProxyLookup(_l_to_r_op(operator.mod))
__rdivmod__ = _ProxyLookup(_l_to_r_op(divmod))
__rpow__ = _ProxyLookup(_l_to_r_op(pow))
__rlshift__ = _ProxyLookup(_l_to_r_op(operator.lshift))
__rrshift__ = _ProxyLookup(_l_to_r_op(operator.rshift))
__rand__ = _ProxyLookup(_l_to_r_op(operator.and_))
__rxor__ = _ProxyLookup(_l_to_r_op(operator.xor))
__ror__ = _ProxyLookup(_l_to_r_op(operator.or_))
|
10. class: LocalProxy()
#
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
class LocalProxy:
"""A proxy to the object bound to a :class:`Local`. All operations
on the proxy are forwarded to the bound object. If no object is
bound, a :exc:`RuntimeError` is raised.
.. code-block:: python
from werkzeug.local import Local
l = Local()
# a proxy to whatever l.user is set to
user = l("user")
from werkzeug.local import LocalStack
_request_stack = LocalStack()
# a proxy to _request_stack.top
request = _request_stack()
# a proxy to the session attribute of the request proxy
session = LocalProxy(lambda: request.session)
``__repr__`` and ``__class__`` are forwarded, so ``repr(x)`` and
``isinstance(x, cls)`` will look like the proxied object. Use
``issubclass(type(x), LocalProxy)`` to check if an object is a
proxy.
.. code-block:: python
repr(user) # <User admin>
isinstance(user, User) # True
issubclass(type(user), LocalProxy) # True
:param local: The :class:`Local` or callable that provides the
proxied object.
:param name: The attribute name to look up on a :class:`Local`. Not
used if a callable is given.
.. versionchanged:: 2.0
Updated proxied attributes and methods to reflect the current
data model.
.. versionchanged:: 0.6.1
The class can be instantiated with a callable.
"""
__slots__ = ("__local", "__name", "__wrapped__")
def __init__(
self,
local: t.Union["Local", t.Callable[[], t.Any]],
name: t.Optional[str] = None,
) -> None:
object.__setattr__(self, "_LocalProxy__local", local)
object.__setattr__(self, "_LocalProxy__name", name)
if callable(local) and not hasattr(local, "__release_local__"):
# "local" is a callable that is not an instance of Local or
# LocalManager: mark it as a wrapped function.
object.__setattr__(self, "__wrapped__", local)
def _get_current_object(self) -> t.Any:
"""Return the current object. This is useful if you want the real
object behind the proxy at a time for performance reasons or because
you want to pass the object into a different context.
"""
if not hasattr(self.__local, "__release_local__"): # type: ignore
return self.__local() # type: ignore
try:
return getattr(self.__local, self.__name) # type: ignore
except AttributeError:
raise RuntimeError(f"no object bound to {self.__name}") # type: ignore
__doc__ = _ProxyLookup( # type: ignore
class_value=__doc__, fallback=lambda self: type(self).__doc__
)
# __del__ should only delete the proxy
__repr__ = _ProxyLookup( # type: ignore
repr, fallback=lambda self: f"<{type(self).__name__} unbound>"
)
__str__ = _ProxyLookup(str) # type: ignore
__bytes__ = _ProxyLookup(bytes)
__format__ = _ProxyLookup() # type: ignore
__hash__ = _ProxyLookup(hash) # type: ignore
__bool__ = _ProxyLookup(bool, fallback=lambda self: False)
__getattr__ = _ProxyLookup(getattr)
# __getattribute__ triggered through __getattr__
__setattr__ = _ProxyLookup(setattr) # type: ignore
__delattr__ = _ProxyLookup(delattr) # type: ignore
__dir__ = _ProxyLookup(dir, fallback=lambda self: []) # type: ignore
__class__ = _ProxyLookup(fallback=lambda self: type(self)) # type: ignore
__instancecheck__ = _ProxyLookup(lambda self, other: isinstance(other, self))
__subclasscheck__ = _ProxyLookup(lambda self, other: issubclass(other, self))
# __class_getitem__ triggered through __getitem__
__call__ = _ProxyLookup(lambda self, *args, **kwargs: self(*args, **kwargs))
__len__ = _ProxyLookup(len)
__length_hint__ = _ProxyLookup(operator.length_hint)
__getitem__ = _ProxyLookup(operator.getitem)
__setitem__ = _ProxyLookup(operator.setitem)
__delitem__ = _ProxyLookup(operator.delitem)
# __missing__ triggered through __getitem__
__iter__ = _ProxyLookup(iter)
__next__ = _ProxyLookup(next)
# 删除部分,方便阅读
|
self.__local
什么时候被初始化?答:在__init__
中,用到了叫名字改编
(name mangling)这一特性…在python中以双下划线开头的变量被进行“名字改编”为_className__val, 例如__local,被改编为_ProxyLocal__local
__init__(self, local, name)
,LocalProxy通常在两种常见下被实例化,一是通过Local(),二是通过LocalStack(), 前者直接通过Local()变量初始化,而后者通过一个函数(callable)初始化
_get_current_object()
获取LocalProxy
所代理的那个对象
- 注意那些用
ProxyLookup
描述符实现的property,比较重要的如__setattr__, __getattr__, __delattr__, __iter__
。在描述器中,都会先访问_get_current_object()
获得所代理的具体对象,然后绑定函数,再执行具体的操作