blackclody

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
6.1什么是特殊方法
特殊方法又称为magic method(魔术方法)
  • 用于print的__str__
  • 用于len的__len__
  • 用于cmp的__cmp__
  • ...
特殊方法的特点:
  • 特殊方法定义在class中
  • 不需要直接调用
  • python的某些函数或操作符会调用对应的特殊方法
python定义的特殊方法,关于magic method更多的方法可参考http://pyzh.readthedocs.io/en/latest/python-magic-methods-guide.html
如何正确的实现特殊方法
  • 只需要编写用到的特殊方法
  • 有关联性的特殊方法都必须实现 如__getattr__,__setattr__,__delattr__

6.2 python中的__str__和__repr__

如果要把一个类的实例变成 str,就需要实现特殊方法__str__()

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

现在,在交互式命令行下用 print 试试:

>>> p = Person('Bob', 'male')
>>> print p
(Person: Bob, male)

但是,如果直接敲变量 p

>>> p
<main.Person object at 0x10c941890>

似乎__str__() 不会被调用。

因为 Python 定义了__str__()__repr__()两种方法,__str__()用于显示给用户,而__repr__()用于显示给开发人员。

有一个偷懒的定义__repr__的方法:

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

任务

请给Student 类定义__str____repr__方法,使得能打印出<Student: name, gender, score>:

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score
  1. class Person(object):
  2. def __init__(self, name, gender):
  3. self.name = name
  4. self.gender = gender
  5. class Student(Person):
  6. def __init__(self, name, gender, score):
  7. super(Student, self).__init__(name, gender)
  8. self.score = score
  9. def __str__(self):
  10. return '(Student:'+str( self.name )+','+str(self.gender )+ ','+str( self.score )+')'
  11.        # return 'Student:%s,%s,%s'%(self.name,self.gender,self.score)
  12. __repr__=__str__
  13. s = Student('Bob', 'male', 88)
  14. print s
6.3 python中的__cmp__

对 intstr 等内置数据类型排序时,Python的 sorted() 按照默认的比较函数 cmp 排序,但是,如果对一组 Student 类的实例排序时,就必须提供我们自己的特殊方法 __cmp__()

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

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

上述 Student 类实现了__cmp__()方法,__cmp__用实例自身self和传入的实例 进行比较,如果 self 应该排在前面,就返回 -1,如果 s 应该排在前面,就返回1,如果两者相当,返回 0。

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

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

注意: 如果list不仅仅包含 Student 类,则 __cmp__ 可能会报错:

L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello']
print sorted(L)

请思考如何解决。

任务

请修改 Student 的 __cmp__ 方法,让它按照分数从高到底排序,分数相同的按名字排序。

  1. class Student(object):
  2. def __init__(self, name, score):
  3. self.name = name
  4. self.score = score
  5. def __str__(self):
  6. return '(%s: %s)' % (self.name, self.score)
  7. __repr__ = __str__
  8. def __cmp__(self, s):
  9. if self.score > s.score:
  10. return -1
  11. elif self.score < s.score:
  12. return 1
  13. else:
  14. return cmp(self.name,s.name)
  15. L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
  16. print sorted(L)
输出
[(Alice: 99), (Tim: 99), (Bob: 88)]
6.4 python中__len__

如果一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。

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

例如,我们写一个 Students 类,把名字传进去:

class Students(object):
    def __init__(self, *args):
        self.names = args
    def __len__(self):
        return len(self.names)

只要正确实现了__len__()方法,就可以用len()函数返回Students实例的“长度”:

>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3

任务

斐波那契数列是由 0, 1, 1, 2, 3, 5, 8...构成。

请编写一个Fib类,Fib(10)表示数列的前10个元素,print Fib(10) 可以打印出数列的前 10 个元素,len(Fib(10))可以正确返回数列的个数10。

  1. class Fib(object):
  2. def __init__(self, num):
  3. self.num = num
  4. self.lst = []
  5. for i in range(self.num):
  6. if i == 0 or i == 1:
  7. self.lst.append(i)
  8. else:
  9. self.lst.append(self.lst[-1]+self.lst[-2])
  10. def __str__(self):
  11. return self.lst.__str__()
  12. def __len__(self):
  13. return len(self.lst)
  14. f = Fib(10)
  15. print f
  16. print len(f)
输出
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
10
6.5 python中的数据运算

Python 提供的基本数据类型 int、float 可以做整数和浮点的四则运算以及乘方等运算。

但是,四则运算不局限于int和float,还可以是有理数、矩阵等。

要表示有理数,可以用一个Rational类来表示:

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

p、q 都是整数,表示有理数 p/q。

如果要让Rational进行+运算,需要正确实现__add__:

class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
    def __add__(self, r):
        return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
    def __str__(self):
        return '%s/%s' % (self.p, self.q)
    __repr__ = __str__

现在可以试试有理数加法:

>>> r1 = Rational(1, 3)
>>> r2 = Rational(1, 2)
>>> print r1 + r2
5/6

任务

Rational类虽然可以做加法,但无法做减法、乘方和除法,请继续完善Rational类,实现四则运算。

提示:
减法运算:__sub__
乘法运算:__mul__
除法运算:__div__

  1. def gcd(a, b):
  2. if a < b:
  3. a, b = b, a
  4. while b != 0:
  5. temp = a % b
  6. a = b
  7. b = temp
  8. return a
  9. class Rational(object):
  10. def __init__(self, p, q):
  11. a = gcd(p,q)
  12. self.p = p/a
  13. self.q = q/a
  14. def __add__(self, r):
  15. return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
  16. def __sub__(self, r):
  17. return Rational(self.p * r.q - self.q * r.p, self.q * r.q)
  18. def __mul__(self, r):
  19. return Rational(self.p * r.p, self.q * r.q)
  20. def __div__(self, r):
  21. return Rational(self.p * r.q , self.q * r.p)
  22. def __str__(self):
  23. return '%s/%s'%(self.p,self.q)
  24. __repr__ = __str__
  25. r1 = Rational(1, 2)
  26. r2 = Rational(1, 4)
  27. print r1 + r2
  28. print r1 - r2
  29. print r1 * r2
  30. print r1 / r2
输出

3/4
1/4
1/8
2/1

6.6 python中类型转换

Rational类实现了有理数运算,但是,如果要把结果转为 int  float 怎么办?

考察整数和浮点数的转换:

>>> int(12.34)
12
>>> float(12)
12.0

如果要把 Rational 转为 int,应该使用:

r = Rational(12, 5)
n = int(r)

要让int()函数正常工作,只需要实现特殊方法__int__():

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

结果如下:

>>> print int(Rational(7, 2))
3
>>> print int(Rational(1, 3))
0

同理,要让float()函数正常工作,只需要实现特殊方法__float__()

请继续完善Rational,使之可以转型为float。

  1. class Rational(object):
  2. def __init__(self, p, q):
  3. self.p = p
  4. self.q = q
  5. def __int__(self):
  6. return self.p // self.q
  7. def __float__(self):
  8. return float(self.p)/self.q
  9. print float(Rational(7, 2))
  10. print float(Rational(1, 3))

6.7 python中@property

考察 Student 类:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

当我们想要修改一个 Student 的 scroe 属性时,可以这么写:

s = Student('Bob', 59)
s.score = 60

但是也可以这么写:

s.score = 1000

显然,直接给属性赋值无法检查分数的有效性。

如果利用两个方法:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.__score = score
    def get_score(self):
        return self.__score
    def set_score(self, score):
        if score < 0 or score > 100:
            raise ValueError('invalid score')
        self.__score = score

这样一来,s.set_score(1000) 就会报错。

这种使用 get/set 方法来封装对一个属性的访问在许多面向对象编程的语言中都很常见。

但是写 s.get_score() 和 s.set_score() 没有直接写 s.score 来得直接。

有没有两全其美的方法?----有。

因为Python支持高阶函数,在函数式编程中我们介绍了装饰器函数,可以用装饰器函数把 get/set 方法“装饰”成属性调用:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.__score = score
    @property
    def score(self):
        return self.__score
    @score.setter
    def score(self, score):
        if score < 0 or score > 100:
            raise ValueError('invalid score')
        self.__score = score

注意: 第一个score(self)是get方法,用@property装饰,第二个score(self, score)是set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。

现在,就可以像使用属性一样设置score了:

>>> s = Student('Bob', 59)
>>> s.score = 60
>>> print s.score
60
>>> s.score = 1000
Traceback (most recent call last):
  ...
ValueError: invalid score

说明对 score 赋值实际调用的是 set方法

任务

如果没有定义set方法,就不能对“属性”赋值,这时,就可以创建一个只读“属性”。

请给Student类加一个grade属性,根据 score 计算 A(>=80)、B、C(<60)。

  1. class Student(object):
  2. def __init__(self, name, score):
  3. self.name = name
  4. self.__score = score
  5. @property
  6. def score(self):
  7. return self.__score
  8. @score.setter
  9. def score(self, score):
  10. if score < 0 or score > 100:
  11. raise ValueError('invalid score')
  12. self.__score = score
  13. @property
  14. def grade(self):
  15. if self.score >= 80:
  16. grade = 'A'
  17. elif self.score >= 60:
  18. grade = 'B'
  19. else:
  20. grade = 'C'
  21. return grade
  22. s = Student('Bob', 59)
  23. print s.grade
  24. s.score = 60
  25. print s.grade
  26. s.score = 99
  27. print s.grade
6.8 python中的__slots__

由于Python是动态语言,任何实例在运行期都可以动态地添加属性。

如果要限制添加的属性,例如,Student类只允许添加 name、genderscore 这3个属性,就可以利用Python的一个特殊的__slots__来实现。

顾名思义,__slots__是指一个类允许的属性列表:

class Student(object):
    __slots__ = ('name', 'gender', 'score')
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score

现在,对实例进行操作:

>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'
Traceback (most recent call last):
  ...
AttributeError: 'Student' object has no attribute 'grade'

__slots__的目的是限制当前类所能拥有的属性,如果不需要添加任意动态的属性,使用__slots__也能节省内存。

任务

假设Person类通过__slots__定义了namegender,请在派生类Student中通过__slots__继续添加score的定义,使Student类可以实现name、genderscore 3个属性。

  1. class Person(object):
  2. __slots__ = ('name', 'gender')
  3. def __init__(self, name, gender):
  4. self.name = name
  5. self.gender = gender
  6. class Student(Person):
  7. __slots__ = Person.__slots__ +('score',)
  8. def __init__(self,name,gender,score):
  9. super(Student,self).__init__(name,gender)
  10. self.score = score
  11. s = Student('Bob', 'male', 59)
  12. s.name = 'Tim'
  13. s.score = 99
  14. print s.score
输出
99
6.9 python中的__call__

在Python中,函数其实是一个对象:

>>> f = abs
>>> f.__name__
'abs'
>>> f(-123)
123

由于 f 可以被调用,所以,f 被称为可调用对象。

所有的函数都是可调用对象。

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

我们把 Person 类变成一个可调用对象:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def __call__(self, friend):
        print 'My name is %s...' % self.name
        print 'My friend is %s...' % friend

现在可以对 Person 实例直接调用:

>>> p = Person('Bob', 'male')
>>> p('Tim')
My name is Bob...
My friend is Tim...

单看 p('Tim') 你无法确定 p 是一个函数还是一个类实例,所以,在Python中,函数也是对象,对象和函数的区别并不显著。

任务

改进一下前面定义的斐波那契数列:

class Fib(object):
    ???

请加一个__call__方法,让调用更简单:

>>> f = Fib()
>>> print f(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
 
  1. class Fib(object):
  2. def __call__(self,n):
  3. lst = []
  4. for i in range(n):
  5. if i == 0 or i == 1:
  6. lst.append(i)
  7. else:
  8. lst.append(lst[-1]+lst[-2])
  9. return lst.__str__()
  10. f = Fib()
  11. print f(10)
输出
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

 






posted on 2017-05-22 08:46  blackclody  阅读(363)  评论(0编辑  收藏  举报