Python学习笔记(9),Python面向对象高级特性2 -- 定制类和多重继承

多重继承

Python与Java不同,Python可以多重继承,在设计类时,可以考虑MixIn设计,一个类继承多个类,使其具有多个功能。

定制类

介绍了以下几种类的方法:
__str__(), __iter__(), __next__(), __getitem__(), __getattr__(), __call__()

__str__类似java中的toString()方法,print一个类调用的就是类的__str__方法。

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

直接显示变量调用的不是__str__(),而是__repr__()__str__返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,用于调式服务。通常两个方法代码一样

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

__iter__类似于Java中的Iterator。一个类想被用于for循环,就必须实现一个__iter__()方法。该方法返回一个迭代对象,循环不断调用__next__方法获取下一个值,直到遇到StopIteration错误时退出循环。

class Fib:
    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 > 20:
            raise StopIteration()
        return self.a
for n in Fib():
    print(n)
1
1
2
3
5
8
13

__getitem__可以使该类像list一样取第几个元素

def getitem(self, n):  # 定义getitem函数,也可以在函数定义内定义
    a, b = 1, 1
    for x in range(n):
        a, b = b, a+b
    return n
Fib.__getitem__ = getitem # 添加__getitem__ 方法。
f = Fib() 
print(f[0])
print(f[5])
0
5

如果想要实现切片的方法,需要对__getitem__()方法进一步改进
类似,如果把对象看成是dict__getitem__()的参数是一个可以作为keyobject,例如str。与之对应有__setitem__()__delitem()__方法。
这些方法可以让自己定义的类和Python自带的list, tuple, dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。

__getattr__()方法,动态返回一个属性

class Student2:
    def __init__(self):
        self.name = 'Michael'
    def __getattr__(self, attr):
        if attr == 'score':
            return 99

当调用的属性不存在时,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样就有机会返回score的值

s = Student2()
print(s.name)
print(s.score)
print(s.zipcode)
Michael
99
None

__getattr__也可以返回函数。
只有在没有找到属性的情况下,才会调用__getattr__,如果是已有属性,不会在__getattr__中查找。
没有定义的调用s.zipcode会返回None,因为__getattr__默认返回就是None
为了避免这种情况,让类只响应几个特定属性,需要抛出AttributeError错误。

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

s3 = Student3()
s3.abc
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

 in 
      6 
      7 s3 = Student3()
----> 8 s3.abc


 in __getattr__(self, attr)
      3         if attr == 'age':
      4             return lambda: 25
----> 5         raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
      6 
      7 s3 = Student3()


AttributeError: 'Student' object has no attribute 'abc'

把一个类的所有方法和属性动态处理了。可针对完全动态地情况作调用。

例如,利用完全动态地__getattr__,写出一个链式调用

class Chain:
    def __init__(self, path = ''):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path)) 
    
    def __str__(self):
        return self._path
    __repr__ = __str__
chain = Chain()  # create a new instance
print(chain.status)   # this will call __getattr__('/status)
print(chain.user)     # this will call __getattr__('/user')
print(chain.status.user.timeline) 
/status
/user
/status/user/timeline

解析
__getattr__('/status') will call Chain('/status), now, _path = '/status'
then __getattr__('user') will call Chain('/status/user'), now, _path = '/status/user'
continue this chain, _path variable can be dynamically setted.

Chain().user.fan.documents.markdown_file   # since __repr__ = __str__, we can directly print it without print() method.
/user/fan/documents/markdown_file

__call__可以直接对实例进行调用

class Student4:
    def __init__(self, name):
        self.name = name
    def __call__(self):
        print('My name is %s' % self.name)
s4 = Student4('Fan')
s4()
My name is Fan

__call__还可以定义参数,对实例进行直接调用就和对一个函数进行调用一样,所以我们完全可以把对象看成函数,把函数看成是对象,因为两者根本就没啥区别。

如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。

那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数是Callable的,它可以被调用,同样我们上面定义的带有__call__()的类实例也是Callable的。

callable(max)   # True, 函数是callable的
callable(Student4('Fan'))  # True Student4的实例是callable的
callable('str')  # False, 一个string不能被调用,不是callable  
False

你可能感兴趣的:(Python)