import random as r; r.seed(1337); " ".join([f'{b:02x}' for b in r.randbytes(20)])Wynik:
'03 0d 25 9e 7b e3 ef ec a5 17 84 88 cd b1 ba b5 ff 3c a8 5d'
ekstaza, geniusz, przebłysk, olśnienie, półprawdy, półśrodki, przemilczenia, zaćmienia, głupstwa, kłamstewka, oszustwa, hultajstwo, wyrachowanie, nieprawda, nieobiektywność, niepodważalna prawda, nierówność, nieomylność, słuszność, perfekcja, krnąbrność ... niegodziwość
import random as r; r.seed(1337); " ".join([f'{b:02x}' for b in r.randbytes(20)])Wynik:
'03 0d 25 9e 7b e3 ef ec a5 17 84 88 cd b1 ba b5 ff 3c a8 5d'
import asyncio async def short_task(): print('short_task before') await asyncio.sleep(2) print('short_task after') async def print_task(): print('print_task') async def long_task(): print('long_task before') await asyncio.sleep(5) print('long_task after') async def draw_task(): print('draw_task') def main(): loop = asyncio.get_event_loop() loop.create_task(print_task()) loop.create_task(long_task()) loop.run_until_complete(short_task()) loop.run_until_complete(draw_task()) loop.close() if __name__ == '__main__': main()Program czeka aż zakończą się dwa zadania: short_task i draw_task, wcześniej uruchamiająć long_task. Ponieważ short_task i draw_task kończą się szybciej dostajemy ostrzeżenie, o wciąż działającym long_taks.
print_task long_task before short_task before short_task after draw_task Task was destroyed but it is pending! task: <Task pending coro=<long_task() done, defined at /home/beru/python_asyncio/run_until.py:13> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f47d9b217d0>()]>>Bardziej zaawansowany przykład serwera/czatu. Tutaj mamy do czynienia z trzema rodzajami zdarzeń: uruchomienie serwera (otwarcie portów i nasłuchiwanie), oczekiwanie na tekst na stdio oraz oczekiwanie na nadejście wiadomości od klienta. Samo oczekiwanie na tekst składa się z dwóch zdarzeń: pojawienie się tekstu na stdio i zapisanie go do kolejki, oraz odczytanie, gdy coś w tej kolejce się znajduje.
import sys import asyncio def main(): queue = asyncio.Queue() loop = asyncio.get_event_loop() # Start monitoring the fd file descriptor for read availability and invoke # callback with the specified arguments once fd is available for reading loop.add_reader(sys.stdin, got_stdin_data, queue) fut = loop.create_future() fut.add_done_callback(cancel_all_task) coro = loop.create_server(lambda: Chat(loop, queue, fut), '127.0.0.1', 7777) server = loop.run_until_complete(coro) # Run until Ctrl+C is pressed or loop is stopped try: loop.run_forever() except KeyboardInterrupt: print('[+] Keyboard exception') cancel_all_task() # Stop server. Closing listening sockets it's done asynchronously, so # wait_closed() need to be used to ensure. server.close() loop.run_until_complete(server.wait_closed()) loop.close() def got_stdin_data(queue): loop = asyncio.get_event_loop() loop.create_task(queue.put(sys.stdin.readline())) def cancel_all_task(result=None): print('[+] Cancel all tasks') loop = asyncio.get_event_loop() for task in asyncio.Task.all_tasks(): task.cancel() loop.create_task(stop_loop()) async def stop_loop(): print('[+] Stop loop') loop = asyncio.get_event_loop() loop.stop() class Chat(asyncio.Protocol): def __init__(self, loop, queue, fut): self.loop = loop self.queue = queue self.fut = fut def connection_made(self, transport): peername = transport.get_extra_info('peername') print('[+] Connection from:', peername) self.transport = transport self.loop.create_task(self._wait_for_stdin_data()) def connection_lost(self, exc): print('[+] Connection lost') self.fut.set_result(True) def data_received(self, data): message = data.decode() print('[+] Data received: {!r}'.format(message)) if message.strip() == "exit": self.fut.set_result(True) def _send_reply(self, reply): print('[+] Data send: {!r}'.format(reply)) self.transport.write(reply.encode()) self.loop.create_task(self._wait_for_stdin_data()) async def _wait_for_stdin_data(self): reply = await self.queue.get() self._send_reply(reply) if __name__ == '__main__': main()W celu połączenia się z serwerm:
nc 127.0.0.1 7777Działanie:
[+] Connection from: ('127.0.0.1', 45260) [+] Data received: 'asdf\n' [+] Data received: 'exit\n' [+] Cancel all tasks [+] Stop loop
# Katalog roboczy mkdir ~/opencv_workspace cd ~/opencv_workspace git clone https://github.com/opencv/opencv.git git clone https://github.com/opencv/opencv_contrib.git # Instalacja virualenv dla Python-a. Przyda się numpy i scipy virtualenv -p python3 venv source venv/bin/activate pip install numpy pip install scipy # Konfiguracja za pomocą CMake. # Wszystko co potrzebne do budowania znajdzie się w katalogu build, # a zainstalowane zostanie do katalogu $VIRTUAL_ENV/local/ cd opencv mkdir build cmake -B build/ -D CMAKE_BUILD_TYPE=Debug \ -D OPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules/ \ -D CMAKE_INSTALL_PREFIX=$VIRTUAL_ENV/local/ \ -D PYTHON_EXECUTABLE=$VIRTUAL_ENV/bin/python \ -D PYTHON_PACKAGES_PATH=$VIRTUAL_ENV/lib/python3.7/site-packages \ -D INSTALL_PYTHON_EXAMPLES=ON # Kompilacja i instalacja (do katalogu $VIRTUAL_ENV/local/) cd build make -j4 make installPrzykładowy program
#include <opencv2/opencv.hpp> #include <iostream> int main() { cv::Mat grayImg = cv::imread("color.png", cv::IMREAD_GRAYSCALE); cv::imwrite("gray.png", grayImg); }Kompilacja:
cd ~/opencv_workspace g++ -I./venv/local/include/opencv4 -L./venv/local/lib -Wl,-rpath=./venv/local/lib \ main.cpp \ -lopencv_core \ -lopencv_imgcodecs \ -lopencv_imgprocWynik:
mkdir vpython3 cd vpython3/ virtualenv -p python3 venv source venv/bin/activate pip3 install jupyter pip3 install vpythonW API pojawiły się pewne drobne zmiany, dlatego postanowiłem przepisać standardowy przykład "bouncing ball" na nową wersję. Przy okazji odkryłem na stronie projektu informacje o module povexport do przechwytywania obrazu animacji. Wcześniej przechwytywałem obraz z ekranu (za pomocą PIL) i wycinałem obszar w którym pojawia się okno animacji.
import vpython as vp import povexport scene = vp.canvas(title='3D scene', x=0, y=0, width=400, height=400, center=vp.vector(0, 0, -0), autoscale=False) vp.display(scene) starting_height = 4 floor = vp.box(pos=vp.vector(0, 0, 0), length=4, height=0.5, width=4, color=vp.color.green) ball = vp.sphere(pos=vp.vector(0, starting_height, 0), radius=1, color=vp.color.red) ball.velocity = vp.vector(0, -1, 0) dt = 0.01 frame = 0 while 1: vp.rate(100) ball.pos = ball.pos + ball.velocity*dt if ball.pos.y < ball.radius: ball.velocity.y = abs(ball.velocity.y) else: ball.velocity.y = ball.velocity.y - 9.8*dt file_name = 'img-%04d.pov' % frame inclist = ['colors.inc', 'stones.inc', 'woods.inc', 'metals.inc'] povexport.export(scene, filename=file_name, include_list=inclist) frame += 1 if ball.pos.y > starting_height: breakCo się zmieniło (dla mnie):
# ... # cpos = 1.5*displayscale*canv.camera.pos # 1.5 is a not understood fudge factor cpos = 10*displayscale*canv.camera.pos # ...W zasadzie, można pokusić się również o stworzenie skryptu, który edytuje pliki .pov i zmienia linijki z ustawieniami kamery. Zmiana w pliku .pov, którą uzyskałem:
camera { right <-image_width/image_height, 0, 0> // vpython uses right-handed coord. system location <0.000000, 0.000000, 173.205081> up <0.000000, 1.000000, 0.000000> look_at <0.000000, 0.000000, 0.000000> angle 60.000000 }Przerobienie plików z formatu .pov na .png:
for i in *.pov; do povray Height=400 Width=400 Display=false Output_File_Type=N $i 2>/dev/null; doneNowa wersja skryptu ([1], [2]) do tworzenia animacji video/gif. Wyeliminowałem straty, które pojawiały się podczas składaniu video z plików .png, przez zastosowania kodeka H.264 i manipulacji "Constant Rate Factor" (uwaga, z ustawieniami CRF trzeba ostrożnie)
#!/bin/sh PALETTE_FILE="tmp_pallete.png" VIDEO_FILE="output_video.mp4" INPUT_FILES="img-%4d.png" OUTPUT_FILE="output.gif" FILTERS="fps=25" ffmpeg -r 100 -i $INPUT_FILES -c:v libx264 -crf 0 -preset veryslow $VIDEO_FILE ffmpeg -v warning -i $VIDEO_FILE -vf "$FILTERS,palettegen" -y $PALETTE_FILE ffmpeg -v warning -i $VIDEO_FILE -i $PALETTE_FILE -lavfi "$FILTERS [x]; [x][1:v] paletteuse" -y $OUTPUT_FILEEfekt końcowy:
#!/usr/bin/env python # -*- coding: utf-8 -*- import curses import random from time import sleep import os import subprocess import collections Light = collections.namedtuple('Light', ['y', 'x', 'symbol']) class LightningIndex: def __init__(self, index, branch): self.index = index self.branch = branch def createLightning(): x = curses.COLS / 2 + random.randint(-10, 10) y = 0 lightning = [Light(y, x, random.choice('/|\\'))] branches = [] while y < curses.LINES - 1: _, _, prev_symbol = lightning[-1] if prev_symbol == '|': y += 1 symbol = random.choice('/|\\') elif prev_symbol == '/': symbol = random.choice('/|\\_') if symbol == '/' or symbol == '_': x -= 1 if symbol != '_': y += 1 elif prev_symbol == '\\': symbol = random.choice('/|\\_') if symbol == '\\' or symbol == '_': x += 1 if symbol != '_': y += 1 elif prev_symbol == '_': if lightning[-1].x < lightning[-2].x: symbol = random.choice('/_') x -= 1 else: symbol = random.choice('\\_') x += 1 if symbol != '_': y += 1 if random.randint(0, 30) == 1: branches.append(createBranch(lightning[-1], Light(y, x, symbol))) lightning.append(Light(y, x, symbol)) return lightning, branches def createBranch(prev, root): branch = [prev, root] y = root.y x = root.x for i in range(random.randint(15, 30)): _, _, prev_symbol = branch[-1] if prev_symbol == '|': y += 1 symbol = random.choice('/\\') elif prev_symbol == '/': symbol = random.choice('/___') if symbol == '/' or symbol == '_': x -= 1 if symbol != '_': y += 1 elif prev_symbol == '\\': symbol = random.choice('\\___') if symbol == '\\' or symbol == '_': x += 1 if symbol != '_': y += 1 elif prev_symbol == '_': if branch[-1].x < branch[-2].x: symbol = random.choice('/___') x -= 1 else: symbol = random.choice('\\___') x += 1 if symbol != '_': y += 1 if x < 0 or x >= curses.COLS or y < 0 or y >= curses.LINES: break branch.append(Light(y, x, symbol)) del branch[0] return branch def blink(lightning, attr1, attr2): for l in lightning: scr.addstr(l.y, l.x, l.symbol, attr1) sleep(0.1) scr.refresh() for l in lightning: scr.addstr(l.y, l.x, l.symbol, attr2) sleep(0.1) scr.refresh() def indexer(light, branches): res = [] for bs in branches: if light.x == bs[0].x and light.y == bs[0].y: res.append(LightningIndex(0, bs)) return res scr = curses.initscr() curses.start_color() # Potrzebne do definiowania kolorów curses.use_default_colors() # Używaj kolorów terminala curses.halfdelay(5) # Ile częśći sekundy czekamy na klawisz, od 1 do 255 curses.noecho() # Nie drukuje znaków na wejściu curses.curs_set(False) # Wyłącza pokazywanie kursora GRAY = 2 curses.init_color(1, 600, 600, 600) # Zdefinuj kolor pod identyfikatorem 1, # daje kolor RGB, ale wartości 0-1000 curses.init_pair(GRAY, 1, -1) # Stwórz parę tło/czcionka. -1 przeźroczyste WHITE = 3 curses.init_pair(WHITE, curses.COLOR_WHITE, -1) random.seed(4876) while True: ch = scr.getch() # Oczekiwanie aż upłynie czas, lub albo zostanie # naciśnięty klawisz scr.clear() # Czyści ekran if ch == ord('q'): break lightning, branches = createLightning() indexed = [LightningIndex(0, lightning)] for l in lightning: indexed += indexer(l, branches) for i in indexed: if i.index >= len(i.branch): continue light = i.branch[i.index] scr.addstr(light.y, light.x, light.symbol, curses.color_pair(GRAY)) i.index += 1 sleep(0.01) scr.refresh() # Odświeżanie ekranu blink(lightning, curses.A_BOLD | curses.color_pair(WHITE), curses.A_NORMAL | curses.color_pair(WHITE)) blink(lightning, curses.A_BOLD | curses.color_pair(WHITE), curses.A_NORMAL | curses.color_pair(WHITE)) curses.endwin() # Przywraca terminal do oryginalnych ustawieńLinki:
c:\Python34\Scripts>pip install cryptography c:\Python34\Scripts>pip install pycrypto c:\Python34\Scripts>pip install paramiko c:\Python34\Scripts>pip install py2exeDomyślny pip dostarczony wraz z interpreterem miał kłopot, aby zainstalować niektóre pakiety. Okazało się, że aktualizacja do najnowszej wersji rozwiązuje problem.
You are using pip version 7.1.2, however version 8.1.2 is available. You should consider upgrading via the 'python -m pip install --upgrade pip' command.Problemem okazała się również wersja pythona, której używałem. py2exe działa do wersji 3.4. Wersji 3.5 towarzyszą jakieś zawirowania i trzeba będzie na nią jeszcze poczekać.
Building 'dist\rforward.exe'. error: [Errno 2] No such file or directory: 'c:\\Python35\\lib\\site-packages\\py2exe\\run-py3.5-win-amd64.exe'Aby stworzyć plik wykonywalny należy stworzyć skrypt setup.py, w którym wyszczególnimy z jakich plików składa się nasz program.
from distutils.core import setup import py2exe from glob import glob setup(console=['rforward.py'], data_files = [('', ['C:\\Windows\\System32\\msvcr100.dll', 'C:\\Windows\\System32\\msvcp100.dll'])])To czego może zabraknąć na docelowej maszynie, to biblioteki z Visual Studio 2010, których wymaga do działania python. Można je również zainstalować za pomocą Microsoft Visual C++ 2010 Redistributable Package (x86). Ja wybrałem wersję na 32-bitową, zgodną z zainstalowanym interpreterem. Tutorial py2exe (dla starszej wersji pythona), ostrzega o restrykcjach odnośnie kopiowania tych bibliotek, a mianowicie powinno kopiować się wszystkie trzy, a nie tylko wybraną oraz manifest. Pozostawię to jako kwestię otwartą, ponieważ, nie mogłem znaleźć tego w dokumentacji, ponadto jeden z plików (msvcp100.dll), gdzieś wyparował. Z tego co wiem do działania wymagany jest jedynie msvcr100.dll (nie testowałem). Pliki należy skopiować, do głównego katalogu, tak, by exe mógł je odnaleźć.
c:\Python34\python.exe setup.py py2exe
CPython implementation detail: In CPython, due to the Global Interpreter Lock, only one thread can execute Python code at once (even though certain performance-oriented libraries might overcome this limitation). If you want your application to make better use of the computational resources of multi-core machines, you are advised to use multiprocessing or concurrent.futures.ProcessPoolExecutor. However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.Niemniej jednak Python posiada stosowne API, które da się wykorzystać w innym interpreterze bez ograniczeń GIL, albo przy zadaniach typu I/O.
# -*- coding: utf-8 -*- from Queue import Queue from threading import Thread, current_thread def worker(): while True: item = q.get() print current_thread().name + ': ' + str(item[0] + item[1]) q.task_done() q = Queue() num_worker_threads = 3 for i in range(num_worker_threads): t = Thread(target=worker) t.daemon = True t.start() for item in [(1, 2), (4, 7), (5, 2)]: q.put(item) q.join()Wynik:
Thread-2: 3 Thread-2: 11 Thread-3: 7
(gdb) python >import os >print('pid %d' % os.getpid()) >end pid 17865Odczyt informacji o breakpointach:
Reading symbols from a.out...done. (gdb) b 8 Breakpoint 1 at 0x400589: file main.cpp, line 8. (gdb) info break Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000400589 in main() at main.cpp:8 (gdb) r Starting program: /home/user/cpp_gdb_test/a.out is i now 7 Breakpoint 1, main () at main.cpp:8 8 i++; (gdb) python >print(gdb.breakpoints()[0].location) >end /home/user/cpp_gdb_test/main.cpp:8Ustawienie braekpointa z poziomu pythona:
(gdb) python gdb.Breakpoint('11') Breakpoint 2 at 0x4005a1: file main.cpp, line 11. (gdb) c Continuing. is i now 8 Breakpoint 2, main () at main.cpp:11 11 printf("Hello world!!!\n");
#!/usr/bin/env python # -*- coding: utf-8 -*- from visual import * import ImageGrab display(x=0, y=0, width=400, height=400, userzoom=False, center=(0, 0, 1), foreground=(0, 0, 0), background=(1, 1, 1)) ar = arrow(pos=(0, 0, 0), axis=(10, 0, 0), shaftwidth=0.3) total_angle = 0 angle = 0.1 / (2 * math.pi) frame = 0 while total_angle <= 2 * math.pi: rate(100) nx = ar.axis.x * math.cos(angle) - ar.axis.y * math.sin(angle) ny = ar.axis.x * math.sin(angle) + ar.axis.y * math.cos(angle) nz = 0 ar.axis = (nx, ny, nz) file_name = 'img-' + '{fr:03d}'.format(fr=frame) + '.png' frame += 1 im = ImageGrab.grab((0, 0, 400, 400)) im.save(file_name) total_angle += angle print total_angle exit()
#!/usr/bin/env python # -*- coding: utf-8 -*- import numpy as np import matplotlib.patches as mpatches import matplotlib.pyplot as plt def main(): red_patch = mpatches.Patch(color='red', label=r'$f(x)$ - moja funkcja') blue_patch = mpatches.Patch(color='blue', label=r'$f^\prime(x)$ - obliczone numerycznie') cyan_patch = mpatches.Patch(color='cyan', label=r'$f^\prime(x)$ - obliczone recznie') green_patch = mpatches.Patch(color='green', label=r"$\int_{-3}^{1}f(x)$ - obliczone numerycznie") plt.legend(handles=[red_patch, blue_patch, cyan_patch, green_patch], loc='lower right') x = np.linspace(-4, 2, 10) x_dense = np.linspace(-4, 2) y_f = func(x) plt.plot(x, y_f, 'red', linewidth=2) y_deriv = derivative(func, x) plt.plot(x, y_deriv, 'blue', linewidth=2) y_own_deriv = own_deriv_func(x_dense) plt.plot(x_dense, y_own_deriv, 'cyan', linewidth=1) summo1 = definite_integral(func, a=-3, b=1) summo2 = own_integral_func(a=-3, b=1) print 'Całka oznaczona, numerycznie: %f' % summo1 print 'Całka oznaczona, ręcznie: %f' % summo2 current_figure = plt.gcf() current_figure.savefig('rachunek_rozniczkowy.png') plt.show() def func(x): return -2 * (x ** 3) - 4 * (x ** 2) + (8 * x) + 1 def own_deriv_func(x): return -6 * (x ** 2) - 8 * x + 8 def own_integral_func(a, b): F = lambda x: -(1/2.0) * (x ** 4) - (4/3.0) * (x ** 3) + 4 * (x ** 2) + x return F(b) - F(a) def derivative(fun, x): h = 0.2 # dx return (fun(x + h) - fun(x)) / h def definite_integral(fun, a, b): axes = plt.gca() dx = (b - a) / 20.0 summo = 0 x = a while x < b: axes.add_patch(mpatches.Rectangle(xy=(x, 0), width=dx, height=fun(x), facecolor='green')) summo += dx * fun(x) x += dx return summo if __name__ == '__main__': main()Wynik:
Całka oznaczona, numerycznie: -26.080000 Całka oznaczona, ręcznie: -25.333333
# http://scikit-learn.org/stable/ # https://www.continuum.io/ # sudo apt-get install python-scikits-learn from sklearn import tree # Collect training data SMOOTH = 1 BUMPY = 0 APPLE = 0 ORANGE = 1 features = [[140, SMOOTH], [130, SMOOTH], [150, BUMPY], [170, BUMPY]] labels = [APPLE, APPLE, ORANGE, ORANGE] # Train classifier clf = tree.DecisionTreeClassifier() clf = clf.fit(features, labels) # Make predictions result = clf.predict([[160, 0]]) if result[0] == APPLE: print 'APPLE' elif result[0] == ORANGE: print 'ORANGE' else: 'Result unknown'Wynik:
ORANGE
from visual import * import ImageGrab # from PIL starting_height = 4 floor = box (pos=(0, 0, 0), length=4, height=0.5, width=4, color=color.blue) ball = sphere (pos=(0, starting_height, 0), radius=1, color=color.red) ball.velocity = vector(0, -1, 0) dt = 0.01 frame = 0 while 1: rate (100) ball.pos = ball.pos + ball.velocity*dt if ball.y < ball.radius: ball.velocity.y = abs(ball.velocity.y) else: ball.velocity.y = ball.velocity.y - 9.8*dt file_name = 'img-' + '{fr:03d}'.format(fr=frame) + '.png' frame += 1 im = ImageGrab.grab((0, 0, 500, 500)) # screen box from (0,0)-(500,500) im.save(file_name) # save image to disk if ball.pos.y > starting_height: exit()Skrypt wygenerował około 140 zrzutów ekranu, które trzeba było poddać obróbce, nieoceniony okazał się tutaj ImageMagic. Kluczowym problem jest wysterowanie czasu pomiędzy kolejnymi klatkami.
mogrify -crop 413x411+9+30 +repage $(ls -v *.png)Chociaż ImageMagic potrafi generować gif-y, jednak za żadne skarby nie mogłem skonfigurować właściwego opóźnienia, pomiędzy kolejnymi klatkami. Szperając po internecie udało się ten problem ominąć, wymaga to jednak kroków pośrednich, w postaci utworzenia filmiku z animacją, a następnie przepuszczenia go jeszcze raz przez ffmpeg i convert (ImageMagic). Magiczny zestaw komend:
# Filmik ze zdjęć ffmpeg -r 100 -f image2 -i img-%3d.png video.avi # Wyciąganie klatek z filmiku i zlepianie w gif-a ffmpeg -i video.avi -vf scale=320:-1 -r 10 -f image2pipe -vcodec ppm - | convert -delay 10 -loop 0 - gif:- | convert -layers Optimize - output.gif # Alternatywna wersja, też działa ffmpeg -i video.avi -vf scale=320:-1 -r 10 -f image2pipe -vcodec ppm - | convert -delay 10 -loop 0 - output2.gifW animacji jest jakiś uszczerbek jakości, tak jakby brakowało wszystkich potrzebnych klatek. Być może uda mi się kiedyś rozwiązać tą zagadką i odpowiednio dobrać parametry, wtedy też o nich napiszę. Próbowałem także innych rozwiązań, bezskutecznie próbując wysterować opóźnienie pomiędzy kolejnymi klatkami.
# Gif generowany tylko przez convert (ImageMagic) convert -dispose none -delay 1.5 $(ls -v *.png) -coalesce output3.gif # Gif generowany tylko przez ffmpeg ffmpeg -f image2 -framerate 100 -i img-%3d.png output4.gif
from collections import namedtuple User = namedtuple('User', ['name', 'address', 'email']) user = User('Bob', 'blog.example.com', 'bob@example.com') print('Name: ' + user.name) print('Web: ' + user.address) print('Email: ' + user.email)Wynik:
Name: Bob Web: blog.example.com Email: bob@example.comCounter pozwala na zliczanie elementów - zamiast dedykowanej pętli for.
from collections import Counter indexer = Counter(['Bob', 'Alice', 'Bob', 'Bob']) print(indexer['Bob'])Wynik:
3defaultdict jest klasą pozwalają na ustawienie domyślnej wartości, dla nowych elementów. Przydatne, gdy dodajemy coś do wartości i nie chcemy robić testu na to, czy taki element już istnieje.
from collections import defaultdict d = defaultdict(lambda: '-') d['Bob'] += 'one_' d['Alice'] += 'two_' d['Bob'] += 'three_' print(d)Wynik:
defaultdict(<function <lambda> at 0x7fbf33b27578>, {'Bob': '-one_three_', 'Alice': '-two_'})