Python 回调和首参数绑定

Stella981
• 阅读 976

函数参数的绑定和调用方式

这里想讨论的问题是,如果把python的方法作为参数传递给其他对象调用,那么相应的python实例是如何绑定的?

class C:
    def callback(self):
        print('callback')

    @staticmethod
    def sc():
        print('sc')

    @classmethod
    def cc(cls):
        print('cc')

def f():
    print('f')

def f_with_1_parameter(param):
    print(param)

def do_something(cb):
    print('do something')
    cb()

if __name__ == '__main__':
    instance = C()
    # 它们绑定的是谁?
    do_something(instance.callback)
    do_something(C.sc)
    do_something(C.cc)
    do_something(f)
    do_something(instance.f_with_1_parameter)
    do_something(C.f_with_1_parameter)

首参数绑定

python 有这样的约定,实例方法的第一个参数必然是self,名字可以不叫self,但是第一个参数总是调用方法的类实例。

类方法的第一个参数必然是cls,名字可以不叫cls,但是第一个参数总是调用该方法的类对象。

依照直觉,可以写出这样的代码。

class C:
    @classmethod
    def class_method_1(cls):
        print('class method 1')

    def instance_method_1(self):
        print('instance method 1')

def do_something(cb):
    cb()

cb(C.class_method_1)
cb(C().instance_method_1)

并且它们确实能很好地工作。

或许你们注意到了,C()创建了一个匿名的C类实例,然后将这个实例的instance_method_1交给了cb,这看起来是不安全的。

在C++中,相同的方法需要显式地将实例指针与实例方法绑定成一个函数对象。C++的实例方法确实是一个“函数”,它的this没有python的self那么魔幻。

直觉上感觉不安全,是因为 python 的对象是自动回收的,而且我们能看得出来:C()似乎没有引用了。

但其实不是,实例方法instance.method实际上已经绑定了instancemethod,在实例方法也失去引用之前,instance不会被释放。

这也是为什么instance_method = instance.method后,instance_method()工作得和instance.method()一样好的原因:这是python的魔法,将实例和实例方法绑定在了一起。

语法糖?

那么这种绑定是不是语法糖呢...

我也不知道(诶嘿)。语言规范查阅起来太麻烦了,稍稍不求甚解一下,看看 CPython 这个官方的实现是怎么处理的吧。

类.实例方法

首先是第一问:实例方法如果用class.method的方式调用,self参数会绑定成类对象吗?

class C:
    def method(self):
        print(self)

C.method()

输出是

Traceback (most recent call last):
  File "打码:/打码/打码/打码/test.py", line 6, in <module>
    C.method()
TypeError: method() missing 1 required positional argument: 'self

看来并不会,class.method的方式并不会将class绑定为self传递给method,只有通过实例.方法的情况会绑定实例对象到self参数。

实例.类方法

class C:

    @classmethod
    def method(cls):
        print (cls)

instance = C()
instance.method()

这次的结果比较神奇,因为它正常执行了。

<class '__main__.C'>

并没有报错。

这应该是类似于原型链的机制在其中作祟:instance.class_field是可以正常访问的,不像是C++访问类变量时需要class::class_field这样的特殊语法。

classmethod 装饰器做的事情感觉像是给一个函数包了一层重载了__get__的类,然后这个包了__get__descriptor打入另一个owner类里,可以参考下这篇文章Python3 Data Model

做个具体的实例。

def f(cls):
    print(cls)

def C:
    pass

C.f = classmethod(f)
C.f() # 正常执行

这应该是 classmethod装饰器实现的方式了(猜测)。

运行时改变类?

如果在运行时给类插入一个实例方法呢?如果插入的实例方法正常运作,说明这仅仅是一个语法糖:实例.方法会绑定self参数,仅此而已。

def f(self):
    print(self)

class C:
    pass

C.f = f
instance = C()
instance.f()

输出是

<__main__.C object at 0x038262B0>

看起来没错了,这仅仅是一个语法糖。

总结

实例.方法的形式访问到的其实是一个这样子的实例。

class WhoCare:
    def __init__(self, instance, method):
        """
        在调用 实例.方法时,实例.方法构成了这样的一个奇特对象

        当然了,别当真。只是为了说明这种语法背后做了什么的理解。
        """
        self._i=instance
        self._method=method

    def __call__(self,*args,**kwargs):
        """
        反正固定了第一个 instance 参数,其他参数照样送进去就 ok
        """
        self._method(self._i, *args, **kwargs)
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Python3:sqlalchemy对mysql数据库操作,非sql语句
Python3:sqlalchemy对mysql数据库操作,非sql语句python3authorlizmdatetime2018020110:00:00coding:utf8'''
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这