Python3之定制类

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

  Python中还有许多有特殊用途的函数,能够帮助咱们定制类python

  __str__app

  先定义一个Student类,打印一个实例函数

>>> class Student(object):
...     def __init__(self,name):
...         self.name=name
... 
>>> print(Student('Zhangsan'))
<__main__.Student object at 0x7f8a4830a748>

  打印出<__main__.Student object at 0x7f8a4830a748>很差看,不直观spa

  怎么才能打印的好看呢?只须要定义好__str__()方法,返回一个好看的字符串就能够了调试

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

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

  可是若是直接在终端敲变量而不用print,打印出来仍是同样很差看htm

>>> Student('Zhangsan')
<__main__.Student object at 0x7f8a4830a8d0>

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

解决办法是再定义一个__repr__()。可是一般__str__()__repr__()代码都是同样的,因此,有个偷懒的写法:blog

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

  

  __iter__

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

class Fib(object):
    def __init__(self):
        #初始化两个计数器
        self.a,self.b=0,1
    def __iter__(self):
        #实例自己就是迭代对象,返回本身
        return self
    def __next__(self):
        self.a,self.b=self.b,self.a+self.b
        #设置循环退出条件
        if self.a>10000:
            raise StopIteration()
        return self.a

for n in Fib():
    print(n)

  输出

>>> for n in Fib():
...     print(n)
...
1
1
2
3
5
...
46368
75025

  

  __grtitem__

  Fib实例虽然能做用于for循环,看起来和list有点像,可是,把它当成list来使用仍是不行,好比,取第5个元素

>>> Fib()[5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Fib' object is not subscriptable

  要表现的象list那样按照下标取出元素,须要实现__getitem__()方法 

  special_getitem.py

class Fib(object):
    def __getitem__(self,n):
        a,b=1,1
        for x in range(n):
            a,b=b,a+b
        return a

  如今,就能够按下标访问数列的任意一项了

>>> f[0]
1
>>> f[1]
1
>>> f[100]
573147844013817084101

  f[0]至关于把n=0参数传递给方法__getitem__(0),由于n=0,for循环不执行,返回a=1,

       f[1]至关于把n=1参数传递给方法__getitem__(1),由于n=1,for循环执行一次a=b=1,b=a+b=2 返回值为a=1

       f[2]至关于把n=2参数传递给方法__geritem__(2),第一次循环,a=b=1,b=a+b=2 再次循环 a=b=2,b=a+b=1+2=3 两次循环结束返回值a=2

       以此类推

  可是list有个切片的方法对应Fib确报错

>>> f[1:5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __getitem__
TypeError: 'slice' object cannot be interpreted as an integer

  缘由是__getitem__()传入的参数多是一个int,也能够是一个切片对象slice,因此要判断   

       special_getitem.py

class Fib(object):
    def __getitem__(self,n):
        #n是整数
        if isinstance(n,int):
            a,b=1,1
            for x in range(n):
                a,b=b,a+b
            return a
        #n是切片相似[0:2]
        if isinstance(n,slice):
            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
f=Fib()
print(f[0])
print(f[1])
print(f[2])
print(f[3])
print(f[0:3])

  若是传递的参数是整数则和上面的方法不变,一次返回一个整数

      若是传递的参数是切片slice则从切片的start开始至stop结束返回一个列表

      运行结果以下

1
1
2
3
[1, 1, 2]

  若是传递参数是切片,执行过程分析f[0:1]切片取索引为0及第一个元素的列表

if判断[0:1]是slice
start=n.start 因此start=None
stop=n.stop   stop=1
if判断若是start为None则把start置为0
定义初始两位数为1,1
定义空列表L=[]
执行循环 for x in range(1):
x=1执行第一次循环
if判断1>=1知足条件
执行append语句后L=[1]
接着执行往下语句a,b=b,a+b
a=b,b=a+b是同时执行执行完毕后
a=1 b=2
退出for循环返回列表L=[1]

  若是传递的切片参数为[0:2]

if判断[0:2]是slice
start=n.start 因此start=None
stop=n.stop   stop=2
if判断若是start为None则把start置为0
定义初始两位数为1,1
定义空列表L=[]
执行循环 for x in range(1):
x=0执行第一次循环
if判断0>=0知足条件
执行append语句后L.append(a) 及L.append(1) L=[1]
接着执行往下语句a,b=b,a+b
a=b,b=a+b是同时执行执行完毕后
a=1 b=2
for循环返回列表L=[1]
x=1执行第二次循环
if判断1>=1知足条件
执行append语句L.append(a)及L.append(1) L=[1,1]
执行a,b=b,a+b执行完毕后
a=2 b=3 由于stop=2执行两次之后退出循环 返回列表L=[1,1]

  若是传递是切片参数是[0:3]

if判断[0:3]是slice
start=n.start 因此start=None
stop=n.stop   stop=3
if判断若是start为None则把start置为0
定义初始两位数为1,1
定义空列表L=[]
执行循环 for x in range(1):
x=0执行第一次循环
if判断0>=0知足条件
执行append语句后L.append(a) 及L.append(1) L=[1]
接着执行往下语句a,b=b,a+b
a=b,b=a+b是同时执行执行完毕后
a=1 b=2
for循环返回列表L=[1]
x=1执行第二次循环
if判断1>=0知足条件
执行append语句L.append(a)及L.append(1) L=[1,1]
执行a,b=b,a+b执行完毕后
a=2 b=3
x=2执行第三次循环
if判断2>=0知足条件
执行append语句L.append(a)及L.append(2) L=[1,1,2]
由于stop=3执行三次次之后退出循环 返回列表L=[1,1,2]

  以此类推

  若是start不是 0

for循环会继续执行,可是尚未到start处由于不知足x>=start条件因此L.append(a)不会执行,不会往列表内追加元素
可是a,b=b,a+b会继续执行

  

  关于切片及切片函数slice参考:http://www.javashuo.com/article/p-oxwdyocv-ea.html

  

  可是以上仍是有缺陷没有对step参数作处理

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

  虽然加了参数:2步数仍是1

  也没有对负数进行处理,因此,要正确实现如下__getitem__()还有不少工做要作

  

  此外,若是把对象当作dict,__getitem__()的参数也多是一个能够作key的object,例如str。

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

 

  __getattr__

  正常状况下,当咱们调用类的方法或属性时,若是不存在,就会报错。好比定了Student类

class Student(object):
  def __init__(self):
    self.name="Zhangsan"

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

>>> s.name
'Zhangsan'
>>> s.score
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

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

  要避免这个错误,除了能够加上一个score属性外,python还有另外一个机制,那就是写一个__getattr__()方法,动态返回一个属性

  special_getattr.py

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 __init__(self):
        self.name = 'Michael'
    def __getattr__(self, attr):
        if attr=='score':
            return 99
    def __getattr__(self,attr):
        if attr=='age':
            return lambda:25

s=Student()
print(s.age())

  调用方式改成s.age

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

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

>>> s=Student()
>>> s.name
'Michael'
>>> s.aba
>>> print(s.aba)
None

  改为以下

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

  

  __call__一个对象实例能够有本身是属性和方法,当咱们调用实例方法时,咱们用instance.method()来调用,能不能直接在实例自己上调用呢?

  任何类,只须要定义一个__call__()方法,就能够直接对实例进行调用

  special_call.py

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

  调用方法以下

>>> s=Student('Zhansan')
>>> s()
My name is Zhansan.

  __call__()还能够定义参数。对实例进行直接调用就比如对一个函数进行调用同样,因此你彻底能够把对象当作函数,把函数当作对象,由于这二者之间原本就没啥根本的区别。

若是你把对象当作函数,那么函数自己其实也能够在运行期动态建立出来,由于类的实例都是运行期建立出来的,这么一来,咱们就模糊了对象和函数的界限。

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

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

  经过callable()函数,咱们就能够判断一个对象是不是“可调用”对象。

相关文章
相关标签/搜索