面向对象进阶

面向对象进阶

主要内容:

  1. 多态
  2. isinstance, issubclass
  3. __str__
  4. __del__
  5. __call__
  6. __slots__
  7. __getattr__ , __setattr__,  __delattr__
  8. __getitem__, __setitem__, __delitem__
  9. 运算符重载
  10. 迭代器协议
  11. 上下文管理

1. 多态

  多态的官方解释就是多个不同的对象可以响应同一个方法, 产生不同的结果. 通俗的讲就是多个对象拥有相同的方法, 我们可以通过这个统一的方法来调用管理这些对象,多态的存在简化了调用的工作,在python中,多态处处可见,比如len方法, str方法可以转换任意对象为字符串形式.而实现的方式也多种多样, 使用抽象类强制规定必须实现同样的方法, 以及Python最推崇的鸭子类型, 都是多态的实现方法. 因此可以看出多态并不是一种语法, 只是一种状态, 形式, 一种编程方式.

class Chicken(object):

    def spawn(self):
        print('下鸡蛋')


class Duck(object):

    def spawn(self):
        print('下鸭蛋')


class Goose(object):

    def spawn(self):
        print('下鹅蛋')


animals = [Chicken(), Duck(), Goose()]
for animal in animals:
    animal.spawn()

  上面3个类都不相同, 但是有相同的方法下蛋, 这样就可以利用for循环统一调用由这些对象组成的集合体的统一方法了, 大大简化了调用者的工作.

2. isinstance, issubclass

  这两个方法属于内置函数, 是判断类型相关的函数.

  isinstance(obj, type): 判断一个对象是不是另一个类的实例对象.

  issubclass(subclass, class): 判断一个类是不是另一个类的子类

class Foo:
    pass


class Bar(Foo):
    pass


print(isinstance(int, object))     # True
print(issubclass(int, object))     # True

bar = Bar()
print(isinstance(bar, Foo))        # True
print(isinstance(Bar, Foo))        # False
print(isinstance(Bar, object))     # True
print(isinstance(object, object))  # True
print(isinstance(Bar, type))       # True
print(isinstance(object, type))    # True

上面比较奇怪的就是任何类都是既是object类的子类,又是object类的实例, 正常定义的类都是元类type的子类及实例对象.

3. __str__

  双下str方法是用来定义一个对象的str(字符串)表现形式. 双下方法其实都有类似的特性, 即都是当触发了某个时机, 会自动调用这个方法. 双下str方法也一样, 当我们需要对象的字符串形式的时候,会自动调用这个方法. 使用双下str方法也可以定制我们想要的对象在执行print方法时的输出形式.

class Foo(object):

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

    def __str__(self):
        return 'Class Name: {} Attrs--- name: {} age: {}'.format('Foo', self.name, self.age   )


f = Foo('test', 12)
print(f)  # Class Name: Foo Attrs--- name: test age: 12

4. __del__

  双下del方法是当我们主动删除对象的时候会触发, 或是当对象的生命周期结束的时候, 垃圾回收机制回收的时候也会触发这个方法.

class Foo(object):

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

    def __del__(self):
        print('{} del Foo...'.format(self.name))


def test(obj):
    pass


f = Foo('test1')
test(Foo('test2'))
# test2 del Foo...
# test1 del Foo...

当对象进入函数内, 函数执行完毕后, 生命周期结束, 自动触发双下del方法, 当解释器结束时也会触发这个del方法

5. __call__

  这个方法是当我们调用实例对象的时候会触发这个方法, 一般用于元类中.使用范畴比较下载.

class Foo(object):

    def __call__(self, *args, **kwargs):
        print('Execute call method...')
        print(args)
        print(kwargs)


f = Foo()
f(1, 2, a=2)
Execute call method...
(1, 2)
{'a': 2}

6. __slots__

  双下slots是作为类属性存在的, 它更多是作为一个内存优化工具来使用的, 即双下slots类属性规定当前类有哪些属性, 当对象创建完成后, 它不再支持动态的添加属性的功能, 换句话说,即双下slots属性接管了__dict__的工作, 当双下slots存在时, 双下dict属性就不存在了.

import sys


class Foo:
    # __slots__ = ['x', 'y']

    def __init__(self, x, y):
        self.x = x
        self.y = y


f = Foo(1, 2)
print(sys.getsizeof(Foo))  # 1056个字节, 不加双下slots
print(sys.getsizeof(Foo))  # 888个字节
f.z = 1  # AttributeError: 'Foo' object has no attribute 'z'

7.__getattr__, __setattr__, __delattr__

  这三个方法是一组的, 见名字就知道和属性设置获取有关, 且他们都与点语法有关.

  __getattr__方法是当在对象的名称空间(__dict__)及其继承树的类中都找不到属性才会触发这个方法.
  __setattr__方法是在对对象使用点语法赋值的时候会触发这个方法, 赋值需要对__dict__添加新属性, 否则会触发无限递归

  __delattr__方法是在对对象的属性删除时会触发的方法, 在这本质就是需要删除__dict__字典的值

class MyDict(dict):

    def __getattr__(self, item):
        return self.get(item)

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

    def __delattr__(self, item):
        self.pop(item)


d = MyDict()
d.x = 12
print(d['x'])  # 12

d['y'] = 12
print(d.y)     # 12

8. __getitem__, __setitem__, __delitem__

  这3个方法也是一组, 这3个方法能让定义的对象支持[] 来取值, 就像字典一样

class Foo(object):

    def __init__(self):
        self.container = {}

    def __getitem__(self, item):
        return self.container[item]

    def __setitem__(self, key, value):
        self.container[key] = value

    def __delitem__(self, key):
        self.container.pop(key)

    def __str__(self):
        return str(self.container)


# 这个类没啥用, 只是用来理解这三个方法的作用
f = Foo()
f['name'] = 'tank'
f['age'] = 29
print(f['name'])
print(f)
del f['name']
print(f)
tank
{'name': 'tank', 'age': 29}
{'age': 29}

9. 运算符重载

  为了让两个对象之间能够比较以能够支持排序算法, 这就需要为对象创建__gt__, __lt__, __eq__等等方法了

  根据这几个单词的英文单词会比较好记, 如gt就是greater than, lt就是less than ,eq就是equal 等等, 见下面这个例子就能明白运算符重载的好处了.

class Student(object):

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

    def __str__(self):
        return 'name: {}, score: {}'.format(self.name, self.score)

    def __gt__(self, other):
        """根据分数排序, 然后根据名字的顺序排序"""
        return self.score > other.score or (self.score == other.score and self.name > other.name)

    def __eq__(self, other):
        return self.score, self.name == other.score, other.name


s1 = Student('jason', 70)
s2 = Student('egon', 80)
s3 = Student('alex', 80)
s4 = Student('jack', 78)
s5 = Student('tank', 88)
for s in sorted([s1, s2, s3, s4, s5]):
    print(s)
name: jason, score: 70
name: jack, score: 78
name: alex, score: 80
name: egon, score: 80
name: tank, score: 88

10. 迭代器协议

  遵循迭代器协议让我们自己写的类生成的对象也可以变成一个迭代器对象. 所谓的迭代器协议也非常简单, 就是需要在内部实现__iter__方法, 和__next__方法.

  实现了这两个方法, 我们就能通过for循环遍历我们自己实现的类. 例如下面这个自己实现的简单range类

class MyRange(object):

    def __init__(self, start, end, step=1):
        self.start = start
        self.end = end
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):

        while self.start < self.end and self.step > 0 or self.start > self.end and self.step < 0:
            temp = self.start
            self.start += self.step
            return temp
        else:
            raise StopIteration


for i in MyRange(10, 1, -1):
    print(i)

11. 上下文管理

  所谓上下文管理是为了能够让资源能够及时回收, 这个双下del方法有类似的用法, 即当我们需要回收不是属于解释器的资源的时候, 就需要用到这些方法, 例如我们需要主动关闭网络套接字, 文件的句柄等, 这些python解释器并不能主动释放. 与双下del方法不同的是上下文管理管的只是自己的语句块内的内容, 而双下del则是与对象的生存周期有关. 

  上下文管理是用with语法, 然后类需要实现两个魔法方法, __enter__方法, __exit__方法

  __enter__方法使用时需要返回对象自己

  __exit__方法则填写相关的关闭逻辑, 它还有三个参数, 是与异常处理相关的, 当with语句块内出现异常时, 会传递到这里面来处理, 然后这个函数还可以传返回值, 当返回True则说明已经处理完毕异常了,程序可以正常往下执行了, 而返回False则会把异常继续往上传递.

  

class MyOpen(object):

    def __init__(self, path, mode='r', encoding='utf-8'):
        self.path = path
        self.mode = mode
        self.encoding = encoding

    def __enter__(self):
        self.file = open(self.path, self.mode, encoding=self.encoding)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        print(exc_type, exc_val, exc_tb)
        return True


with MyOpen('a.txt') as f:
    print(f.file.read())


with MyOpen('a.txt') as f:
    print('start')
    1 / 0
    print('stop')
123
None None None
start
<class 'ZeroDivisionError'> division by zero <traceback object at 0x000001B8C866EEC8>

  当出现异常情况时, 会立即终止with语句块的内容, 如果exit返回值为True, 则程序还可以正常运行, 否则就会报错.

 

posted @ 2019-07-29 16:41  yscl  阅读(137)  评论(0)    收藏  举报