Python面向对象编程之 定制类常用的定制方法

目录

面向对象编程
__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传给key
Call 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传给value
Call 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的一个实例a
Call function __init__ 
a[1] = 10                                           # 执行赋值方法__setitem__ 
Call function __setitem__ 
a[2] = 20                                           # 执行赋值方法__setitem__
Call function __setitem__ 
a[1]                                                # 执行取值方法__getitem__,[1]有对应的值10
Call 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.age
AttributeError: '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__()的第二个参数attr
1

也能返回一个函数

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
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

猜你喜欢

转载自blog.csdn.net/pansaky/article/details/89336719