Discuss / Python / 对这章内容的一点理解

对这章内容的一点理解

Topic source

東NANIAN

#1 Created at ... [Delete] [Delete and Lock User]

实现Chain().users('michael').repos输出/users/michael/repos

无图无真相,上代码:

class Chain(object):
    def __init__(self, path=''):
       self.__path = path

   def __getattr__(self, path):
       return Chain('%s/%s' % (self.__path, path))

   def __call__(self, path):
       return Chain('%s/%s' % (self.__path, path))

   def __str__(self):
       return self.__path

   __repr__ = __str__

   print(Chain().users('michael').repos) # /users/michael/repos

定制类这章教程一直很迷惑,来来回回看了好几天,总算是有点理解。(每天在脑子最清醒的时候返回来学最困惑的)

Chain().users('michael').repos这是一串什么东西,链式调用?没学过,分分钟想跳过看下一章。

分解成能看懂的:

urls = Chain()    # 初始化一个实例
urls = urls.users    # 查找实例的一个属性
urls = urls('michael)    # 调用一个函数
urls = urls.repos    # 还是实例的属性

还原成常规方式就成了最基础的东西。

1.第一步

urls = Chain()

初始化一个实例,此时urls等于,因为定义了默认值path=''

2.第二步

urls = urls.users

查找urls的属性users,没找到定义的属性,那就调用__getattr__方法,返回了一个函数调用:

def __getattr__(self, users):
    return Chain('%s/%s' % (self.__path, users))

这一步调用了Chain(),而且把要查找的属性users作为参数传递了进去,也就是Chain(users),那么根据Chain()的逻辑,最后返回的是:/users,然后跟上一步的结果拼接,最终返回:/users

3.第三步

urls = urls('michael')

每次迷茫都在这一步。举例子理解一下:

f = abs
print(f.__name__)    # 'abs'
print(f(-123))    # 123
print(callable(f))    # True

由于f可以被调用,那就可以称:f为可调用对象;

def func():
    pass

print(callable(func))    # True

函数本身就可以被调用,这点无需质疑,所以函数也是可调用对象;

class Test(object):
    def __init__(self):
    pass

print(callable(Test))    # True

类本身也是可调用对象,不然怎么生成实例化对象;

class Test(object):
    def __init__(self):
    pass

test = Test()
print(callable(test))    # False

咦?发现个不一样的,类的实例化对象不可以被调用,那它就仅仅只是个纯粹的对象了;

终于对课程上描述__call__的话有所理解了, 对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。

你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。 原来是为了让实例化对象和函数一样可以被使用;

那这一步就简单了,可以抽象的理解为:

class urls(Chain):
    def __init__(self, path='/users'):
       self.__path = path

   def __getattr__(self, path):
       return urls(('%s/%s' % (self.__path, path)))

    def __call__(self, path):
       return urls(('%s/%s' % (self.__path, path)))

   def __str__(self):
       return self.__path

   __repr__ = __str__

然后调用urls = urls('michael'),那么最终返回:/users/michael

4.最后一步

urls = u.repos

它和第二步没什么区别,所以urls最终为:/users/michael/repos;

over!

谢谢!

细心的学者

return Chain()  返回一个对象

终于对课程上描述__call__的话有所理解了,对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。

你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。

这里是不是可以看做因为python是鸭子语言,那么只要有__call__方法,那么就是callable的

拿笔小星

#6 Created at ... [Delete] [Delete and Lock User]

很棒!一开始也看晕了,看了你的demo。嗯,理解更深刻了,关键就在于方法返回的是`Chain()`对象。

每次调用类Chain()都会运行一下类里面的__init__()函数进行初始化里面的相应属性,在这里边相当于重建实例覆盖之前的实例,具体来看

Step 1:
Chain()  # 实例化
Step 2:
Chain().users
# 由于没有给实例传入初始化对应属性的具体信息,从而自动调用__getattr__()函数,从而有:
Chain().users = Chain('\users') # 这是重建实例
Step 3:
Chain().users('michael')
Chain().users('michael') = Chain('\users')('michael') # 这是对实例直接调用,相当于调用普通函数一样
# 关键就在这步,上面的朋友没有说明晰(并不是说你们不懂),这一步返回的是Chain('\users\michael'),再一次重建实例,覆盖掉Chain('\users'),
#记 renew = Chain('\users\michael'), 此时新实例的属性renew.__path = \users\michael;
Step 4:
Chain().users('michael').repos
# 这一步是查询renew实例的属性repos,由于没有这一属性,就会执行__getattr__()函数,再一次返回新的实例Chain('\users\michael\repos')并且覆盖点之前的实例,
# 这里记 trinew =Chain('\users\michael\repos'),不要忘了,一单定义了一个新的实例,就会执行__init__方法;
Step 5:
print(Chain().users('michael').repos) = print(trinew)  #由于我们定义了__str__()方法,那么打印的时候就会调用此方法,据此方法的定义,打印回来的是trinew的__path属#性,即——\users\michael\repos  。至此,我们也把所有定义的有特殊用的方法都用上了,完毕。

林宗主

#8 Created at ... [Delete] [Delete and Lock User]

谢谢您嘞

mark 点个赞

讲的太好了,mk


Reply