Python 使用上下文管理器而不使用;加上;块

Python 使用上下文管理器而不使用;加上;块,python,block,with-statement,contextmanager,Python,Block,With Statement,Contextmanager,下面是我的my_create方法的一个示例,以及正在使用的该方法的一个示例 @contextmanager def my_create(**attributes): obj = MyObject(**attributes) yield obj obj.save() with my_create(a=10) as new_obj: new_obj.b = 7 new_obj.a # => 10 new_obj.b # => 7 new_obj.

下面是我的
my_create
方法的一个示例,以及正在使用的该方法的一个示例

@contextmanager
def my_create(**attributes):
    obj = MyObject(**attributes)
    yield obj
    obj.save()

with my_create(a=10) as new_obj:
     new_obj.b = 7

new_obj.a  # => 10
new_obj.b  # => 7
new_obj.is_saved()  # => True
对于Ruby/Rails的用户来说,这可能看起来很熟悉。它类似于
ActiveRecord::create
方法,其中
中的代码与
块充当块

但是:

with my_create(a=10) as new_obj:
    pass

new_obj.a  # => 10
new_obj.is_saved()  # => True
在上面的示例中,我将一个空的“块”传递给了我的
my_create
函数。事情按预期进行(
my_obj
已初始化并保存),但格式看起来有点不稳定,而且
with
块似乎没有必要

我希望能够直接调用
my_create
,而不必设置
pass
ing
with
块。不幸的是,这在我当前的
myu create
实现中是不可能的

my_obj = create(a=10)
my_obj  # => <contextlib.GeneratorContextManager at 0x107c21050>
我总是想做上下文管理器的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
部分。我并不总是想提供一个街区。如果不需要,我不想用块设置
pass
ing


这在Ruby中是可能且容易的。如果在Python中也可以,那就太酷了,因为我们已经非常接近了(我们只需要设置一个
pass
ing
with
block)。我知道语言机制使它变得困难(技术上不可能?),但一个足够接近的解决方案对我来说很有趣。

MyObject
上添加一个新方法,该方法创建并保存

这是一个替代的初始值设定项a,并且设计模式在Python标准库和许多流行框架中都有先例。其中,备用初始值设定项
Model.create(**args)
可以提供常规
Model(**args)
无法提供的附加功能(例如,持久化到数据库)

有没有一种方法可以编写my_create函数,以便使用“块”作为可选的“参数”调用它


没有。

好的,似乎有些混乱,所以我不得不拿出一个示例解决方案。这是到目前为止我能想到的最好的

class my_create(object):
    def __new__(cls, **attributes):
        with cls.block(**attributes) as obj:
            pass

        return obj

    @classmethod
    @contextmanager
    def block(cls, **attributes):
        obj = MyClass(**attributes)
        yield obj
        obj.save()
如果我们像上面那样设计
my_create
,我们可以正常使用它,而不需要阻塞:

new_obj = my_create(a=10)
new_obj.a  # => 10
new_obj.is_saved()  # => True
我们可以用一个方块来稍微不同地称呼它

with my_create.block(a=10) as new_obj:
    new_obj.b = 7

new_obj.a  # => 10
new_obj.b  # => 7
new_obj.saved  # => True
调用
my_create.block
类似于调用芹菜任务
Task.s
,不想用block调用
my_create
的用户只需正常调用它,所以我允许它

然而,
my\u create
的这个实现看起来不可靠,因此我们可以创建一个包装器,使它更像问题中的
context\u管理器(my\u create)
的实现

import types

# The abstract base class for a block accepting "function"
class BlockAcceptor(object):
    def __new__(cls, *args, **kwargs):
        with cls.block(*args, **kwargs) as yielded_value:
            pass

        return yielded_value

    @classmethod
    @contextmanager
    def block(cls, *args, **kwargs):
        raise NotImplementedError

# The wrapper
def block_acceptor(f):
    block_accepting_f = type(f.func_name, (BlockAcceptor,), {})
    f.func_name = 'block'
    block_accepting_f.block = types.MethodType(contextmanager(f), block_accepting_f)
    return block_accepting_f
然后
my_create
变成:

@block_acceptor
def my_create(cls, **attributes):
    obj = MyClass(**attributes)
    yield obj
    obj.save()
使用中:

# creating with a block
with my_create.block(a=10) as new_obj:
    new_obj.b = 7

new_obj.a  # => 10
new_obj.b  # => 7
new_obj.saved  # => True


# creating without a block
new_obj = my_create(a=10)
new_obj.a  # => 10
new_obj.saved  # => True
理想情况下,
myu-create
函数不需要接受
cls
,而
block\u-acceptor
包装器可以处理这个问题,但我现在没有时间进行这些更改

蟒蛇?不,有用吗?可能吧


我仍然有兴趣看看其他人想出了什么。

我建议使用不同的函数来获取上下文管理器,该管理器将对象保存在
\uuuuuuuuu退出\uuuuuu
上,并获取自动保存的对象。要让一个函数同时完成这两件事是不容易的。(除了你说不想要的函数外,没有你可以传递的“块”。)

例如,您可以创建第二个函数,该函数只创建并立即保存对象,而无需运行任何额外代码:

def create_and_save(**args):
    obj = MyObject(**args)
    obj.save()
    return obj
因此,您可以使用两个函数使其工作。但是一种更具python风格的方法可能是去掉上下文管理器函数,让
MyObject
类充当自己的上下文管理器。您可以为其提供非常简单的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
\uuuuuuuuuuuuuuuuuuuuu
方法:

def __enter__(self):
    return self

def __exit__(self, exception_type, exception_value, traceback):
    if exception_type is None:
        self.save()
你的第一个例子是:

with MyObject(a=10) as new_obj:
     new_obj.b = 7
您还可以将我上面展示的
create\u and\u save
函数转换为
classmethod

@classmethod
def create_and_save(cls, **args):
    obj = cls(**args)
    obj.save()
    return obj
第二个例子是:

new_obj = MyObject.create_and_save(a=10)

这两种方法都可以在基类中编写,并简单地由其他类继承,因此不要认为您需要一直重写它们。

我知道有这样的
create
方法存在,但它并没有真正回答我的问题。如果问题不太清楚,很抱歉。好的,我刚刚编辑了以提供您的文字问题的答案。谢谢wim,但您的答案对于社区指南来说太短了?@j0eb:不不不不不不不。Python不是Ruby。在Python中传递块不是一件事,而
yield
意味着与Ruby中完全不同的东西。不。@j0eb您说过“解决方案不必涉及到或contextmanager”。我已经给你提供了一个Pythonic解决方案。不知道你还想问什么我对这个问题的灵感,如果有人感兴趣:为了异常安全,您需要使用try finally包装yield obj。不依赖
@contextmanager
并定义自己的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。我用“一些澄清”更新了我的问题,因为这似乎更适合于单一目的“我希望能够创建和保存对象问题”。您的
头文件
函数实际上是将上下文管理器放置在错误位置的另一个示例。文件已经是上下文管理器(它们在
\uuuuu exit\uuuu
中关闭自己)。因此,将
header\u file
设置为一个普通函数,返回一个打开的文件,该文件已写入头行。然后,您可以在
with
语句中使用它(该文件是上下文管理器),也可以自己调用函数,然后在文件上调用
close
。感谢您的响应。但是,我必须在文件上调用
close
。现在我的
@classmethod
def create_and_save(cls, **args):
    obj = cls(**args)
    obj.save()
    return obj
new_obj = MyObject.create_and_save(a=10)