简体   繁体   中英

Should we use a decorator or a context manager to handle undo queues in Maya?

I know context managers and decorators are two completely unrelated concepts in Python, but both can be used to achieve the same goal. It can be sometimes confusing which one is the best practice to use. In Maya, if you want a list of actions to be grouped as a single element of the undo queue, you need to open and close the chunk. It is quite risky because if an exception is raised while the chunk is open, it can break the undo queue entirely.

Let's say I want to execute while the undo chunk is open the following code:

def do_stuff():
    print("I do stuff...")

One way is to write:

cmds.undoInfo(openChunk=True)
try:
    do_stuff()
finally:
    cmds.undoInfo(closeChunk=True)

It is obviously a one-off solution and is not very practical. I know I can automatize it as a decorator as such:

def open_undo_chunk(func):
    def wrapper():
        cmds.undoInfo(openChunk=True)
        print("chunck opened")
        func()
        cmds.undoInfo(closeChunk=True)
        print("chunck closed")
    return wrapper
    

@open_undo_chunk
def do_stuff():
    print("I do stuff...")
    
do_stuff()

But another way to do this would be to use the context manager.

class Open_undo_chunk():
    def __enter__(self):
        cmds.undoInfo(openChunk=True)
        print("chunck opened")
        return
    
    def __exit__(self, exec_type, exec_val, traceback):
        cmds.undoInfo(closeChunk=True)
        print("chunck closed")

with Open_undo_chunk():
    do_stuff()

Which one is the best practice and why in this context?

I think best practice usually comes down to what style suits you best. I don't particularly think there's a noticeable performance difference between these two methods, but perhaps someone can do some simple benchmarking for us.

In a very subjective answer to your question, I personally prefer with-statements. It indicates that you're executing code with a resource that will dispose itself once completed. This is also usually how you execute built-in contexts like when opening a file, etc.

One more added benefit is that you don't need to define a method to run in the with-statement.

However, you can save yourself some effort by using contextlib to generate your context:

import contextlib
import maya.cmds as cmds

@contextlib.contextmanager
def undoable():
    '''Insert undo chunk'''

    try:
        cmds.undoInfo(openChunk=True)
        yield
    finally:
        cmds.undoInfo(closeChunk=True)


# Simple example of invocation
with undoable():
    print("I'm doing stuff")
    cmds.polySphere(name='I_Am_Undoable')

Here's a thread with more information on the yield statement . Above I'm essentially executing yield None , which means you can't really do anything with the resource undoable() returns. If you for instance had a file pointer or something like that, you would yield <resource> to send it back to the calling context.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM