Python学习笔记__7.3章定制类

1、概览

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

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

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

 

1.1、__str__()  和 __repr__()

        1、__str__()

修改print(instance) 显示的值

# 正常打印instance

>>> print (s)

<__main__.Student object at 0x00000005AD1EAAC8>

# 在类中加入__str__() 方法

...     def __str__(self):

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

# 再次打印

>>> print(s)

Student object (name: Bob)

        2、__repr__()

修改 instance 显示的值

# 正常instance 显示

>>> s

<__main__.Student object at 0x00000005AD1EAAC8>

# 在类中加入__str__() 方法

...     def __repr__(self):

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

# 再次打印

>>>s

Student object (name: Bob)

  1. __str__()  和 __repr__() 的区别

__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串

通常__str__()和__repr__()代码都是一样的。所以可以这样写

    def __str__(self):

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

    __repr__ = __str__

 

1.2、__iter__()

我们知道,只有 iterable对象可以进行 for…in… 循环。

如果想让一个类被用于for循环,就需要__iter__() 方法。该方法返回一个iterable 对象。然后,Python的for循环会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

  1. 以斐波那契数列为例,写一个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 # 返回斐波那契数列

# 调用

>>> for n in Fib():   # for 循环打印 Fib() 实例

...     print(n)

 

1.3、__getitem__()

    1、__getitem__()方法,可以实现 实例像list 一样的下标取值

# 类的编写

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]  # 0 就是传入的参数 n,返回的值是a的值

1

注:

虽然 f 可以像list一样,进行下标取值,但不能进行切片。

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

        2、__getitem__()方法,实现 实例的切片

# 类的编写

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:      # 判断a值 是否应该加入L

                    L.append(a)

                a, b = b, a + b  # a的值一直再变。只是上面if 要判断是否保存a值

            return L

# 调用

>>> f = Fib()

>>> f[0:5]

[1, 1, 2, 3, 5]

 

  • 总结

Fib(),现在可以下标取值 或s切片取值,但切片的处理没有步长和负数,所以,要正确实现一个__getitem__()还是有很多工作要做的。

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

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

1.3、关于切片对象slice的思考

slice一般是跟在list后面的。所以不能通过判断切片对list的操作,如L[0:5] 这样的表达式,来判断slice的数据类型。

在Python中,有一个slice对象,它的类型就是 slice。所以猜想:

上面的代码中的f[0:5],n==[0:5]。__getitem__(),会把[0:5],解释成 slice(0,5,none)。从而判断传入的参数是 slice。

n是slice的实例,有start,stop属性。所以通过 start = n.start ,stop = n.stop,来获取 slice 开始和结束的范围

 

1.4、__getattr__()

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。那么,我们可以通过__getattr__()方法,动态返回一个属性

__getattr__() 方法返回属性的前提是,在__getattr__()的函数体里,这个属性符合你设置的条件

        1、动态返回属性

class Student(object):

    def __init__(self):

        self.name = 'Michael'

    def __getattr__(self, attr):

        if attr=='score':

            return 99

# 我们只定义了name属性,如果尝试获取score属性,就会交由__getattr__() 方法处理

>>> s.score

99

# 如果请求的属性不符合__getattr__()方法的判断条件呢

>>> s.gender    # 无显示。正常会报错

>>> print(s.gender)  # 显示None,正常会报错

None

        2、动态返回函数

class Student(object):

    def __getattr__(self, attr):

        if attr=='age':

            return lambda: 25

# 调用

>>> s.age()

25

        3、抛出错误

使用__getattr__()方法后,如果 某属性 class里没定义,__getattr__()里也没有,是不会抛出错误的。针对这些属性,如果要正常抛出错误,需要在__getattr__() 方法里定义

class Student(object):

    def __getattr__(self, attr):

        if attr=='age':

            return lambda: 25

        raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)

1.5、__call__()

我们调用实例方法时,我们用instance.method()来调用。而定义一个__call__()方法,就可以直接对实例进行调用

class Student(object):

    def __init__(self, name):

        self.name = name

    def __call__(self):

        print('My name is %s.' % self.name)

 

# 调用

>>> s()  # self参数不要传入

My name is Michael.

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

 

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

>>> callable(max)

True

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

False

 

2、例题

1、利用完全动态的__getattr__,写出一个链式调用。完全动态的生成URL

class Chain(object):

    def __init__(self,path=""): # 初始化实例,Chain().path 为空

        self._path=path

    def __getattr__(self,path): # 使用类没有定义的属性,就调用

        return Chain("%s/%s"%(self._path,path))

    def __call__(self,path):  # 直接对实例进行调用,将实例当作类似函数一样调用      

          return Chain("%s/%s"%(self._path,path))

    def __str__(self):  # 实例显示的值

        return self._path

    __repr__=__str__

 

# 调用

print(Chain().a.b.user("ChenTian").c.d)

/a/b/user/ChenTian/c/d

调用解析

  1. 创建了一个实例Chain()。

  2. Chain().a,类没有a属性,调用__getattr__() 方法,将 实例名和属性名传进去,返回一个Chain(/a)实例

  3. Chain(/a).b,操作同上,返回一个Chain(/a/b)实例

  4. Chain(/a/b).user("ChenTian"),先会执行getattr返回Chain实例,Chain(/a/b/user("ChenTian"))

然后由于有__call__方法,可以直接对实例调用。此时就会调用__call__方法。传入的path="ChenTian"。

然后返回Chain(/a/b/user/ChenTian)

  1. Chain(/a/b/user/ChenTian).c.d 操作同第2步


猜你喜欢

转载自blog.51cto.com/12758568/2116785