python中实现定制类的特殊方法总结


Posted in Python onSeptember 28, 2014

看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。

__slots__我们已经知道怎么用了,__len__()方法我们也知道是为了能让class作用于len()函数。

除此之外,Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。

__str__

我们先定义一个Student类,打印一个实例:

>>> class Student(object):

...     def __init__(self, name):

...         self.name = name

...

>>> print Student('Michael')

<__main__.Student object at 0x109afb190>

打印出一堆<__main__.Student object at 0x109afb190>,不好看。

怎么才能打印得好看呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了:

>>> class Student(object):

...     def __init__(self, name):

...         self.name = name

...     def __str__(self):

...         return 'Student object (name: %s)' % self.name

...

>>> print Student('Michael')

Student object (name: Michael)

这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。

但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看:

>>> s = Student('Michael')

>>> s

<__main__.Student object at 0x109afb310>

这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。

解决办法是再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:

class Student(object):

    def __init__(self, name):

        self.name = name

    def __str__(self):

        return 'Student object (name=%s)' % self.name

    __repr__ = __str__

__iter__

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:

class Fib(object):

    def __init__(self):

        self.a, self.b = 0, 1 # 初始化两个计数器a,b
    def __iter__(self):

        return self # 实例本身就是迭代对象,故返回自己
    def next(self):

        self.a, self.b = self.b, self.a + self.b # 计算下一个值

        if self.a > 100000: # 退出循环的条件

            raise StopIteration();

        return self.a # 返回下一个值

现在,试试把Fib实例作用于for循环:

>>> for n in Fib():

...     print n

...

1

1

2

3

5

...

46368

75025

__getitem__

Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:

>>> Fib()[5]

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

TypeError: 'Fib' object does not support indexing

要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:
class Fib(object):

    def __getitem__(self, n):

        a, b = 1, 1

        for x in range(n):

            a, b = b, a + b

        return a

现在,就可以按下标访问数列的任意一项了:
>>> f = Fib()

>>> f[0]

1

>>> f[1]

1

>>> f[2]

2

>>> f[3]

3

>>> f[10]

89

>>> f[100]

573147844013817084101

但是list有个神奇的切片方法:
>>> range(100)[5:10]

[5, 6, 7, 8, 9]

对于Fib却报错。原因是__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:
class Fib(object):

    def __getitem__(self, n):

        if isinstance(n, int):

            a, b = 1, 1

            for x in range(n):

                a, b = b, a + b

            return a

        if isinstance(n, slice):

            start = n.start

            stop = n.stop

            a, b = 1, 1

            L = []

            for x in range(stop):

                if x >= start:

                    L.append(a)

                a, b = b, a + b

            return L

现在试试Fib的切片:
>>> f = Fib()

>>> f[0:5]

[1, 1, 2, 3, 5]

>>> f[:10]

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

但是没有对step参数作处理:
>>> f[:10:2]

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

也没有对负数作处理,所以,要正确实现一个__getitem__()还是有很多工作要做的。

此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以作key的object,例如str。

与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。

总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。

__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类似:

http://api.server/user/friends
http://api.server/user/timeline/list

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

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

class 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

试试:
>>> 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()来调用。能不能直接在实例本身上调用呢?类似instance()?在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()

My name is Michael.

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

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

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

>>> callable(Student())

True

>>> callable(max)

True

>>> callable([1, 2, 3])

False

>>> callable(None)

False

>>> callable('string')

False

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

小结

Python的class允许定义许多定制方法,可以让我们非常方便地生成特定的类。

Python 相关文章推荐
Python判断某个用户对某个文件的权限
Oct 13 Python
python去掉行尾的换行符方法
Jan 04 Python
python爬虫获取多页天涯帖子
Feb 23 Python
python使用Flask操作mysql实现登录功能
May 14 Python
Python实现曲线拟合操作示例【基于numpy,scipy,matplotlib库】
Jul 12 Python
利用Django提供的ModelForm增删改数据的方法
Jan 06 Python
python自动发微信监控报警
Sep 06 Python
python爬虫中多线程的使用详解
Sep 23 Python
python爬虫开发之使用Python爬虫库requests多线程抓取猫眼电影TOP100实例
Mar 10 Python
Python中常见的数制转换有哪些
May 27 Python
Keras SGD 随机梯度下降优化器参数设置方式
Jun 19 Python
pycharm配置安装autopep8自动规范代码的实现
Mar 02 Python
python之wxPython菜单使用详解
Sep 28 #Python
python中lambda函数 list comprehension 和 zip函数使用指南
Sep 28 #Python
python之wxPython应用实例
Sep 28 #Python
Python实现从url中提取域名的几种方法
Sep 26 #Python
Python实现的一个简单LRU cache
Sep 26 #Python
python网络编程实例简析
Sep 26 #Python
python的re模块应用实例
Sep 26 #Python
You might like
深入分析使用mysql_fetch_object()以对象的形式返回查询结果
2013/06/05 PHP
php 批量替换程序的具体实现代码
2013/10/04 PHP
PHP会话控制:Session与Cookie详解
2014/09/27 PHP
使用PHP生成PDF方法详解
2015/01/23 PHP
php访问数组最后一个元素的函数end()用法
2015/03/18 PHP
php实现购物车功能(下)
2016/01/05 PHP
超清晰的document对象详解
2007/02/27 Javascript
FormValidate 表单验证功能代码更新并提供下载
2008/08/23 Javascript
javaScript 删除字符串空格多种方法小结
2012/10/24 Javascript
node.js中的fs.fchown方法使用说明
2014/12/16 Javascript
jQuery判断多个input file 都不能为空的例子
2015/06/23 Javascript
jquery单击事件和双击事件冲突解决方案
2016/03/02 Javascript
原生js获取iframe中dom元素--父子页面相互获取对方dom元素的方法
2016/08/05 Javascript
AngularJS表单详解及示例代码
2016/08/17 Javascript
微信小程序实现锚点定位楼层跳跃的实例
2017/05/18 Javascript
详解vuejs几种不同组件(页面)间传值的方式
2017/06/01 Javascript
微信小程序实现跟随菜单效果和循环嵌套加载数据
2017/11/21 Javascript
vue.js vue-router如何实现无效路由(404)的友好提示
2017/12/20 Javascript
详解Angular结合zTree异步加载节点数据
2018/01/20 Javascript
js动态设置select下拉菜单的默认选中项实例
2018/08/21 Javascript
apicloud拉起小程序并传递参数的方法示例
2018/11/21 Javascript
nodejs简单抓包工具使用详解
2019/08/23 NodeJs
js实现简单的点名器随机色实例代码
2020/09/20 Javascript
基于Vue2实现移动端图片上传、压缩、拖拽排序、拖拽删除功能
2021/01/05 Vue.js
python通过imaplib模块读取gmail里邮件的方法
2015/05/08 Python
Django发送html邮件的方法
2015/05/26 Python
Python基于csv模块实现读取与写入csv数据的方法
2018/01/18 Python
Django框架登录加上验证码校验实现验证功能示例
2019/05/23 Python
python opencv 实现对图像边缘扩充
2020/01/19 Python
Python实现括号匹配方法详解
2020/02/10 Python
Python函数参数分类原理详解
2020/05/28 Python
印尼最大的在线购物网站:MatahariMall.com
2016/08/26 全球购物
idealfit英国:世界领先的女性健身用品和运动衣物品牌
2017/11/25 全球购物
描述Cookie和Session的作用,区别和各自的应用范围,Session工作原理
2015/03/25 面试题
企业厂务公开实施方案
2014/03/26 职场文书
党的群众路线教育实践活动总结材料
2014/10/30 职场文书