【20200417踩坑记录】类方法调用实例方法,报错:TypeError: format() missing 1 required positional argument: 'data'(附解决方案)

问 题

类方法 get_access_token 调用父类实例方法 format() 报错。

报错信息

test_wework.py:15 (TestBaseApi.test_get_access_token)
self = <test_wework.TestBaseApi object at 0x00000194F9DB8B38>

    def test_get_access_token(self):
>       assert WeWork.get_access_token(self.secret) != ""

test_wework.py:17: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class 'api.wework.WeWork'>
corpsecret = 'W1a-_z8wERVJRacyrzhuf5W4vHqBTkd2ghvTwAfivwI'

    @classmethod
    def get_access_token(cls, corpsecret):
        r = requests.get(
            "https://qyapi.weixin.qq.com/cgi-bin/gettoken",
            params={
                "corpid": cls._corpid,
                "corpsecret": corpsecret
            },
            proxies=proxies,
            verify=False
        )
>       cls.format(r.json())
E       TypeError: format() missing 1 required positional argument: 'data'

..\api\wework.py:50: TypeError

源代码

class BaseApi:
   def format(self, data):
       print(json.dumps(data, indent=2)) 
    
class WeWork(BaseApi):
   @classmethod
   def get_access_token(cls, corpsecret):
       r = requests.get(
           "https://qyapi.weixin.qq.com/cgi-bin/gettoken",
           params={
               "corpid": cls._corpid,
               "corpsecret": corpsecret
           },
           proxies=proxies,
           verify=False
       )
    	# 这里报错了!!!说我没有带data参数。。。我传了呀。。。
        cls.format(r.json())
        return r.json()["access_token"]

思 考

debuging…

  • 实例方法调用父类实例方法没问题

    class Department(BaseApi):
        _secret = "-tzpw8mha37Q7pDSfu22XbLg9EQ38Kx9Dr9aBsX94VU"
    
        def list(self):
            r = requests.get(
                "https://qyapi.weixin.qq.com/cgi-bin/department/list",
                params={"access_token": WeWork.get_token(self._secret)},
                # headers={"content-type": "application/json; charset=UTF-8"},
                headers={"content-type": "charset=utf-8"},
                proxies=proxies,
                verify=False
            )
            # 同样的语句,在非类方法中,可以正常调用format!
            self.format(r.json())
            return r.json()
    
  • 既然提示少一个参数,那我随便加个值吧

    class BaseApi:
       def format(self, data):
           print(json.dumps(data, indent=2)) 
        
    class WeWork(BaseApi):
       @classmethod
       def get_access_token(cls, corpsecret):
           r = requests.get(
               "https://qyapi.weixin.qq.com/cgi-bin/gettoken",
               params={
                   "corpid": cls._corpid,
                   "corpsecret": corpsecret
               },
               proxies=proxies,
               verify=False
           )
        	# 随便加了个空字符串,case可以跑通!!!!
            cls.format("", r.json())
            return r.json()["access_token"]
    

我还是不满足,感觉还是有很多疑问…

疑 问

  1. 类实例方法的self代表什么意思?为什么类的实例方法之间互相调用不用带self?

    self代表类实例对象本身。Python解释器看到self会自动将该方法和类对象绑定,实例方法之间可以隐式调用(不用带self)。

  2. classmethod类方法中的cls又代表什么?

    cls代表类对象本身

  3. 为什么类方法不能隐式调用实例方法?类方法调用实例方法的原理是什么?如何调用比较优雅?

    定义类方法的时候,会默认带一个参数 cls(可随意,默认是cls),代表类对象本身。Python解释器会自动绑定类属性和类方法,也就是说,类方法可以隐式调用类属性或者类方法(不用带cls)。

    比如,类方法get_token()调用类方法get_access_token(),就不需要带cls参数

class WeWork(BaseApi):
  _corpid = "ww84f2624b18176321"
  _token = {}

  @classmethod
  def get_token(cls, secret):
      # done: 需要保存token,不用每次调用都去请求
      if secret not in cls._token.keys():
          # 类方法之间可以隐式调用,不用带cls参数!!!!
          cls._token[secret] = cls.get_access_token(secret)
      cls.format("", cls._token)
      return cls._token[secret]

  # 类函数,直接通过类调用,不需要创建对象
  @classmethod
  def get_access_token(cls, corpsecret):
      r = requests.get(
          "https://qyapi.weixin.qq.com/cgi-bin/gettoken",
          params={
              "corpid": cls._corpid,
              "corpsecret": corpsecret
          },
          proxies=proxies,
          verify=False
      )
      cls.format("", r.json())
      return r.json()["access_token"]

解决方案

如果类对象想要调用实例方法,分两种情况:

	1. 如果需要用到self参数,则传入类实例对象进去 

	2. 如果不需要用到self参数,则可以随便传值

举个栗子

class Dream:
    ambition = "Travel around the world"

    def let_it_go(self):
        # 引用了self,必须传入类实例对象
        print(self.ambition)
        print("Wish you make your dream come true.")

    def i_have_a_dream(self):
        print("I HAVE A DREAM")

    @classmethod
    def how_to_get(cls):
        # TypeError: let_it_go() missing 1 required positional argument: 'self'
        # cls.let_it_go()
        # 传入类实例对象Dream()
        cls.let_it_go(Dream())
        # 没有用到self参数,可以随便传值
        cls.i_have_a_dream("")
        print("Just RUN!")


if __name__ == '__main__':
    d = Dream()

    # 传入Dream的类实例对象d
    Dream.let_it_go(d)
    # TypeError: let_it_go() missing 1 required positional argument: 'self'
    # Dream.let_it_go()

    Dream.how_to_get()

运行结果

Travel around the world
Wish you make your dream come true.
Travel around the world
Wish you make your dream come true.
I HAVE A DREAM
Just RUN!

Process finished with exit code 0

参考资料

1 个赞

如有错漏,欢迎指出,多谢了 :handshake: