Compilation @pythonetc, June 2018


Hey. I am the author of the channel @ pythonetc with tips about Python in particular and about programming in general. From this month we are launching a series of collections with the best posts for the month translated into Russian.


Call chain data transfer


When you want to pass some information along a chain of calls, you usually use the easiest way: pass data in the form of function arguments.


But sometimes it is very inconvenient to change all the functions in the chain, only to transfer a new piece of data. In such cases, it is better to create a peculiar context that the functions will use. How to do it?


The simplest solution is a global variable. In Python, you can use modules and classes as context keepers, since, strictly speaking, they are also global variables. Perhaps you periodically do this, say, when you create loggers.


If you have a multi-threaded application, then ordinary global variables will not help, because they are not thread safe. At the same time you can run multiple call chains, and each will need its own context. The threading module provides a thread-safe threading.local() object. Save any data in it by simply referring to the attributes: threading.local().symbol = '@' .


However, both approaches are not concurrency-safe, that is, they will not work in coruntine call chains, in which the korutins may not call other korutins, but do await on them. If the quorutine is in a standby state, the event loop can start another quorutine from another chain. This option will not work:


 import asyncio import sys global_symbol = '.' async def indication(timeout): while True: print(global_symbol, end='') sys.stdout.flush() await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'): loop = asyncio.get_event_loop() global global_symbol global_symbol = symbol loop.create_task(indication(indication_t)) await asyncio.sleep(t) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather( sleep(1, 0.1, '0'), sleep(1, 0.1, 'a'), sleep(1, 0.1, 'b'), sleep(1, 0.1, 'c'), )) 

You can solve the problem by forcing the event loop to save and restore the context each time you return to the coroutine. So does the aiotask_context module, which with the help of loop.set_task_factory changes the way tasks are created. This option will work:


 import asyncio import sys import aiotask_context as context async def indication(timeout): while True: print(context.get('symbol'), end='') sys.stdout.flush() await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'): loop = asyncio.get_event_loop() context.set(key='symbol', value=symbol) loop.create_task(indication(indication_t)) await asyncio.sleep(t) loop = asyncio.get_event_loop() loop.set_task_factory(context.task_factory) loop.run_until_complete(asyncio.gather( sleep(1, 0.1, '0'), sleep(1, 0.1, 'a'), sleep(1, 0.1, 'b'), sleep(1, 0.1, 'c'), )) 

Create SVG


SVG is a vector graphic format that stores image information in the form of all forms and numbers required for drawing to XML. For example, the orange circle can be represented as follows:


 <svg xmlns="http://www.w3.org/2000/svg"> <circle cx="125" cy="125" r="75" fill="orange"/> </svg> 

Since SVG is a subset of XML, you can create SVG files in any language quite easily. Including in Python, for example, using lxml. But there is a svgwrite module, created just to create an SVG.


Here is an example of how you can display the Rekaman sequence in the form of a diagram that you saw at the beginning of the article.


Appeal to external scopes


When you use a variable in Python, it first looks for it in the current scope. If it does not find it, then it searches already in the area one level higher. And so on until the global namespace comes up.


 x = 1 def scope(): x = 2 def inner_scope(): print(x) # prints 2 inner_scope() scope() 

But variable assignment works differently. A new variable is always created in the current scope, unless global or nonlocal :


 x = 1 def scope(): x = 2 def inner_scope(): x = 3 print(x) # prints 3 inner_scope() print(x) # prints 2 scope() print(x) # prints 1 

global allows the use of variables of the global namespace, and in the case of nonlocal Python looks for a variable in the nearest ambient context. Compare:


 x = 1 def scope(): x = 2 def inner_scope(): global x x = 3 print(x) # prints 3 inner_scope() print(x) # prints 2 scope() print(x) # prints 3 x = 1 def scope(): x = 2 def inner_scope(): nonlocal x x = 3 print(x) # prints 3 inner_scope() print(x) # prints 3 scope() print(x) # prints 1 

Running scripts


python supports several ways to run a script. The normal python foo.py simply executes foo.py


You can also use the python -m foo construction python -m foo . If foo not a package, then the system will find foo.py in sys.path and execute. If it is, Python will execute foo/__init__.py , and then foo/__main__.py . Notice that the __name__ variable is in the course of executing __init__.py takes the value foo , and in the course of executing __main__.py is __main__ .


You can also use the python dir/ form, or even python dir.zip . Then python will search for dir/__main__.py , and if it finds it, it will.


 $ ls foo __init__.py __main__.py $ cat foo/__init__.py print(__name__) $ cat foo/__main__.py print(__name__) $ python -m foo foo __main__ $ python foo/ __main__ $ python foo/__init__.py __main__ 

The number of seconds since the beginning of the era


Before the advent of Python 3.3, it was difficult to convert a datetime object to the number of seconds since the beginning of the Unix era.


The most logical way is to use the strftime method, which can format datetime . By taking %s as a format, you can get a timestamp.


 naive_time = datetime(2018, 3, 31, 12, 0, 0) utc_time = pytz.utc.localize(naive_time) ny_time = utc_time.astimezone( pytz.timezone('US/Eastern')) 

ny_time is absolutely the same time as utc_time , but written in the same way as is customary in New York:


 # utc_time datetime.datetime(2018, 3, 31, 12, 0, tzinfo=<UTC>) # utc_time datetime.datetime(2018, 3, 31, 8, 0, tzinfo=<DstTzInfo 'US/Eastern' ...>) 

If the time is the same, then the timestamps should be equivalent:


 In : int(utc_time.strftime('%s')), int(ny_time.strftime('%s')) Out: (1522486800, 1522468800) 

Uh, what? Why are they different? The fact is that you cannot use strftime to solve this problem. In Python, strftime does not support %s at all as an argument, and this only works because the C-library platform's strftime() function is called internally. But, as you can see, the time zone of the datetime object is completely ignored.


The correct result can be obtained with a simple subtraction:


 In : epoch_start = pytz.utc.localize( datetime(1970, 1, 1)) In : (utc_time - epoch_start).total_seconds() Out: 1522497600.0 In : (utc_time - epoch_start).total_seconds() Out: 1522497600.0 

And if you use Python 3.3+, you can solve the problem using the timestamp method of the datetime class: utc_time.timestamp() .

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


All Articles