Multiline string literal preserves every symbol between opening and closing quotes, including indentation:
A possible solution is to remove indentation, Python will still correctly parse the code:
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:
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
This is why every built-in collection has an immutable version:
+ Immutable
+ Immutable
+ Immutable
+
And since it is just a proxy, not a new type, it reflects all the changes in the original mapping:
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.
Fun fact: for explanation what is SQL Injection the documentation links xkcd about Bobby tables instead of some smart article or Wikipedia page.
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
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:
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 (
ones like the diamond operator (
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
But if you enable the "Barry as FLUFL" feature the behavior changes:
Unfortunately, this easter egg is only working in interactive mode (REPL), but not in usual
By the way, it's interesting that this feature is marked as becoming mandatory in Python 4.0.
Many Pythonistas know about the notorious "walrus" operator (
:=
), but there are less famousones 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.
Have you ever wondered how do relative imports work?
Im pretty sure that you've done something like that at some point:
It's using a special magic attribute on the module called
Lets say you have the following structure:
The value of
Note that for
So when you're doing
You can actually hack
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
that submodule.
So if you do
You can play around with
Create simple Python module anywhere on your system:
__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
+ Unclosed files.
+ Unawaited coroutines.
+ Unknown encoding for
+ Memory allocation issues.
-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:
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
To prevent producing invalid JSON, pass
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:
+
+
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.
+
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.
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}!')
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
+ 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
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
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:
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