Python etc
6.11K subscribers
18 photos
194 links
Regular tips about Python and programming in general

Owner — @pushtaev

© CC BY-SA 4.0 — mention if repost
Download Telegram
Multiline string literal preserves every symbol between opening and closing quotes, including indentation:

def f():
return """
hello
world
"""
f()
# '\n hello\n world\n '


A possible solution is to remove indentation, Python will still correctly parse the code:

def f():
return """
hello
world
"""
f()
# '\nhello\n world\n'


However, it's difficult to read because it looks like the literal is outside of the function body but it's not. So, a much better solution is not to break the indentation but instead remove it from the string content using textwrap.dedent:

from textwrap import dedent

def f():
return dedent("""
hello
world
""")
f()
# '\nhello\n world\n'
If any function can modify any passed argument, how to prevent a value from modification? Make it immutable! That means the object doesn't have methods to modify it in place, only methods returning a new value. This is how numbers and str are immutable. While list has append method that modifies the object in place, str just doesn't have anything like this, all modifications return a new str:

a = b = 'ab'
a is b # True
b += 'cd'
a is b # False


This is why every built-in collection has an immutable version:

+ Immutable list is tuple.
+ Immutable set is frozenset.
+ Immutable bytearray is bytes.
+ dict doesn't have an immutable version but since Python 3.3 it has types.MappingProxyType wrapper that makes it immutable:

from types import MappingProxyType

orig = {1: 2}
immut = MappingProxyType(orig)

immut[3] = 4
# TypeError: 'mappingproxy' object does not support item assignment


And since it is just a proxy, not a new type, it reflects all the changes in the original mapping:

orig[3] = 4
immut[3]
# 4
Python has a built-in module sqlite3 to work with SQLite database.

import sqlite3
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute('SELECT UPPER(?)', ('hello, @pythonetc!',))
cur.fetchone()
# ('HELLO, @PYTHONETC!',)


Fun fact: for explanation what is SQL Injection the documentation links xkcd about Bobby tables instead of some smart article or Wikipedia page.
Since Python doesn't have a char type, an element of str is always str:

'@pythonetc'[0][0][0][0][0]
# '@'


This is an infinite type and you can't construct in a strictly typed language (and why would you?) because it's unclear how to construct the first instance (thing-in-itself?). For example, in Haskell:

Prelude> str = str str

<interactive>:1:7: error:
• Occurs check: cannot construct the infinite type: t1 ~ t -> t1
Some operators in Python have special names.
Many Pythonistas know about the notorious "walrus" operator (:=), but there are less famous
ones like the diamond operator (<>) — it's similar to the "not equals" operator but written in SQL style.
The diamond operator was suggested in PEP 401 as one of
the first actions of the new leader of the language Barry Warsaw after Guido went climbing Mount Everest.
Luckily, it was just an April Fool joke and the operator was never really a part of the language.
Yet, it's still available but hidden behind the "import from future" flag.

Usually you compare for non-equality using !=:

>>> "bdfl" != "flufl"
True


But if you enable the "Barry as FLUFL" feature the behavior changes:

>>> from __future__ import barry_as_FLUFL
>>> "bdfl" != "flufl"
File "<stdin>", line 1
"bdfl" != "flufl"
^
SyntaxError: with Barry as BDFL, use '<>' instead of '!='
>>> "bdfl" <> "flufl"
True


Unfortunately, this easter egg is only working in interactive mode (REPL), but not in usual *.py scripts.

By the way, it's interesting that this feature is marked as becoming mandatory in Python 4.0.
This guest post is written by Pythonic Attacks channel.
Have you ever wondered how do relative imports work?


Im pretty sure that you've done something like that at some point:


from . import bar
from .bar import foo



It's using a special magic attribute on the module called __package__.
Lets say you have the following structure:


foo/
__init__.py
bar/
__init__.py
main.py



The value of __package__ for foo/__init__.py is set to "foo", and for foo/bar/__init__.py its "foo.bar".


Note that for main.py __package__ isn't set, that's because main.py is not in a package.


So when you're doing from .bar import buz within foo/__init__.py, it simply appends "bar" to foo/__init__.py's __package__ attribute, esentially it gets translated to from foo.bar import buz.


You can actually hack __package__, e.g:


>>> __package__ = "re"
>>> from . import compile
>>> compile
<function compile at 0x10e0ee550>
Modules have a magic attribute called __path__. Whenever you're doing subpackage imports, __path__ is being searched for
that submodule.

__path__
looks like a list of path strings, e.g ["foo/bar", "/path/to/location"].

So if you do from foo import bar, or import foo.bar, foo's __path__ is being searched for bar. And if found - loaded.

You can play around with __path__ to test it out.

Create simple Python module anywhere on your system:


$ tree
.
└── foo.py

$ cat foo.py
def hello():
return "hello world"

Then, run the interpreter there and do the following:
python
>>> import os
>>> os.__path__ = ["."]
>>> from os.foo import hello
>>> hello()
'hello world'

As you can see, foo is now available under os:
python
>>> os.foo
<module 'os.foo' from './foo.py'>
Python 3.7 introduced Development Mode. The mode can be activated with the -X dev argument and it makes the interpreter produce some helpful warnings. For instance:

+ Unclosed files.
+ Unawaited coroutines.
+ Unknown encoding for str.encode (by default, it is unchecked for empty strings).
+ Memory allocation issues.

$ echo 'open("/dev/null")' > tmp.py
$ python3 -X dev tmp.py
tmp.py:1: ResourceWarning: unclosed file <_io.TextIOWrapper name='/dev/null' mode='r' encoding='UTF-8'>
open("/dev/null")
ResourceWarning: Enable tracemalloc to get the object allocation traceback
JSON states for "JavaScript Object Notation". It's a subset of JavaScript and representation of values is based on how they are represented in JavaScript:

import json
json.dumps(1) # '1'
json.dumps(1.2) # '1.2'
json.dumps('hi') # '"hi"'
json.dumps({}) # '{}'
json.dumps([]) # '[]'
json.dumps(None) # 'null'
json.dumps(float('inf')) # 'Infinity'
json.dumps(float('nan')) # 'NaN'


The last two examples are valid JavaScript but explicitly forbidden by RFC 4627 "The application/json Media Type for JSON":

> Numeric values that cannot be represented as sequences of digits (such as Infinity and NaN) are not permitted.

And so, the inf / nan values, successfully serialized in Python, can fail deserialization in another language. For example, in Go:

import "encoding/json"

func main() {
var v float64
err := json.Unmarshal(`Infinity`, &v)
println(err)
// Output: invalid character 'I' looking for beginning of value
}


To prevent producing invalid JSON, pass allow_nan=False argument:

json.dumps(float('nan'), allow_nan=False)
# ValueError: Out of range float values are not JSON compliant
Internally, the module re uses 2 undocumented libraries:

+ sre_parse to parse regular expressions into an abstract syntax tree.
+ sre_compile to compile parsed expression.

The first one can be used to see how a regexp was parsed by Python. There are many better tools and services (like regex101.com) to debug regular expressions but this one is already in the stdlib.

>>> import sre_parse
>>> sre_parse.parse(r'([Pp]ython)\s?etc').dump()
SUBPATTERN 1 0 0
IN
LITERAL 80
LITERAL 112
LITERAL 121
LITERAL 116
LITERAL 104
LITERAL 111
LITERAL 110
MAX_REPEAT 0 1
IN
CATEGORY CATEGORY_SPACE
LITERAL 101
LITERAL 116
LITERAL 99
Hi there! As you’ve probably already noticed there is no activity here at the moment. I hereby declare the second season to be officially over. I currently have no specific plan nor ideas for the third season. DM @pushtaev if you do.

For the time being, you can enjoy the archive. Posts of the channel are mostly relevant for this day.
You are also welcome to consider joining my team. We still develop the voice assistant and beautiful devices for her to live in:
Finally, you can express your gratitude towards the authors by donating via the new telegram donation service. Thanks and hope to see you in the third season one day.
channel = '@pythonetc'
print(f'Happy new Year, {channel}!')

# there are our top posts from 2021
by_likes = {
'join-lists': 236,
'dev-mode': 181,
'is-warning': 170,
'str-concat': 166,
'class-scope': 149,
}
by_forwards = {
'class-scope': 111,
'dev-mode': 53,
'join-lists': 50,
'str-concat': 44,
'eval-strategy': 36,
}
by_views = {
'__path__': 7_736,
'dev-mode': 7_113,
'immutable': 6_757,
'class-scope': 6_739,
'sre-parse': 6_661,
}

from datetime import date
from textwrap import dedent
if date.today().year == 2022:
print(dedent("""
The season 2.6 is coming!
This is what awaits:
"""))
print(
'native telegram reactions instead of buttons',
'deep dive into garbage collection, generators, and coroutines',
'the season is still ran by @orsinium',
'as always, guest posts and donations are welcome',
sep='\n',
)

print('See you next year \N{Sparkling Heart}!')
The module atexit allows registering hooks that will be executed when the program terminates.

There are only a few cases when it is NOT executed:

+ When os._exit (don't confuse with sys.exit) is called.
+ When the interpreter failed with a fatal error.
+ When the process is hard-killed. For example, someone executed kill -9 or the system is ran out of memory.

In all other cases, like an unhandled exception or sys.exit, the registered hooks will be executed.

A few use cases:

+ Finish pending jobs
+ Send pending log messages into the log system
+ Save interactive interpreter history

However, keep in mind that there is no way to handle unhandled exceptions using atexit because it is executed after the exception is printed and discarded.

import atexit

atexit.register(print, 'FINISHED')
1/0

Output:

Traceback (most recent call last):
File "example.py", line 4, in <module>
1/0
ZeroDivisionError: division by zero
FINISHED
The module faulthandler allows registering a handler that will dump the current stack trace in a specific file (stderr by default) upon receiving a specific signal or every N seconds.

For example, dump stack trace every 2 seconds:

import faulthandler
from time import sleep

faulthandler.dump_traceback_later(
timeout=2,
repeat=True,
)
for i in range(5):
print(f"iteration {i}")
sleep(1)

Output:

iteration 0
iteration 1
Timeout (0:00:02)!
Thread 0x00007f8289147740 (most recent call first):
File "tmp.py", line 10 in <module>
iteration 2
iteration 3
Timeout (0:00:02)!
Thread 0x00007f8289147740 (most recent call first):
File "tmp.py", line 10 in <module>
iteration 4