Advance Python 03 :深入类和对象

Introduce:

  • 鸭子类型和多态

  • 抽象基类(abc模块)

  • isinstance 和 type 的区别

  • 类变量与实例变量

  • 类属性和实例属性以及查找顺序

  • 静态方法、类方法、对象方法以及参数

  • 数据封装和私有属性

  • python对象的自省机制

  • super真的是调用父类吗

  • mixin继承案例(django、rest、framework)

  • Python中的with语句

  • contextlib简化上下文管理器

一、鸭子类型和多态

鸭子类型

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来像鸭子,那么这只鸟可以被称为鸭子

这样理解:

  • 在Python中,有些时候我们需要一个有某个功能(比如说:鸭子叫)的对象,那我们可以通过判断这个对象是不是一只鸭子来检测是否满足我们的需求
  • 但仔细想想这有些缺陷,因为我们真正需要的是鸭子叫这个方法,一个对象无论是不是鸭子,只要他会像鸭子一样叫就可以了。

多态

定义时的类型和运行时的类型不一样,此时就成为多态 ,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。

  • Python的多态,就是弱化类型,重点在于对象参数是否有指定的属性和方法,如果有就认定合适,而不关心对象的类型是否正确。
  • Python “鸭子类型”

关于Python面向对象三兄弟(三大特性):封装、多态、继承

分享几篇大佬的文章:

面向对象编程(Python版详解)_辰兮要努力-CSDN博客_python面向对象编程

面向对象编程(继承)—Python版(案例详解)_辰兮要努力-CSDN博客

面向对象编程(封装、多态)Python版(Demo详解)_辰兮要努力-CSDN博客

示例代码:

class Cat:
    def say(self):
        print('I am a cat.')


class Dog:
    def say(self):
        print('I am a dog.')


class Duck:
    def say(self):
        print('I am a duck.')


# Python中较灵活,只要实现say方法就行,实现了多态
animal = Cat
animal().say()

# 实现多态只要定义了相同方法即可
animal_list = [Cat, Dog, Duck]
for an in animal_list:
    an().say()

"""
class Animal:
    def say(self):
        print('I am a animal.')

# 需要继承Animal,并重写say方法
class Cat(Animal):
   def say(self):
       print('I am a cat.')

# Java 中定义需要指定类型
Animal an = new Cat()
an.say()
"""

li1 = ['i1', 'i2']
li2 = ['i3', 'i4']

tu = ('i5', 'i6')
s1 = set()
s1.add('i7')
s1.add('i8')

# 转变观念,传入的不单单是list,甚至自己实现 iterable 对象
li1.extend(li2)  # iterable
li1.extend(tu)
li1.extend(s1)
print(li1)

运行结果:

I am a cat.
I am a cat.
I am a dog.
I am a duck.
['i1', 'i2', 'i3', 'i4', 'i5', 'i6', 'i7', 'i8']

进程已结束,退出代码为 0
  • 实现多态只要定义了相同方法即可
  • 魔法函数充分利用了鸭子类型的特性,只要把函数塞进类型中即可

二、抽象基类(abc模块)

1、概念

Python 的抽象基类:

是指必须让继承它的子类去实现它所要求的抽象方法的类

或者这样说:

抽象基类它提供了接口,但是又没有去把接口实现,需要由子类完成。

感觉它就是老板,只告诉你要完成项目A, 你接到项目A后(继承),你自己去把它完成。

抽象基类特点:

  • 抽象基类无法实例化
  • 变量没有类型限制,可以指向任何类型
  • 抽象基类和魔法函数构成了python的基础,即协议

在抽象基类定义了抽象方法,继承了抽象基类的类,必须实现这些方法

2、应用场景

一般有两个应用场景:

场景一:想判断某个对象的类型

# 检查某个类是否有某种方法
class Company:
    def __init__(self, name):
        self.name = name

    def __len__(self):
        return len(self.name)


company = Company('Linda Process Ltd.')
print(hasattr(company, '__len__'))

# 我们在某些情况之下希望判定某个对象的类型
from collections.abc import Sized
print(isinstance(company, Sized))

运行结果:

True
True

进程已结束,退出代码为 0

补充:

hasattr () 函数:
用来判断某个类实例对象是否包含指定名称的属性或方法。. 该函数的语法格式如下:. hasattr (obj, name) 其中 obj 指的是某个类的实例对象,name 表示指定的属性名或方法名。
isinstance () 函数:
用来判断一个对象是否是一个已知的类型,类似 type ()type () 不会认为子类是一种父类类型,不考虑继承关系。
isinstance () 会认为子类是一种父类类型,考虑继承关系。

场景二:强制子类必须实现某些方法

比如开头说的,老板让我们去实现某些接口,怎么强制这些接口必须完成呢?

可以使用抽象基类,因为它必须让继承它的子类去实现它所要求的方法

为了方便理解,我们用两种方式实现抽象基类:

(1)我们先自己去定义抽象基类

# 我们需要强制某个子类必须实现某些方法
# 比如我们实现了一个web框架,这个框架集成cache(redis, cache, memorychache)
# 需要设计一个抽象基类, 指定子类必须实现某些方法
# 如何去模拟一个抽象基类

class CacheBase():
    def get(self, key):
        raise NotImplementedError

    def set(self, key, value):
        raise NotImplementedError


class RedisCache(CacheBase):
    pass


redis_cache = RedisCache()
redis_cache.set("key", "value")

运行结果:

继承类必须实现抽象类的方法

所以激发报错(raise NotImplementedError)

NotImplementedError

进程已结束,退出代码为 1

(2)用 abc 模块

# 我们需要强制某个子类必须实现某些方法
# 比如我们实现了一个web框架,这个框架集成cache(redis, cache, memorychache)
# 需要设计一个抽象基类, 指定子类必须实现某些方法
# 如何去模拟一个抽象基类

import abc


class CacheBase(metaclass=abc.ABCMeta):  # metaclass会在元类编程中讲

    @abc.abstractmethod
    def get(self, key):
        pass

    @abc.abstractmethod
    def set(self, key, value):
        pass


class MemoryCache(CacheBase):
    pass
 

redis_cache = MemoryCache()
redis_cache.set("key", "value")

运行结果:

报错,继承类必须实现抽象类的方法

TypeError: Can't instantiate abstract class MemoryCache with abstract methods get, set

进程已结束,退出代码为 1

注意:

实际上,抽象基类并不常用

抽象基类容易设计过度,多继承推荐使用Mixin

分享几篇大佬的文章:

Python官方:abc — 抽象基类 — Python 3.10.2 文档

python抽象基类 - 知乎 (zhihu.com)

三、 isinstance 和 type 的区别

  • isinstance 会去查找继承链
  • type 只判断变量的内存地址
class A:
    pass


class B(A):
    pass


b = B()
print(isinstance(b, B))  # True
print(isinstance(b, A))  # True

# is 判断 id 的意思
print(type(b) is B)  # True
print(type(b) is A)  # False

运行结果:

True
True
True
False

进程已结束,退出代码为 0

四、类变量与实例变量

  • 类变量定义与使用
  • 实例变量定义与使用
  • 类变量是所有实例变量共享

示例一:

class A:
    aa = 1  # 类变量

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


a = A(2, 3)
print(a.x, a.y, a.aa)  # 2 3 1

A.aa = 11
a.aa = 100
print(a.x, a.y, a.aa)  # 2 3 100
print(A.aa)  # 11

b = A(3, 5)
print(b.aa)  # 11

运行结果:

2 3 1
2 3 100
11
11

进程已结束,退出代码为 0

示例二:

class A:
    name = "A"

    def __init__(self):
        self.name = "obj"


a = A()
print(a.name)

运行结果:

obj

进程已结束,退出代码为 0

五、类属性和实例属性以及查找顺序

1、类属性和实例属性

类属性和实例属性

  • 类属性:定义在类中的变量和方法
  • 实例属性:__init__中定义

在上一章的案例中,我们可以很容易的搞明白类变量和实例变量之间的关系

但当涉及到属性,特别是多继承的情况,就会变得复杂

2、MRO算法

MRO

:(Method Resolution Order)方法解析顺序。

概念补充:

经典类 vs 新式类

关于”类“

Python 2.x中默认都是经典类,只有显式继承了object才是新式类

(具体好像Python2.2之前都是经典类)

Python 3.x中默认都是新式类,不必显式的继承object

那么关于类属性和实例属性的到底采用怎样的查找顺序???

1、类为经典类时,多继承情况下,会按照深度优先查找

2、类为新式类时,多继承情况下,会按照广度优先查找

但是,注意:

无论DFS还是BFS在面对多继承都存在缺陷

以往Python采用过DFS、BFS,但

自从Python2.3后到现在使用 MRO C3 算法(C3 算法)

我们通过一些案例简单了解一下这些搜索算法:

  • DFS
  • BFS
  • MRO C3

(1)深度优先 DFS

示例一:

请添加图片描述

  • 查找顺序为 A -> B -> D -> C -> E
  • 此种场景深度优先较为合适

示例二:

请添加图片描述

  • 查找顺序为 A -> B -> D -> C

  • 此种场景 当C中重载了D中某个方法,该查找顺序就不合适

(2)广度优先 BFS

示例一:

请添加图片描述

  • 查找顺序为 A -> B -> C -> D -> E

  • 此种场景 B继承D,B和D是一体的,D应该先于C

示例二:

请添加图片描述

  • 查找顺序为 A -> B -> C -> D

  • 此种场景深度优先较为合适

(3)MRO C3 算法

C3算法比较复杂

这里提供俩示例

示例一:菱形继承

请添加图片描述

示例代码:

class D:
    pass


class C(D):
    pass


class B(D):
    pass


class A(B,C):
    pass


if __name__ == '__main__':
    print(A.__mro__)
    

运行结果:

(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
  • A ==> B ==> C ==> D

示例二:B、C 各自继承D、E场景

请添加图片描述

示例代码:

class D:
    pass


class E:
    pass


class C(E):
    pass


class B(D):
    pass


class A(B, C):
    pass


print(A.__mro__)

运行结果:

(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)

六、静态方法、类方法、对象方法以及参数

  • 静态方法 @staticmethod
  • 类方法 @classmethod
  • 实例方法

python中实现静态方法和类方法都是依赖于python的修饰器来实现的。

先看一段代码,这仨方法的具体定义在后文

示例代码:

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    # 实例方法
    def tomorrow(self):
        self.day += 1

    # 静态方法
    @staticmethod
    def date_from_str(date_str):
        year, month, day = tuple(date_str.split('-'))
        return Date(int(year), int(month), int(day))

    # 类方法
    @classmethod
    def date_from_string(cls, date_str):
        year, month, day = tuple(date_str.split('-'))
        return cls(int(year), int(month), int(day))

    def __str__(self):
        return '{year}/{month}/{day}'.format(year=self.year, month=self.month, day=self.day)


if __name__ == '__main__':
    new_day = Date(2020, 2, 20)
    new_day.tomorrow()
    print(new_day)

    date_str = '2022-2-5'
    print(Date.date_from_str(date_str))
    print(Date.date_from_string(date_str))

运行结果:

2020/2/21
2022/2/5
2022/2/5

进程已结束,退出代码为 0

实例方法

定义:无装饰器。
第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);

调用:只能由实例对象调用。

类方法

定义:使用装饰器@classmethod。
第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);

调用:类和实例对象都可以调用。

静态方法

定义:使用装饰器@staticmethod。
参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;

调用:类和实例对象都可以调用。

总结:

参数可以调用的方法可以调用的属性
实例方法无装饰器。
第一个参数必须是实例对象,该参数名一般约定为“self”
实例方法、类方法、静态方法类属性,实例属性
类方法使用装饰器@classmethod。
第一个参数必须是当前类对象,该参数名一般约定为“cls
类方法、静态方法类属性
静态方法使用装饰器@staticmethod。
参数随意,没有“self”和“cls”参数。
实例方法、类方法、静态方法(通过类名调用)类属性(通过类名调用)

七、数据封装和私有属性

私有属性和私有方法

设置私有属性和方法:

  • 在属性名和方法名前面加两个下划线__
  • 定义为私有属性和私有方法,不能被子类继承

私有属性是无法通过实例.私有属性来获取,也无法通过子类来获取

示例代码:

class Person(object):
    # 构造函数
    def __init__(self, name):
        self.name = name
        self.__age = 18


temp = Person("Coder")
print(temp.name)  # 可获取
print(temp.__age)  # 不可获取,报错:AttributeError

运行结果:

    print(temp.__age)
AttributeError: 'Person' object has no attribute '__age'
Coder

进程已结束,退出代码为 1

注意:

但实际上,Python 中,并没有 真正意义私有

私有属性还是可以通过多种方法获取:

(1)

  • 在给 属性方法 私有化命名时,实际是对 名称 做了一些特殊处理,使得外界无法访问到
  • 处理方式:在 名称 前面加上 _类名 => _类名__名称,如上面的示例:print(obj._Person__age)

(2)

示例代码:

class User:
    def __init__(self):
        self.__age = 18

    def get_age(self):
        return self.__age


if __name__ == '__main__':
    user = User()
    print(user.get_age())

    # print(user.__age)

    # _class__attr, 做了变形
    print(user._User__age)

运行结果:

18
18

进程已结束,退出代码为 0
  • python并不能严格限制私有属性的使用,这是一种写代码规范

八、python对象的自省机制

重点:

  • 自省机制概念
  • dir()

自省机制

通过一定的机制查询对象的内部结构

Python中比较常见的自省(introspection)机制(函数用法)有:

  • dir()
  • type()
  • hasattr()
  • isinstance()

通过这些函数,我们能够在程序运行时得知对象的类型,判断对象是否存在某个属性,访问对象的属性。

综述:

  • type(),判断对象类型
  • dir(), 带参数时获得该对象的所有属性和方法;不带参数时,返回当前范围内的变量、方法和定义的类型列表
  • isinstance(),判断对象是否是已知类型
  • hasattr(),判断对象是否包含对应属性
  • getattr(),获取对象属性
  • setattr(), 设置对象属性

(1)dir()

  • dict
  • dir()

dir() 函数可能是 Python 自省机制中最著名的部分了。它返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 dir() 返回当前作用域中的名称。

不带参数:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

带参数:

dir('ABC')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

示例代码:

class User:
    name = 'user'

class Student(User):
    def __init__(self):
        self.school_name = 'school'

if __name__ == '__main__':
    stu = Student()

    # 通过__dict__ 查询属性, C语言实现,经过优化,较快
    print(stu.__dict__)
    stu.__dict__['age'] = 18
    print(stu.age)

    print(User.__dict__)

    print(dir(stu))
    

运行结果:

{'school_name': 'school'}
18
{'__module__': '__main__', 'name': 'user', '__dict__': <attribute '__dict__' of 'User' objects>, '__weakref__': <attribute '__weakref__' of 'User' objects>, '__doc__': None}
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'school_name']

进程已结束,退出代码为 0

(2)type()

type() 函数有助于我们确定对象是字符串还是整数,或是其它类型的对象。

(返回对应的class类型)

In [27]: type(123)
Out[27]: int

In [28]: type('123')
Out[28]: str

In [29]: type(None)
Out[29]: NoneType

(3)hasattr()

对象拥有属性,并且 dir() 函数会返回这些属性的列表。但是,有时我们只想测试一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由 hasattr() 和 getattr() 函数来完成.

注意:getattr、hasattr、setattr获取对象的状态

In [1]: class Myobject(object):
   ...:     def __init__(self):
   ...:         self.x = 9
   ...:     def power(self):
   ...:         return self.x * self.x
   ...:

In [2]: obj = Myobject()

In [3]: obj.power()
Out[3]: 81

In [4]: hasattr(obj, 'x') # 有属性x么?
Out[4]: True

In [5]: hasattr(obj, 'y') # 有属性y么?
Out[5]: False

In [6]: setattr(obj, 'y') # 设置属性必须有三个参数,少了值
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-3b0a0f22117d> in <module>()
----> 1 setattr(obj, 'y')

TypeError: setattr expected 3 arguments, got 2

In [7]: setattr(obj, 'y', 100) # 设置属性y的值为100

In [8]: hasattr(obj, 'y') # 有属性y么?
Out[8]: True

In [9]: getattr(obj, 'y') # 获取属性y!
Out[9]: 100

In [10]: obj.y # 获取属性y
Out[10]: 100

In [11]: obj.x # 获取属性x
Out[11]: 9

(4)isinstance()

可以使用 isinstance() 函数测试对象,以确定它是否是某个特定类型或定制类的实例

示例:

判断a数据类型是不是list

In [37]: a = [1, 2, 3]

In [38]: isinstance(a, list)
Out[38]: True

我们用自省最重要的几个目的就是,让python回答我们:

  1. 对象名称是什么?
  2. 对象能做什么?
  3. 对象是什么类型?
  4. 对象的一些基础信息是什么?

九、super真的是调用父类吗

1、super的作用

当存在继承关系的时候,有时候需要在子类中调用父类的方法,此时最简单的方法是把对象调用转换成类调用,需要注意的是这时self参数需要显式传递,例如:

class FooParent:
  def bar(self, message):
    print(message)
class FooChild(FooParent):
  def bar(self, message):
    FooParent.bar(self, message)
>>> FooChild().bar("Hello, Python.")
Hello, Python.

这样做有一些缺点,比如说如果修改了父类名称,那么在子类中会涉及多处修改,另外,Python是允许多继承的语言,如上所示的方法在多继承时就需要重复写多次,显得累赘。

为了解决这些问题,Python引入了super()机制

python中的super()和__init__()方法的作用:

  • init()方法用来创建对象的实例变量;
  • super()方法用于调用父类的方法。

示例代码:

class A:
    def __init__(self):
        print("A")


class B(A):
    def __init__(self):
        print("B")
        super().__init__()

运行结果:

B
A

**super()**常与 多继承 一同出现:

多重继承时,会涉及继承顺序
直接用类名调用父类方法会涉及查找顺序、重复调用等问题。
super()则是返回继承顺序的下一个类,而不是父类。

在类的继承里面super()非常常用, 它解决了子类调用父类方法的一些问题, 父类多次被调用时只执行一次, 优化了执行逻辑

2、既然我们重写 B 的构造函数,为什么还要去调用super?

答:不是所有的子类都有必要去调用super。

假设继承Thread,在初始化函数里面需要赋值name。但真的需要赋值吗?

其实Thread类的初始化函数本身就做了很多判断,所以没必要写冗余代码。

所以把构造函数交给父类Thread去实例化,不用自己再去写不必要的逻辑了。

示例代码:

from threading import Thread


class MyThread(Thread):
    def __init__(self, name, user):
        self.user = user
        # 某种程度上复用父类代码
        super().__init__(name=name)
       

3、super到底执行顺序是什么样的?

super()函数不是直接调用父类,而是调用 mro 顺序中下一个类的构造函数

super() 函数的调用顺序不是单纯的调用父类,而是根据 mro() 函数指定的调用顺序进行调用的

那么mro() 获取指定的调用顺序又是怎么实现的呢?

答案是根据 python 内置的 C3 算法实现的

示例代码:

class A:
    def __init__(self):
        print("A")


class B(A):
    def __init__(self):
        print("B")
        super().__init__()


class C(A):
    def __init__(self):
        print("C")
        super().__init__()


class D(B, C):
    def __init__(self):
        print("D")
        super().__init__()


if __name__ == "__main__":
    print(D.__mro__)
    d = D()

运行结果:

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
D
B
C
A

十、mixin继承案例(django、rest、framework)

Mixin

Mixin编程是一种开发模式,是一种将多个类中的功能单元的进行组合的利用的方式,这听起来就像是有类的继承机制就可以实现,然而这与传统的类继承有所不同。

通常mixin并不作为任何类的基类,也不关心与什么类一起使用,而是在运行时动态的同其他零散的类一起组合使用

建议:

  • 实际编码中,尽量使用单继承,不推荐多继承,避免继承混乱
  • 有多个功能,就写多个类
  • 以后编码,尽量使用mixin模式

mixin多继承案例(如django restframework中的mixins)

  • 1.mixin类功能单一;
  • 2.不和基类关联,可以和任意基类组合,基类可以不和mixin关联就能初始化成功;
  • 3.在mixin中不要使用super函数;
  • 4.尽量以Mixin结尾

示例代码:

'''
python 对于 mixin 命名方式一般以 MixIn, able, ible 为后缀。

由于 mixin 是组合,因而是做加法,为已有的类添加新功能,而不像继承一样,下一级会覆盖上一级相同的属性或方法。
但在某些方面仍然表现得与继承一样, 例如类的实例也是每个 mixin 的实例。
mixin 使用不当会导致类的命名空间污 染,所以要尽量避免 mixin 中定义相同方法。
对于相同的方法,有时很难区分 实例到底使用的是哪个方法。
'''
class Mixin1(object):
    def test(self):
        print("mixin 1")
    def which_test(self):
        self.test()


class Mixin2(object):
    def test(self):
        print("mixin 2")


class MyClass1(Mixin1, Mixin2):
    pass                        # 按从左到右顺序从 mixin 中获取功能并添加到 MyClass


class Myclass2(Mixin1, Mixin2):
    def test(self):             # 已有 test 方法,因而不会再添加 Mixin1, Mixin2 的 test 方法
        print("my class 2")


c1 = MyClass1()
c1.test()                       #  "mixin 1"
c2 = MyClass2()
c2.test()                       #  "my class 2"
c2.which_test()                 #  "my class 2"
isinstance(c1, Mixin1)          #  True
issubclass(MyClass1, Mixin2)    #  True

十一、Python中的with语句

内容:

  • with 与 try except finally
  • with与上下文管理器协议

1、with语句的作用

什么是with语句

with 语句是从 Python 2.6 开始引入的一种与异常处理相关的功能。with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。

with语句类似

try :
    
except:
    
finally:
    

那为什么会出现with,本来可以用try except finally来解决的问题,为什么要用with语句呢?

  • python是一门简短精悍的语言,提倡简洁的编码风格,也可以理解为pythonic。
  • with使用了上下文管理器,可以自动获取上下文相关内容,让开发者更专注于业务。

(with语句更简洁。而且更安全。代码量更少。)

with语句是一个新的控制流结构

with 用法:

基本结构(格式)为:

with expression [as variable]:
    with-block

一个很好的例子是文件处理

你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。如果不用with语句,代码如下:

file = open("foo.txt")
data = file.read()
file.close()

这里有两个问题:

  • 可能忘记关闭文件句柄
  • 文件读取数据发生异常,没有进行任何处理

下面是处添加了异常处理的代码:

file = open("foo.txt")
try:
    data = file.read()
finally:
    file.close()

虽然这段代码运行良好,但是太冗长了。

这时候就是with一展身手的时候了。

除了有更优雅的语法,with还可以很好的处理上下文环境产生的异常。

下面是with版本的代码:

with open("foo.txt") as file:
    data = file.read()

2、上下文管理器

它是python中的一种协议,实现了这种协议的类的实例,都是上下文管理器对象。

那么怎么才能实现协议呢?

python中很简单,只需要在类定义的时候,实现两个方法即可。
一个是enter,另一个是exit

示例代码:

class A():
    def __init__(self, val_a):
        self.a = val_a

    def __enter__(self):
        print("class A‘s __enter__ function.’")

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("class A's __exit__ function.")
        

这样,就创建好了一个实现了上下文管理器协议的对象。你可以应用到任何类中。

Python中还有其他的高级特性也能创建上下文管理器,比如使用装饰器@contextmanager。

contextmanager装饰被定义在contextlib模块

使用了@contextmanager装饰器修饰的方法都会变成上下文管理器对象。



python提供了contextlib模块,省去了写__enter__和__exit__重复工作了。contextlib模块提供了3个对象:contextmanager装饰器、上下文管理器closing和nested函数。


分享一篇大佬的博客:

深入理解Python with语句 – 标点符 (biaodianfu.com)****

十二、contextlib简化上下文管理器

Python 在 contextlib 模块中还提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。

相当于简化__enter__和__exit__:

@contextlib.contextmanager装饰器将__enter__和__exit__合起来并进行了一系列操作

通过 yield 将函数分割成两部分,yield 之前的语句在 enter 方法中执行,yield 之后的语句在 exit 方法中执行。紧跟在 yield 后面的值是函数的返回值。

from contextlib import contextmanager

@contextmanager
def file_manager(name, mode):
    try:
        f = open(name, mode)
        yield f
    finally:
        f.close()
        
with file_manager('test.txt', 'w') as f:
    f.write('hello world')

这段代码中,函数 file_manager() 是一个生成器,当我们执行 with 语句时,便会打开文件,并返回文件对象 f;当 with 语句执行完后,finally block 中的关闭文件操作便会执行。你可以看到,使用基于生成器的上下文管理器时,我们不再用定义“enter()”和“exit()”方法,但请务必加上装饰器 @contextmanager,这一点新手很容易疏忽。

另一位大佬的解答:

contextlib模块的作用是提供更易用的上下文管理器,它是通过Generator实现的。

contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制。

常用框架如下:

from contextlib import contextmanager
@contextmanager

def make_context():
    print 'enter'
    try:
        yield "ok"
    except RuntimeError,err:
        print 'error',err
    finally:
        print 'exit'
         
>>>with make_context() as value:
    print value
     
输出为:
    enter
    ok
    exit

其中,yield写入try-finally中是为了保证异常安全(能处理异常)as后的变量的值是由yield返回。

yield前面的语句可看作代码块执行前操作,yield之后的操作可以看作在__exit__函数中的操作。

以线程锁为例:

@contextlib.contextmanager
def loudLock():
    print 'Locking'
    lock.acquire()
    yield
    print 'Releasing'
    lock.release()
 
with loudLock():
    print 'Lock is locked: %s' % lock.locked()
    print 'Doing something that needs locking'
 
#Output:
#Locking
#Lock is locked: True
#Doing something that needs locking
#Releasing
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DLNovice

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值