python:订制类详解编程语言

__str__

__repr__

__iter__

__next__

__getitem__

__getattr__


正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。比如定义Student类:

class Student(object): 
 
    def __init__(self): 
        self.name = 'Michael'

调用name属性,没问题,但是,调用不存在的score属性,就有问题了:

>>> s = Student() 
>>> print(s.name) 
Michael 
>>> print(s.score) 
Traceback (most recent call last): 
  ... 
AttributeError: 'Student' object has no attribute 'score'

错误信息很清楚地告诉我们,没有找到score这个attribute。

要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。修改如下:

class Student(object): 
 
    def __init__(self): 
        self.name = 'Michael' 
 
    def __getattr__(self, attr): 
        if attr=='score': 
            return 99

当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:

>>> s = Student() 
>>> s.name 
'Michael' 
>>> s.score 
99

返回函数也是完全可以的: 

class Student(object): 
 
    def __getattr__(self, attr): 
        if attr=='age': 
            return lambda: 25

只是调用方式要变为:

>>> s.age() 
25

注意,只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。

此外,注意到任意调用如s.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:

class Student(object): 
 
    def __getattr__(self, attr): 
        if attr=='age': 
            return lambda: 25 
        raise AttributeError('/'Student/' object has no attribute /'%s/'' % attr)

这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。

这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。

举个例子:

现在很多网站都搞REST API,比如新浪微博、豆瓣啥的,调用API的URL类似:

如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。

利用完全动态的__getattr__,我们可以写出一个链式调用:

lass Chain(object): 
 
    def __init__(self, path=''): 
        self._path = path 
 
    def __getattr__(self, path): 
        return Chain('%s/%s' % (self._path, path)) 
 
    def __str__(self): 
        return self._path 
 
    __repr__ = __str__

试试:

>>> Chain().status.user.timeline.list 
'/status/user/timeline/list'

这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!

还有些REST API会把参数放到URL中,比如GitHub的API:

GET /users/:user/repos

调用时,需要把:user替换为实际用户名。如果我们能写出这样的链式调用:

Chain().users('michael').repos

就可以非常方便地调用API了。有兴趣的童鞋可以试试写出来。

 

__call__


一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。能不能直接在实例本身上调用呢?在Python中,答案是肯定的

任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。请看示例:

class Student(object): 
    def __init__(self, name): 
        self.name = name 
 
    def __call__(self): 
        print('My name is %s.' % self.name)

调用方式如下:

>>> s = Student('Michael') 
>>> s() # self参数不要传入 
My name is Michael.

__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。

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

那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例:

>>> callable(Student()) 
True 
>>> callable(max) 
True 
>>> callable([1, 2, 3]) 
False 
>>> callable(None) 
False 
>>> callable('str') 
False

通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。

 

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/17626.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论