python学习(六)-定制类

形如__xxx__的变量或者函数名在python中有特殊用途

__str__和__repr__

把一个类的实例变成 str,就需要实现特殊方法 __str__()
__str__()用于显示给用户,而__repr__()用于显示给开发人员

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name

    __repr__ = __str__

    def __cmp__(self, s):
        if self.name < s.name:
            return -1
        elif self.name > s.name:
            return 1
        else:
            return 0

print(Student('Michael'))
Student object (name: Michael)

上述 Student 类实现了__cmp__()方法,__cmp__用实例自身self和传入的实例 s 进行比较

Student类实现了按name进行排序:

>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
>>> print sorted(L)
[(Alice: 77), (Bob: 88), (Tim: 99)]

len() 函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。

class Fib(object):

    def __init__(self, num):
        a, b, L = 0, 1, []
        for n in range(num):
            L.append(a)
            a, b = b, a+b
        self.numbers = L
        
    def __str__(self):
        return str(self.numbers)
        
    __repr__ = __str__
    
    def __len__(self):
        return len(self.numbers)

f = Fib(10)
print f    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
print len(f)   10

有理数加减乘除运算
加法运算:__add__
减法运算:__sub__
乘法运算:__mul__
除法运算:__div__
让int()函数正常工作,只需要实现特殊方法__int__()
让float()函数正常工作,只需要实现特殊方法__float__()

class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q

    def __int__(self):
        return self.p // self.q

    def __float__(self):
        return float(self.p) / self.q

print float(Rational(7, 2))
print float(Rational(1, 3))

__slots__限制添加属性

class Person(object):

    __slots__ = ('name', 'gender')

    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
只允许添加name和gender

一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()

class Fib(object):
    def __call__(self,num):
        a,b,L =0,1,[]
        for x in range(num):
            L.append(a)
            a,b = b,a+b
        return L

f = Fib()
print f(10)  [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

判断一个对象是否能被调用,能被调用的对象就是一个Callable对象

>>> callable(Student())
True
>>> callable(max)
True

只有在没有找到属性的情况下,才调用__getattr__

利用完全动态的__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

    __repr__ = __str__
试试:

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

__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

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

__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            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]

Python内置的@property装饰器就是负责把一个方法变成属性调用

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

推荐阅读更多精彩内容

  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 565评论 0 6
  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 88,632评论 26 537
  • Python进阶框架 希望大家喜欢,点赞哦首先感谢廖雪峰老师对于该课程的讲解 一、函数式编程 1.1 函数式编程简...
    Gaolex阅读 4,335评论 6 51
  • 定义类并创建实例 在Python中,类通过 class 关键字定义。以 Person 为例,定义一个Person类...
    绩重KF阅读 2,197评论 0 12
  • 魔法方法的详解: http://pyzh.readthedocs.io/en/latest/python-magi...
    魔法高校的劣等生阅读 1,192评论 0 15