Using context managers without block "c"

Below is an example of my method my_createand an example of using this method.

@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

To Ruby / Rails users this may seem familiar. It looks like a method ActiveRecord::create, with code inside a block withacting like, well, a block.

However:

with my_create(a=10) as new_obj:
    pass

new_obj.a  # => 10
new_obj.is_saved()  # => True

In the above example, I passed an empty "block" to my function my_create. Things work as expected ( my_objwas initialized and saved), but the formatting looks a bit uncomfortable, and the block withseems unnecessary.

I would prefer to call directly my_createwithout installing the passing block with. Unfortunately, this is not possible with my current implementation my_create.

my_obj = create(a=10)
my_obj  # => <contextlib.GeneratorContextManager at 0x107c21050>

__enter__ __exit__ GeneratorContextManager, .

:

my_create, "" ""? my_create. my_create .

with contextmanager. , , , generator a for, .

, - , , .

:

:

@contextmanager
def header_file(path):
    touch(path)
    f = open(path, 'w')
    f.write('This is the header')
    yield f
    f.close()

with header_file('some/path') as f:
    f.write('some more stuff')

another_f = header_file('some/other/path')

__enter__ __exit__ . . pass ing with, .

Ruby. , Python, ( pass ing with). , ( ?), .

+4
3

MyObject, .

class MyObject:

    @classmethod
    def create(cls, **attributes):
        obj = cls(**attributes)
        obj.save()
        return obj

, factory, Python . Django , Model.create(**args) , Model(**args) (, ).

my_create, "" ""?

.

+6

, , __exit__ . , . ( "", , , , , .)

, , , :

def create_and_save(**args):
    obj = MyObject(**args)
    obj.save()
    return obj

, . Pythonic, , MyObject . __enter__ __exit__:

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_and_save, , classmethod:

@classmethod
def create_and_save(cls, **args):
    obj = cls(**args)
    obj.save()
    return obj

:

new_obj = MyObject.create_and_save(a=10)

, , .

+3

, , , . , .

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 Celery Task.s , my_create, , .

my_create , , context_manager(my_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

Ideally, functions my_createwould not have to be accepted cls, and the wrapper block_acceptorwould handle this, but I don't have time to make these changes just now.

prophetic? no. useful? perhaps?

I'm still curious to know what others have come up with.

-1
source

Source: https://habr.com/ru/post/1693106/


All Articles