继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

【Python入门】15.面向对象编程之 定制类常用的定制方法

慕码人8056858
关注TA
已关注
手记 1092
粉丝 350
获赞 1320

目录

面向对象编程
__str__( )
__repr__( )
__iter__( )与__next__( )
__getitem__( )
__setitem__( )
__delitem__( )
__getattr__( )
__call__( )

面向对象编程

之前已经介绍过形似__xxx__的是特殊变量或函数,如__init__、__slots__等。这一节将介绍更多的特殊方法,来帮助我们定制自己定义到class。

__str__( )

__str__( )方法可以根据定义的字符串,在打印实例时返回所定义的字符串。

在没有使用__str__( )方法时:

>>>class Stu(object):                                              #定义一个class...    def __init__(self, name):
...        self.name = name
...>>>print(Stu('Ming'))                                              #打印一个实例<__main__.Stu object at 0x00000000020E8550>

使用了__str__( )方法后:

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

这样可以清楚地看到实例内部的重要数据,需要注意的是,__str__( )方法里的return后面跟的必须是字符串。

__repr__( )

__repr__( )的用途与__str__( )很像,两者的区别是__str__( )返回的是用户看到的字符串,而__repr__( )返回的是程序开发者看到的字符串。

也就是说,如按上面的定义,在交互模式中直接调用实例,还是不会返回想要的字符串的。

>>>Stu('Ming')
<__main__.Stu object at 0x00000000020E8550>

我们把上面的__str__( )改为__repr__( ),再试试。

>>>class Stu(object):...    def __init__(self, name):
...        self.name = name
...    def __repr__(self):
...        return 'Stu object (name: %s)' % self.name
...>>>Stu('MIng')
Stu object (name: MIng)

成功返回。此时如果使用print,还是能够返回想要的字符串的。

>>>print(Stu('Ming'))
Stu object (name: MIng)

如果同时定义__str__( )和__repr__( )呢?

class Stu(object):
    def __init__(self, name):        self.name = name    def __repr__(self):        return 'Stu object (name: %s)' % self.name    def __str__(self):        return 'str'
>>>Stu('MIng')
Stu object (name: MIng) 
>>>print(Stu('Ming'))
str

可见在这种情况下,print是优先考虑__str__( )的。

总而言之,__str__( )面向用户,而__repr__( )面向程序员。所以如果想要在所有环境下统一显示的话,直接用__repr__即可。若要区分,则定义两个。一般情况下显示的内容时相同的,可用__repr__( ) = __str__( )来简化定义。

__iter__( )与__next__( )

for...in循环是作用于可迭代对象,那想要让自己定义的类可用for...in循环,就必须把自己变成可迭代对象。

__iter__( )方法的用途就是返回一个可迭代对象Iterable

__next__( )方法的用途是定义一个循环的逻辑而返回下一个循环值(用了之后就是一个迭代器对象了Iterator)。

那么结合两种方法,for..in循环就会不断调用该可迭代对象作用于__next__( )方法得出下一个循环值。直到遇到StopIteration错误时退出循环。

举个例子,如生成斐波那契数列,写个Fib类。

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 > 1000:                                  # 退出循环的条件
            raise StopIteration()        return self.a                                      # 返回下一个值

用for..in作用于Fib的实例

>>>for n in Fib():...    print(n)1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

实际上,不用__iter__( )方法也可以得到斐波那契数列,只不过需要不断调用next( )来拿取下一个值,而用了__iter__( )方法之后变成可迭代对象,就可以用for...in循环来轻松拿取了。

__getitem__( )

__getitem__( )方法的用途是让实例像list那样按照下标取元素。__getitem__( )方法需要传入两个参数,一个是实例本身,另一个是下标。当检测到有[ ]时就会运行__getitem__( )。

>>>class A(object):...    def __getitem__(self, key):                       # 定义一个__getitem__方法,key参数是下标...        print('Call function __getitem__')
...        print(key + 1)>>>a = A()>>>a[1]                                                  # 在这里会把a传给self,而把1传给keyCall function __getitem__                                # 当检测到有[]时,就会执行__getitem__方法2                                                        # 返回的是key+1,即1+1=2

利用__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

若还想要想list那样具有切片功能,就需要判断传入的参数是整数还是切片,然后进一步做输出处理。像这样(以下代码转自廖雪峰的官方网站)

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

运行结果:

>>> f = Fib()>>> f[0:5]
[1, 1, 2, 3, 5]

当然还有地方需要改进,如对step参数的处理,对负数的处理等等,这些都可以在后面继续完善,这里值简单介绍__getitem__( )的用法。

__setitem__( )

__setitem__( )同样可以让实例像list那样按照下标取元素,同时还可以给key赋值value,所以需要传入三个参数,分别是实例本身,键key,值value。当检测到有形如[ ] = 的赋值语句时就会执行__setitem__( )方法。

>>>class A(object):...    def __setitem__(self, key, value):            # 定义一个__setitem__方法,key参数是键,value是值...        print('Call function __setitem__')
...        print(value)>>>a = A()>>>a[1] = 10                                         # 在这里会把a传给self,而把1传给key,把10传给valueCall function __getitem__                            # 当检测到有[] = 时,就会执行__setitem__方法10                                                   # 打印value

__delitem__( )

__delitem__( )用来删除指定key的元素,需要传入self和key两个参数。当检测到del时就会执行__delitem__( )方法。

结合__getitem__( )、__setitem__( )和__delitem__( ),方法,给个简单的例子再帮助理解。

class A(object):
    def __init__(self, start = 0):
        print('Call function __init__')        self.start = start        self.dict = {}                              # 定义一个dict

    def __getitem__(self, key):                     # 定义获取值的方法
        print('Call function __getitem__')        try:
            return self.dict[key]                   # 如果有对key赋值,则返回key对应的value
        except KeyError:
            return key                              # 如果没有对key赋值,则返回key本身

    def __setitem__(self, key, value):              # 定义赋值方法
        print('Call function __setitem__')        self.dict[key] = value    def __delitem__(self, key):                     # 定义删除元素的方法
        print('Call function __delitem__')
        del self.dict[key]
a = A()                                             # 创建A的一个实例aCall function __init__ 
a[1] = 10                                           # 执行赋值方法__setitem__ Call function __setitem__ 
a[2] = 20                                           # 执行赋值方法__setitem__Call function __setitem__ 
a[1]                                                # 执行取值方法__getitem__,[1]有对应的值10Call function __getitem__ 
10 
a.dict                                              #dict属性中已有的值{1: 10, 2: 20} 
del a[1]                                            #删除dict属性中,key为[1]的值Call function __delitem__ 
a.dict
{2: 20}

__getattr__( )

当我们尝试调用一个没有被定义的属性或方法时,会出现错误。而__getattr__( )的用途是当调用不存在的属性时,就会执行__getattr__( )尝试返回另外的值。

未定义__getattr__( )时

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

a = Stu('Ming')
>>>a.name
Ming>>>a.ageAttributeError: 'Stu' object has no attribute 'age'

定义了__getattr__( )之后

class Stu(object):
    def __init__(self, name):        self.name = name    
    def __getattr__(self, attr):        return 1
>>>a.name
Ming>>>a.age                          # 这里的age将会传给__getattr__()的第二个参数attr1

也能返回一个函数

class Stu(object):
    def __init__(self, name):        self.name = name    
    def __getattr__(self, attr):        return lambda: 10

当然,我们能根据__getattr__( )的参数,去完善定义,像这样:

class Stu(object):

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

__call__( )

__call__( )方法可以使实例变为可调用对象,当尝试直接调用实例时,会执行__call__( )方法。

class Stu(object):
    def __init__(self, name):        self.name = name    
    def __call__(self):
        print('I am %s' % self.name)
>>>a = Stu('Ming')>>>a()                                           # 注意需要加()I am Ming

__call__( )还能定义参数,这样的话就可以实例对象看成一个函数了。这就模糊了对象和函数的区别,其实很多时候我们需要区分的是对象是否可调用,可以用callable( )函数来判断。

>>> callable(Stu())True>>> callable(max)True>>> callable([1, 2])False>>> callable(None)False



作者:三贝_
链接:https://www.jianshu.com/p/2e7bcb8e9493


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP