python类的特殊方法(一)

类的特殊方法

类的特殊方法是为了 Python 解释器调用的,你自己并不需要调用它,也就是说没有 myobject.__len__() 这种写法,应该使用 len(myobject)

在执行 len(myobject) 的时候,如果 myobject 是一个自定义类的对象,那么python自己会去调用由你实现的 __len__ 方法。

如果 myobject 是一个list, str 或者字节序列(bytearray)的话,那么 Cpython 会抄个近路,__len__ 会直接返回 PyVaryObject 里的 ob_size 属性。 PyVaryObject 是表示内存中长度可变的内置对象的 C语言结构体。直接读取此值会比调用一个方法快得多

str

定义一个类

class Student(object):

    def __init__(self, name):
        self.name = name

当我们打印这个类的实例时:

>>> print(Student('Arya'))

这样很不方便观看,怎么按我们想要的格式打印出实例的信息呢?这时可以用到__str__方法

class Student(object):

    def __init__(self, name):
        self.name = name

    def __str__(self):
        return '%s object, name: %s' %(self.__class__, self.name)

这时,再打印:

>>> s = Student('Arya')
>>> print(s)
 object, name: Arya

可以按我们想要的格式展示了,可以打印实例所属的类,实例的一些属性、方法。

但是,当我们直接输入此实例时,显示的还是以前的值。

>>> s

这是因为,打印和直接显示变量调用的不是同一个函数,如上,打印调用的是实例的__str__方法,后者调用的是实例内置的__repr__方法。

当我们对以上两个方法进行重新定义时,便改变了打印和直接显示时显示的内容。

当重新定义了__str__时,我们可以直接让__repr__与其相等,即可改变直接显示变量的内容。

当定义中无 __str__, 而 python 又需要调用它时,解释器会用定义的 __repr__ 方法代替

class Student(object):

    def __init__(self, name):
        self.name = name

    def __str__(self):
        return '%s object, name: %s' %(self.__class__, self.name)
    __repr__ = __str

getattr

class Student(object):

    def __init__(self, name, id, score):
        self.name = name
        self.id = id
        self.score = score

定义类的几种属性,当我们尝试调用时:

>>> s = Student('Arya', '007', 95)
>>> s.name
'Arya'
>>> s.id
'007'
>>> s.score
95
>>> s.city
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'Student' object has no attribute 'city'

当调用不存在的属性时,会报错,要想不报错,返回指定的内容,就可以用__getattr__。注意,只有访问不存在的属性时,才会调用这个函数。

还有一个类似的特殊方法__getattribute__,定义了这个方法以后,当调用实例属性时,无论其存不存在,都将执行这个方法。如果同时定义了__getattribute____getattr__,那么调用属性时后者不会被被调用,除非在前者中调用了后者。

class Student(object):

    def __init__(self, name, id, score):
        self.name = name
        self.id = id
        self.score = score

    def __getattribute__(self, item):
        return object.__getattribute__(self, item)

另外,在__getattribute__的定义中,返回值不能是__self.__dict__[item],因为如果这样定义,还会调用这个__getattribute__函数,会形成无限递归,形成死循环。

class Student(object):

    def __init__(self, name, id, score):
        self.name = name
        self.id = id
        self.score = score

    def __getattr__(self, item):
        if item == 'city':
            return 'Beijing'

这时,我们再调用

>>> s = Student('Arya', '007', 95)
>>> s.city
'Beijing'
>>> s.sex

就会返回指定的内容,当我们再调用未指定的属性时,会返回None,要想只返回特定的内容,就要抛出AttributeError

class Student(object):

    def __init__(self, name, id, score):
        self.name = name
        self.id = id
        self.score = score

    def __getattr__(self, item):
        if item == 'city':
            return 'Beijing'
        raise AttributeError('Student object has no attribute %s' %(self.__class__, item))

setattr

如果我在程序运行期间想为其增加某种属性怎么办,可以使用__setattr__:

class Student(object):

    def __init__(self, name, id, score):
        self.name = name
        self.id = id
        self.score = score

    def __setattr__(self, key, value):
        self.__dict__[key] = value

这时我们就可以使用s.key = value了:

>>> s = Student('Arya', '007', 95)
>>> s.sex = 'girl'
>>> s.sex
'girl'

增加的属性是实例属性,存储在实例的__dict__中。

call

如果想让创建的实例可以当作函数调用,可以使用__call__方法:

class average(object):

    def __init__(self, *args):
        self.count = 0
        self.add = 0
        for x in args:
            self.count += 1
            self.add += x

    def __call__(self):
        return self.add/self.count

如此:

>>> s = average(10, 9, 8, 7)
>>> s.count
4
>>> s.add
34
>>> s()
8.5

还可以在__call__定义中加参数

getitem

class Fib(object):
    """
    >>> Fib()[0]
    1
    >>> Fib()[1]
    1
    >>> Fib()[2]
    2
    >>> Fib()[3]
    3
    >>> Fib()[4]
    5
    >>> Fib()[10]
    89
    """
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a+b
        return a

if __name__ == '__main__':
    import doctest
    doctest.testmod()

如果想实现按照下标访问元素的方法就要使用 __getitem__ 方法,

另外,需要注意的是,传入的参数可能是索引数字,也可能是切片,如果是未对切片作相应处理会报错:

class Fib(object):
    """
    >>> Fib()[0]
    1
    >>> Fib()[1]
    1
    >>> Fib()[2]
    2
    >>> Fib()[3]
    3
    >>> Fib()[4]
    5
    >>> Fib()[10]
    89
    >>> Fib()[0:5]
    [1, 1, 2, 3, 5]
    """
    def __getitem__(self, n):
        if isinstance(n, int):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a+b
            return a
        if isinstance(n, slice):
            start = n.start
            stop = n.stop
            if start == None:
                start = 0
            L = []
            a, b = 1, 1
            for x in range(stop):
                L.append(a)
                a, b = b, a+b
            L = L[start:stop]
            return L
        
    
if __name__ == '__main__':
    import doctest
    doctest.testmod()

同时,把对象视为 dict , __getitem__ 的参数也可能是一个可以作为 key 的对象,比如 str

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

    def __getitem__(self, key):
        return self.__dict__[key]
        
>>> s = Student('Arya', 'girl')
>>> s.name
'Arya'
>>> s['name']
'Arya'


- tips: `__getattr__` 和 `__getattribute__` 是用来访问实例对象属性的, `__getitem__`是可以通过索引访问对应值,也可以将对象视为 dict, 访问其 key

与其对应的是 `__setitem__` ,可以给对象新增 key 。

你可能感兴趣的:(python类的特殊方法(一))