一文搞懂yield生成器、@contextmanager 装饰器、with语句、以及实际开发中三者的结合使用

在了解contextmanager 之前有必要解释下生成器的概念:

一、生成器

生成器:

生长器是一个可迭代对象,主要用于生成一个序列,能够用next()获取生成器的下一个值,在需要生成的序列元素较多时,使用生成器可以节省内存空间。

生成器与普通函数的本质区别:

普通函数一次返回所有结果(比如一个包含1亿个值的列表),而生成器只有你调用next()时,才会返回一个值,并且终止生成器的运行,记住这个值的位置。当再次调用next()时,从上次终止的位置继续执行,返回下一个值...

那么生成器长什么样呢?

生成器本质就是一个函数,只不过用yield代替return来返回值。

def fun():
    for i in range(20):
        print('start')
        yield i
        print('good',i)

if __name__ == '__main__':
    a=fun()   #创建一个生成器
    a.__next__()或next(a)   #用next取值,输出‘start’,然后执行x=yield i 并终止函数运行并返回i
    a.__next__()或next(a)   #再次调用next,从上次终止的位置继续执行,输出'good',0  然后再次输出‘start’,执行x=yield i 并终止函数运行并返回i 

结论:

yield与return类似,会返回一个值,终止函数运行,但是yield能记住上次函数运行的位置,再次调用函数时,会从上次终止位置继续运行。

 

 

二、with语句

下面再讲with语句:

用上下文管理器封装语句的执行,使原来的try...except...finally变得更加方便。即通过使用with语句,可以自动调用对象的资源释放、异常捕获等操作,减少用户手动调用的繁琐与可能遗漏的危险。

比如你经常用到的with open ()  as file

手动实现with上下文管理器对象

那么如何成为with语句支持的上下文管理器类呢?你只需要在类中实现__enter__()__exit__()两个方法。

import sqlite3

class DataConn:
    def __init__(self,db_name):
        self.db_name = db_name

    def __enter__(self):
        self.conn = sqlite3.connect(self.db_name)
        return self.conn

    def __exit__(self,exc_type,exc_val,exc_tb):
        self.conn.close()
        if exc_val:
            raise

if __name__ == "__main__":
    db = "test/test.db"
    with DataConn(db) as c:
        cursor = c.cursor()
                .
                .
                .
        (do some you sql action) 
  1. with DataConn(db) as conn调用DataConn(db)时自动执行enter()函数,并返回一个连接对象self.conn赋值给as后面的变量c
  2. 然后创建一个游标,之后你就可以做一些sql查询等操作了。
  3. 做完这些操作之后,也就是with下面的语句全部执行完成并且没有异常后,就会执行exit()函数,来关闭数据库连接。

 

装饰器实现with上下文管理器对象

python自带的contextlib.contextmanager装饰器配合yield关键字就可以可轻松实现。但是contextmanager 只是省略了 __enter__() / __exit__() 的编写,但并不负责实现资源的“获取”和“清理”工作;“获取”操作需要定义在 yield 语句之前,“清理”操作需要定义 yield 语句之后,这样 with 语句在执行 __enter__() / __exit__() 方法时会执行这些语句以获取/释放资源。

from contextlib import contextmanager

@contextmanager
def connection(db_name):
    conn = sqlite3.connect(db_name)
    yield coon
    conn.close()


with connection("test/test.db") as c:
    cursor = c.cursor()
        .
        .
        .
    (do some you sql action)
  1. 从这个函数中我们可以总结出该格式: 以yield分段,前面部分是__enter__的逻辑,后面部分是__exit__的逻辑。
  2. with下面的语句全部执行完成并且没有异常后,由with内部机制触发exit逻辑,也就是yield后面部分,即关闭连接。
  3. 上面的小例子并没有加入try...finally,更完整的格式请看第三部分。

with语句什么时候会触发上下文管理器中的exit()函数呢?

  1. with包含的语句全部执行完成并且没有异常时
  2. with包含的语句遇到return,break,continue结束函数运行时

 

 

三、实际开发运用

下面例子是一个ldap认证的一个过程

@contextmanager
def connection(username, password):
    # Connect.
    auto_bind = ldap3.AUTO_BIND_NO_TLS
    try:
        c = ldap3.Connection(
            ldap3.Server(
                settings.LDAP_AUTH_URL,
                allowed_referral_hosts=[("*", True)],
                get_info=ldap3.NONE,
                connect_timeout=settings.LDAP_AUTH_CONNECT_TIMEOUT,
            ),
            user=username,
            password=password,
            auto_bind=auto_bind,
            raise_exceptions=True,
            receive_timeout=settings.LDAP_AUTH_RECEIVE_TIMEOUT,
        )
    except LDAPException as ex:
        logger.warning("LDAP connect failed: {ex}".format(ex=ex))
        yield None
        return
    # Return the connection.
    logger.info("LDAP connect succeeded")
    try:
        yield Connection(c)
    finally:
        c.unbind()



class LDAPBackend(ModelBackend):

    def authenticate(self, request, username=None, password=None, **kwargs):
        if not all([password, username]):
            return None
        # Connect to LDAP.
        username = username + '@' + settings.LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN

        with connection(username, password) as c:
            if c is None:
                return None
            return c.get_user(username)


class Connection(object):

    def __init__(self, connection):
        self._connection = connection

    def _get_or_create_user(self, user_data):
        attributes = user_data.get("attributes")
        if attributes is None:
            logger.warning("LDAP user attributes empty")
            return None

        User = get_user_model()

        # Update or create the user.
        username = attributes['userPrincipalName'][0]
        email = attributes['mail'][0]
        user_fields = {'username': username, 'email': email, 'is_staff': True,}
        user_lookup = {'username': username}
        user, created = User.objects.update_or_create(
            defaults=user_fields,
            **user_lookup,
        )
        if created:
            user.set_unusable_password()
            interviewer_group = Group.objects.filter(name=settings.CONSTANTS.get('INTERVIEWER_GROUP_NAME')).first()
            if interviewer_group:
                user.groups.add(interviewer_group)
            user.save()
        # All done!
        logger.info("LDAP user lookup succeeded")
        return user

    def get_user(self, username):
        if self._connection.search(
            search_base=settings.LDAP_AUTH_SEARCH_BASE,
            search_filter="(&(userPrincipalName={}))".format(username),
            search_scope=ldap3.SUBTREE,
            attributes=ldap3.ALL_ATTRIBUTES,
            get_operational_attributes=True,
            size_limit=1,
        ):
            return self._get_or_create_user(self._connection.response[0])
        logger.warning("LDAP user lookup failed")
        return None


执行流程:

  1. 首先用@contextmanager定义了一个connection上下文管理器对象
  2. 登录请求来时,会走LDAPBackend中的authenticate方法,方法里面通过with处理connection上下文管理器对象
  3. with语句会将上下文管理器对象中的yield后面的值返回并赋值给as 后面的c变量,也就是将Connection(c)赋值给c
  4. 然后继续向下执行with执行体中的代码,直到执行完return c.get_user(username),with语句会返回到connection上下文管理器对象执行exit()清理工作,也就是yield Connection(c)后面的所有代码即c.unbind(),解除连接
  5. 为什么要在connection上下文管理器对象中用try 处理yield Connection(c)呢?别忘了我们之前讲的exit()函数执行时机。当yield Connection(c)一旦出现异常,将不会再执行exit,也就不会执行yield后面的代码即c.unbind()。因此要try。

 

 

 

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
The @contextmanager decorator in Python allows you to create a context manager using a generator function. A context manager is a class or function that is used to manage resources, such as opening and closing a file or acquiring and releasing a lock. The @contextmanager decorator provides a simple and concise way to create a context manager without having to define a class. The decorator takes a generator function as input and returns a context manager. The generator function must yield exactly once to indicate the beginning of the context and again at the end to indicate the end of the context. Any code inside the with statement will be executed within the context. Here is an example of a context manager created using the @contextmanager decorator: ```python from contextlib import contextmanager @contextmanager def open_file(file_path): file = open(file_path, 'w') try: yield file finally: file.close() with open_file('test.txt') as f: f.write('Hello, world!') ``` In this example, the open_file() function is decorated with @contextmanager. The function opens a file for writing and yields the file object. The try-finally block ensures that the file is closed properly when the context is exited. The with statement is used to create a context in which the file is opened and used to write a string to the file. When the with block is exited, the file is closed automatically. The @contextmanager decorator is a useful tool for creating context managers in a concise and readable manner.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值