English 中文(简体)
Python 中写入原子函数
原标题:Writing atomic functions in Python
In many systems, transactions are used to group operations together so that they are atomic—meaning they either all succeed or all fail. However, in Python itself does not seem to have built-in support for transactions outside of the context of databases For example I have two functions as follows: create_user_record(user_record) create_directory(directory_name) I want to wrap these functions in an operation in which if one of them fails, the other fails too. How can I achieve this?
问题回答
Transactions in databases are easy to group together because all operations are making the same thing: operating on data that has a well defined "previous" state which can be rolled back to. A "generic" atomic operation for "everything" can t be done for actions that have "side-effects" on the system. In your example itself, suppose both "create_user_record" and "create_directory" operations are on the file-systemm and "create_directory" fails? How is the Python runtime to know which was the file-system state before the fail, if these two operations are supposed to be atomically grouped? It is only possible if both function calls, in your case, are bound in an object which manages this external state, and which can then either perform an "undo" or just enqueue all actions and carry everything at once if things suceed. It is possible to create such a manager for file-system actions, for example - that would record every operation made, and prepare a "rolback" provision action in case a rollback is needed. But if you add further "side-effects" - either with I/O actions, or changing global program state (by calling methods on existing objects, changing the values of data structures or global variables), all of those actions need to be "managed" by the same entity. All in all: that is feasible, but you have to craft an special manager object, analogue to the database connection, and perform all changes that you want to be atomic by means of this object - either explicitly, or having the object implicitly shielding your side-effect operations - and the more domains you want to include in the "atomicity" (i.e. file I/O, global variable changes, network I/O using an specific API client already instantiated), the more complex your manager have to be. That said, such an object can be created, and the context-manager syntax of Python (the with statement block) is ideal to allow this. Here is a simple class which can manage access to global variables, and "undo" all changes to global variables in a similar way to a rollback: import inspect class GlobalVarsTransaction: def __enter__(self): self.previous_globals = inspect.currentframe().f_back.f_globals.copy() def __exit__(self, exc_type, exc_value, tb): if exc_type is None: # everything fine, allow all global vars set to persist! return # "rollback" global variables: globals = inspect.currentframe().f_back.f_globals new_keys = globals.keys() - self.previous_globals.keys() globals.update(self.previous_globals) for key in new_keys: del globals[key] Note that this is intended as example, and would cover few "real world" usages: in particular, it does not cover modification of a data structure in the global scope - i.e. if one changes elements inside a list, or dictionary bound to a variable in the current global scope that is not tracked in this manager. Here is a straightforward test function to showing the usage of the class above: def test(): global a, b, c, d, e, f, g a = b = c = d = e = f = g = 0 del g with GlobalVarsTransaction(): a = 1 b = 2 assert a == 1 and b == 2 try: c = 3 raise RuntimeError() d = 4 except RuntimeError: pass # partial state change took place assert c == 3 and d == 0 try: with GlobalVarsTransaction(): e = 5 raise RuntimeError() f = 6 except RuntimeError: pass assert e == 0 and f == 0 try: with GlobalVarsTransaction(): g = 7 raise RuntimeError() except RuntimeError: pass try: g # should fail, as "g" is deleted in the beggining of the test except NameError: pass else: assert False, "variable created in failed transactions is set" test()
There are multiple options to create atomic functions in Python. They are generally known as transactions and often require imports. You can use sqlite3 and connect that to your python3 program in order to begin a transaction and make sure it completes. You can use a bunch of Try-except-finally blocks to make sure that the entire block is completed and have it fail if it isn t. You can create a function that manually commits and rollbacks operations. Here is an example of #2: import os import shutil class AtomicOperation: def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): if exc_type: self.rollback() else: self.commit() def commit(self): def rollback(self): def create_user_record(user_record): pass def create_directory(directory_name): os.makedirs(directory_name) def perform_operations(user_record, directory_name): with AtomicOperation() as operation: try: create_user_record(user_record) create_directory(directory_name) except Exception as e: print(f"Operation failed: {e}")




相关问题
Can Django models use MySQL functions?

Is there a way to force Django models to pass a field to a MySQL function every time the model data is read or loaded? To clarify what I mean in SQL, I want the Django model to produce something like ...

An enterprise scheduler for python (like quartz)

I am looking for an enterprise tasks scheduler for python, like quartz is for Java. Requirements: Persistent: if the process restarts or the machine restarts, then all the jobs must stay there and ...

How to remove unique, then duplicate dictionaries in a list?

Given the following list that contains some duplicate and some unique dictionaries, what is the best method to remove unique dictionaries first, then reduce the duplicate dictionaries to single ...

What is suggested seed value to use with random.seed()?

Simple enough question: I m using python random module to generate random integers. I want to know what is the suggested value to use with the random.seed() function? Currently I am letting this ...

How can I make the PyDev editor selectively ignore errors?

I m using PyDev under Eclipse to write some Jython code. I ve got numerous instances where I need to do something like this: import com.work.project.component.client.Interface.ISubInterface as ...

How do I profile `paster serve` s startup time?

Python s paster serve app.ini is taking longer than I would like to be ready for the first request. I know how to profile requests with middleware, but how do I profile the initialization time? I ...

Pragmatically adding give-aways/freebies to an online store

Our business currently has an online store and recently we ve been offering free specials to our customers. Right now, we simply display the special and give the buyer a notice stating we will add the ...

Converting Dictionary to List? [duplicate]

I m trying to convert a Python dictionary into a Python list, in order to perform some calculations. #My dictionary dict = {} dict[ Capital ]="London" dict[ Food ]="Fish&Chips" dict[ 2012 ]="...

热门标签