├── .gitignore
├── README.md
├── branding
└── logo
│ └── hse.svg
├── requirements.txt
├── week01
├── Lecture_01.ipynb
└── Solved_Seminar_01.ipynb
├── week02
├── Clean_Seminar_02.ipynb
├── Lecture_02.ipynb
└── Solved_Seminar_02.ipynb
├── week03
├── Clean_Seminar_03.ipynb
├── Lecture_03.ipynb
├── Seminar_3_solved.ipynb
├── archive_2023.zip
└── flop_file.csv
├── week04
├── Lecture_4.ipynb
├── Seminar_4_clean.ipynb
└── Seminar_4_solved.ipynb
├── week05
├── Dash
│ ├── .DS_Store
│ ├── county_statistics.csv
│ ├── main.py
│ └── pages
│ │ ├── .DS_Store
│ │ ├── compare.py
│ │ ├── home.py
│ │ └── main_dash.py
└── Seminar_5_solved.ipynb
├── week06
├── Lecture_6.ipynb
├── Seminar_6_solved.ipynb
├── __init__.py
├── code
│ ├── __init__.py
│ ├── fibonacci.py
│ └── inventory_management.py
└── tests
│ ├── __init__.py
│ ├── test_fibonacci.py
│ └── test_inventory_management.py
├── week07
├── Lecture_7.ipynb
├── Seminar_7_clean.ipynb
└── chinook.db
├── week08
├── Lecture_8.ipynb
├── Python_2_Seminar_8_solved.ipynb
└── Seminar_8_clean.ipynb
├── week09
├── Python_2_Lecture_9.ipynb
└── Python_2_Seminar_9_clean.ipynb
├── week10
├── Python_2_Lecture_10.ipynb
├── Python_2_Seminar_10.ipynb
└── external-40779.xml
├── week11
├── Aliexpress.txt
├── Aliexpress.zip
├── Aliexpress
│ ├── __init__.py
│ ├── items.json
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ │ ├── AliexpressSpider.py
│ │ └── __init__.py
├── Python_2_Lecture_11.ipynb
└── Python_2_Seminar_11.ipynb
├── week12
├── Python_2_Lecture_12.ipynb
└── Python_2_Seminar_12.ipynb
├── week13
├── Python_2_Lecture_13.ipynb
└── Python_2_Seminar_13.ipynb
└── week14
└── Python_2_Lecture_14.ipynb
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # Other
7 | *.txt
8 | LEcture_01/*.txt
9 | *.DS_Store
10 | *__
11 | *.pytest_cache
12 | *homework
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Продвинутый Python
8 |
9 |
10 |
11 |
12 | ФКН ВШЭ, 2023-2024
13 |
14 |
15 |
16 |
17 |
18 |
19 | | **Название раздела** | **Теоретические занятия** | **Практические занятия** | **Домашнее задание** |
20 | |:--------------------------------------:|:--------------------------:|:--------------------------:|:---------------------------:|
21 | | **Bash, Git, SQL** | [**[Ссылка]**][01/lecture] | [**[Ссылка]**][01/seminar] | [**[Ссылка]**][01/homework] |
22 | | **NumPy, SciPy** | [**[Ссылка]**][02/lecture] | [**[Ссылка]**][02/seminar] | [**[Ссылка]**][02/homework] |
23 | | **Pandas** | [**[Ссылка]**][03/lecture] | [**[Ссылка]**][03/seminar] | [**[Ссылка]**][03/homework] |
24 | | **Matplotlib, Seaborn** | [**[Ссылка]**][04/lecture] | [**[Ссылка]**][04/seminar] | [**[Ссылка]**][04/homework] |
25 | | **Plotly, Dash** | [**[Ссылка]**][05/lecture] | [**[Ссылка]**][05/seminar] | [**[Ссылка]**][05/homework] |
26 | | **Тестирование и контроль исполнения** | [**[Ссылка]**][06/lecture] | [**[Ссылка]**][06/seminar] | - |
27 | | **Базы данных** | [**[Ссылка]**][07/lecture] | - | - |
28 |
29 | ## Установка
30 |
31 | ```console
32 | hse@dev~$ git clone https://github.com/syubogdanov/Deep_Python_2023.git
33 | hse@dev~$ cd Deep_Python_2023/
34 | hse@dev~$ pip install -r requirements.txt
35 | ```
36 |
37 |
38 |
39 | [01/lecture]: week01/Lecture_01.ipynb
40 | [01/seminar]: week01/Solved_Seminar_01.ipynb
41 | [01/homework]: https://youtu.be/dQw4w9WgXcQ?si=-v4q8C2ffRPeBT-R
42 |
43 | [02/lecture]: week02/Lecture_02.ipynb
44 | [02/seminar]: week02/Solved_Seminar_02.ipynb
45 | [02/homework]: https://classroom.github.com/a/53-HXxy3
46 |
47 | [03/lecture]: week03/Lecture_03.ipynb
48 | [03/seminar]: week03/Seminar_3_solved.ipynb
49 | [03/homework]: https://classroom.github.com/a/FB0NP7JZ
50 |
51 | [04/lecture]: week04/Lecture_4.ipynb
52 | [04/seminar]: week04/Seminar_4_solved.ipynb
53 | [04/homework]: https://classroom.github.com/a/LuWn-OeL
54 |
55 | [05/lecture]: week04/Lecture_4.ipynb
56 | [05/seminar]: week05/Seminar_5_solved.ipynb
57 | [05/homework]: https://classroom.github.com/a/pZaJvvhB
58 |
59 | [06/lecture]: week06/Lecture_6.ipynb
60 | [06/seminar]: week06/Seminar_6_solved.ipynb
61 |
62 | [07/lecture]: week07/Lecture_7.ipynb
63 |
--------------------------------------------------------------------------------
/branding/logo/hse.svg:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | dash
2 | jaydebeapi
3 | matplotlib
4 | numpy
5 | pandas
6 | plotly
7 | psycopg2
8 | pyodbc
9 | pypika
10 | pytest
11 | python-dateutil
12 | seaborn
13 | sqlalchemy
14 |
--------------------------------------------------------------------------------
/week03/archive_2023.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week03/archive_2023.zip
--------------------------------------------------------------------------------
/week03/flop_file.csv:
--------------------------------------------------------------------------------
1 | 14 13 265
2 | ахахах ахахахахахах ах
3 | очень плохая строка флопная
4 | 3,1415926 1000 20
5 | yes yes no
6 | 2022-12-01 2022-12-31 2001-01-01
--------------------------------------------------------------------------------
/week05/Dash/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week05/Dash/.DS_Store
--------------------------------------------------------------------------------
/week05/Dash/main.py:
--------------------------------------------------------------------------------
1 | from dash import Dash, html, dcc, Input, Output
2 | import pandas as pd
3 | from urllib.request import urlopen
4 | import json
5 | import plotly.express as px
6 | import dash
7 |
8 | # Подготовка
9 |
10 | external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
11 |
12 | app = Dash(__name__, external_stylesheets=external_stylesheets, use_pages=True)
13 |
14 | app.layout = html.Div(children=[
15 | html.H2(children="US elections"),
16 | html.Div(
17 | [
18 | html.Div(
19 | dcc.Link(
20 | f"{page['name']}", href=page["relative_path"]
21 | )
22 | )
23 | for page in dash.page_registry.values()
24 | ]
25 | ),
26 |
27 | dash.page_container,
28 | ])
29 |
30 | if __name__ == '__main__':
31 | app.run_server(debug=True)
--------------------------------------------------------------------------------
/week05/Dash/pages/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week05/Dash/pages/.DS_Store
--------------------------------------------------------------------------------
/week05/Dash/pages/compare.py:
--------------------------------------------------------------------------------
1 | import dash
2 | from dash import Dash, html, dcc, Input, Output, callback
3 | import pandas as pd
4 | from urllib.request import urlopen
5 | import json
6 | import plotly.express as px
7 |
8 | dash.register_page(__name__)
9 |
10 | df = pd.read_csv("county_statistics.csv")
11 | df = df[~pd.isnull(df.lat)]
12 | df = df[~pd.isnull(df.percentage16_Donald_Trump)]
13 |
14 | df_sample = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/laucnty16.csv')
15 | df_sample['State FIPS Code'] = df_sample['State FIPS Code'].apply(lambda x: str(x).zfill(2))
16 | df_sample['County FIPS Code'] = df_sample['County FIPS Code'].apply(lambda x: str(x).zfill(3))
17 | df_sample['FIPS'] = df_sample['State FIPS Code'] + df_sample['County FIPS Code']
18 |
19 | def replacing_words(x):
20 | x = x.split(",")[0]
21 | samples = [" County", "/town", " Parish", "/city"]
22 | for s in samples:
23 | x = x.replace(s, "")
24 | return x
25 |
26 | def changing_state(x):
27 | x = x.split(", ")[-1]
28 | if x == 'District of Columbia':
29 | x = "DC"
30 | return x
31 |
32 | df_sample["county"] = df_sample["County Name/State Abbreviation"].apply(lambda x: replacing_words(x))
33 | df_sample["state"] = df_sample["County Name/State Abbreviation"].apply(lambda x: changing_state(x))
34 | df_fips = pd.merge(df, df_sample[["county", "state", "FIPS"]], on=["county", "state"], how='left')
35 |
36 | perc_data = pd.melt(df_fips[["county", "state", "White", "Black", "Hispanic", "Asian", "Pacific", "Native"]], id_vars=["county", "state"], var_name="Race", value_name="Perc")
37 |
38 | fig_1 = px.pie(data_frame=perc_data, values="Perc", names="Race")
39 | fig_1.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
40 | fig_2 = px.pie(data_frame=perc_data, values="Perc", names="Race")
41 | fig_2.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
42 |
43 |
44 | layout = html.Div(children=[
45 | html.Div([
46 | html.Div([
47 | dcc.Markdown('Select State'),
48 | dcc.Dropdown(df_fips.state.unique(), df_fips.state[0], id='state_1_dropdown'),
49 | dcc.Markdown('Select County'),
50 | dcc.Dropdown(df_fips.county.unique(), df_fips.county[0], id='county_1_dropdown'),
51 | ]),
52 | html.Div([
53 | dcc.Markdown('Select State'),
54 | dcc.Dropdown(df_fips.state.unique(), df_fips.state[0], id='state_2_dropdown'),
55 | dcc.Markdown('Select County'),
56 | dcc.Dropdown(df_fips.county.unique(), df_fips.county[0], id='county_2_dropdown'),
57 | ]),
58 | ], style={'columnCount': 2, 'align': 'center'}),
59 |
60 | html.Div([
61 | html.Div([
62 | dcc.Markdown('Race distribution'),
63 | dcc.Graph(
64 | id='Race_1_pie',
65 | figure=fig_1,
66 | ),
67 | ]),
68 | html.Div([
69 | dcc.Markdown('Race distribution'),
70 | dcc.Graph(
71 | id='Race_2_pie',
72 | figure=fig_2,
73 | ),
74 | ])
75 | ], style={'columnCount': 2, 'align': 'center'})
76 | ])
77 |
78 | @callback(
79 | Output("county_1_dropdown", 'options'),
80 | Input("state_1_dropdown", "value")
81 | )
82 | def update_counties_1(val):
83 | res = df_fips[df_fips.state == val].county.unique()
84 | return res
85 |
86 | @callback(
87 | Output("county_2_dropdown", 'options'),
88 | Input("state_2_dropdown", "value")
89 | )
90 | def update_counties_2(val):
91 | res = df_fips[df_fips.state == val].county.unique()
92 | return res
93 |
94 | @callback(
95 | Output("Race_1_pie", 'figure'),
96 | Input("county_1_dropdown", "value"),
97 | Input("state_1_dropdown", "value")
98 | )
99 | def update_pue_1(county, state):
100 | fig_1 = px.pie(data_frame=perc_data[(perc_data.county == county) & (perc_data.state == state)], values="Perc", names="Race")
101 | fig_1.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
102 | return fig_1
103 |
104 | @callback(
105 | Output("Race_2_pie", 'figure'),
106 | Input("county_2_dropdown", "value"),
107 | Input("state_2_dropdown", "value")
108 | )
109 | def update_pue_2(county, state):
110 | fig_2 = px.pie(data_frame=perc_data[(perc_data.county == county) & (perc_data.state == state)], values="Perc", names="Race")
111 | fig_2.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
112 | return fig_2
--------------------------------------------------------------------------------
/week05/Dash/pages/home.py:
--------------------------------------------------------------------------------
1 | import dash
2 | from dash import html, dcc
3 |
4 | dash.register_page(__name__, path='/')
5 |
6 | layout = html.Div(children=[
7 | ])
--------------------------------------------------------------------------------
/week05/Dash/pages/main_dash.py:
--------------------------------------------------------------------------------
1 | from dash import Dash, html, dcc, Input, Output, callback
2 | import pandas as pd
3 | from urllib.request import urlopen
4 | import json
5 | import plotly.express as px
6 | import dash
7 |
8 | dash.register_page(__name__)
9 |
10 | df = pd.read_csv("county_statistics.csv")
11 | df = df[~pd.isnull(df.lat)]
12 | df = df[~pd.isnull(df.percentage16_Donald_Trump)]
13 |
14 | df_sample = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/laucnty16.csv')
15 | df_sample['State FIPS Code'] = df_sample['State FIPS Code'].apply(lambda x: str(x).zfill(2))
16 | df_sample['County FIPS Code'] = df_sample['County FIPS Code'].apply(lambda x: str(x).zfill(3))
17 | df_sample['FIPS'] = df_sample['State FIPS Code'] + df_sample['County FIPS Code']
18 |
19 | def replacing_words(x):
20 | x = x.split(",")[0]
21 | samples = [" County", "/town", " Parish", "/city"]
22 | for s in samples:
23 | x = x.replace(s, "")
24 | return x
25 |
26 | def changing_state(x):
27 | x = x.split(", ")[-1]
28 | if x == 'District of Columbia':
29 | x = "DC"
30 | return x
31 |
32 | df_sample["county"] = df_sample["County Name/State Abbreviation"].apply(lambda x: replacing_words(x))
33 | df_sample["state"] = df_sample["County Name/State Abbreviation"].apply(lambda x: changing_state(x))
34 | df_fips = pd.merge(df, df_sample[["county", "state", "FIPS"]], on=["county", "state"], how='left')
35 |
36 | with urlopen('https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json') as response:
37 | counties = json.load(response)
38 |
39 | # Отрисовка изначальных карт
40 |
41 | fig = px.choropleth(df_fips, geojson=counties, locations='FIPS', color='White',
42 | color_continuous_scale="Viridis",
43 | range_color=(0, 100),
44 | scope="usa",
45 | fitbounds='locations',
46 | hover_data=["state", "county", "percentage16_Donald_Trump"]
47 | )
48 | fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
49 |
50 | perc_data = pd.melt(df_fips[["county", "state", "White", "Black", "Hispanic", "Asian", "Pacific", "Native"]], id_vars=["county", "state"], var_name="Race", value_name="Perc")
51 | walk_data = pd.melt(df_fips[["county", "state", 'Drive', 'Carpool', 'Transit', 'Walk', 'OtherTransp', 'WorkAtHome']], id_vars=["county", "state"], var_name="Walk", value_name="Perc")
52 | work_data = pd.melt(df_fips[["county", "state", 'PrivateWork', 'PublicWork', 'SelfEmployed', 'FamilyWork', 'Unemployment']], id_vars=["county", "state"], var_name="Work", value_name="Perc")
53 |
54 | fig_1 = px.pie(data_frame=perc_data, values="Perc", names="Race")
55 | fig_1.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
56 | fig_2 = px.pie(data_frame=walk_data, values="Perc", names="Walk")
57 | fig_2.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
58 | fig_3 = px.pie(data_frame=work_data, values="Perc", names="Work")
59 | fig_3.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
60 |
61 | layout = html.Div(children=[
62 | html.Div([
63 | dcc.Markdown('Select State'),
64 | dcc.Dropdown(df_fips.state.unique(), df_fips.state[0], id='state_dropdown'),
65 | ]),
66 |
67 | dcc.Graph(
68 | id='US_map',
69 | figure=fig,
70 | clickData={'points': [{'customdata': ['SC', 'Horry']}]}
71 | ),
72 |
73 | html.Div([
74 | html.Div([
75 | dcc.Markdown('Race distribution'),
76 | dcc.Graph(
77 | id='Race_pie',
78 | figure=fig_1,
79 | ),
80 | ]),
81 | html.Div([
82 | dcc.Markdown('Transport distribution'),
83 | dcc.Graph(
84 | id='Walk_pie',
85 | figure=fig_2,
86 | ),
87 | ]),
88 | html.Div([
89 | dcc.Markdown('Work distribution'),
90 | dcc.Graph(
91 | id='Work_pie',
92 | figure=fig_3,
93 | ),
94 | ])
95 | ], style={'columnCount': 3, 'align': 'center'})
96 | ])
97 |
98 |
99 | @callback(
100 | Output('US_map', 'figure'),
101 | Input('state_dropdown', 'value')
102 | )
103 | def update_map(state):
104 | fig = px.choropleth(df_fips[df_fips.state == state], geojson=counties, locations='FIPS', color='percentage16_Donald_Trump',
105 | color_continuous_scale="Viridis",
106 | range_color=(0, 1),
107 | scope="usa",
108 | fitbounds='locations',
109 | hover_data=["state", "county", "percentage16_Donald_Trump"]
110 | )
111 | fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
112 | return fig
113 |
114 |
115 | @callback(
116 | Output('Race_pie', 'figure'),
117 | Input('US_map', 'clickData')
118 | )
119 | def display_click_data_race(clickData):
120 | state = clickData["points"][0]["customdata"][0]
121 | county = clickData["points"][0]["customdata"][1]
122 | fig_1 = px.pie(data_frame=perc_data[(perc_data.county == county) & (perc_data.state == state)], values="Perc", names="Race")
123 | fig_1.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
124 | return fig_1
125 |
126 | @callback(
127 | Output('Walk_pie', 'figure'),
128 | Input('US_map', 'clickData')
129 | )
130 | def display_click_data_walk(clickData):
131 | state = clickData["points"][0]["customdata"][0]
132 | county = clickData["points"][0]["customdata"][1]
133 | fig_2 = px.pie(data_frame=walk_data[(walk_data.county == county) & (walk_data.state == state)], values="Perc", names="Walk")
134 | fig_2.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
135 | return fig_2
136 |
137 | @callback(
138 | Output('Work_pie', 'figure'),
139 | Input('US_map', 'clickData')
140 | )
141 | def display_click_data_work(clickData):
142 | state = clickData["points"][0]["customdata"][0]
143 | county = clickData["points"][0]["customdata"][1]
144 | fig_3 = px.pie(data_frame=work_data[(work_data.county == county) & (work_data.state == state)], values="Perc", names="Work")
145 | fig_3.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
146 | return fig_3
--------------------------------------------------------------------------------
/week06/Seminar_6_solved.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "id": "sYZcJVMTSORK"
7 | },
8 | "source": [
9 | "# Продвинутый Python, лекция 2\n",
10 | "\n",
11 | "**Лектор:** Петров Тимур\n",
12 | "\n",
13 | "**Семинаристы:** Петров Тимур, Бузаев Федор, Коган Александра, Дешеулин Олег\n",
14 | "\n",
15 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)"
16 | ]
17 | },
18 | {
19 | "cell_type": "markdown",
20 | "metadata": {
21 | "id": "0FhRbnksSOFM"
22 | },
23 | "source": [
24 | "Итак, сегодня поговорим про такую важную составляющую для любого Python-разработчика: тестирование.\n",
25 | "\n",
26 | "Let's go"
27 | ]
28 | },
29 | {
30 | "cell_type": "markdown",
31 | "metadata": {
32 | "id": "yl-jX7V9QRxR"
33 | },
34 | "source": [
35 | "# Assert"
36 | ]
37 | },
38 | {
39 | "cell_type": "markdown",
40 | "metadata": {
41 | "id": "KVD31FBURKEf"
42 | },
43 | "source": [
44 | "В Python более есть универсальный и гибкий метод тестирования, основанный на встроенной в Python инструкции `assert` (англ. «утверждение»).\n",
45 | "\n",
46 | "Метод тестирования с использованием `assert` в Python позволяет быстро проверить корректность работы небольших фрагментов кода, убедившись, что определенные условия выполняются. Этот метод особенно полезен при разработке и отладке программ, так как позволяет быстро обнаруживать ошибки и неправильное поведение."
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "metadata": {
52 | "id": "9e6KmpeiSFPq"
53 | },
54 | "source": [
55 | "`assert` в Python работает по следующей логике: разработчик передает в него некоторое утверждение, и если это утверждение истинно, assert не возвращает ничего, и тест считается пройденным. Однако, если утверждение оказывается ложным, то `assert` возбуждает исключение типа AssertionError с сообщением об ошибке, а исполнение кода прерывается.\n",
56 | "\n",
57 | "Иными словами, `assert` - это механизм, с помощью которого разработчик может выразить свои ожидания от кода и проверить их. Если ожидания не выполняются, это сигнализирует о проблеме в коде и позволяет быстро выявить место, в котором возникла проблема, и ее характер. Сообщение об ошибке, которое можно добавить к инструкции assert, помогает разработчику легче понять, что пошло не так, что особенно полезно в более сложных кодовых базах."
58 | ]
59 | },
60 | {
61 | "cell_type": "code",
62 | "execution_count": 1,
63 | "metadata": {
64 | "id": "LMEGHjNLbXQU"
65 | },
66 | "outputs": [],
67 | "source": [
68 | "class_name = 'MergeSort'"
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": 2,
74 | "metadata": {
75 | "colab": {
76 | "base_uri": "https://localhost:8080/",
77 | "height": 177
78 | },
79 | "id": "BNo9sT9rOQP8",
80 | "outputId": "7496200c-95cb-46b4-eb24-393e4f29ba3a"
81 | },
82 | "outputs": [
83 | {
84 | "ename": "AssertionError",
85 | "evalue": "ignored",
86 | "output_type": "error",
87 | "traceback": [
88 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
89 | "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
90 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0mclass_name\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'DifferentialEvolution'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"Не пон, а где первая домашка?\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
91 | "\u001b[0;31mAssertionError\u001b[0m: Не пон, а где первая домашка?"
92 | ]
93 | }
94 | ],
95 | "source": [
96 | "assert class_name == 'DifferentialEvolution', \"Не пон, а где первая домашка?\""
97 | ]
98 | },
99 | {
100 | "cell_type": "markdown",
101 | "metadata": {
102 | "id": "614VDAdWbl23"
103 | },
104 | "source": [
105 | "Как работает `assert` внутри Python?\n",
106 | "\n",
107 | "При обработке инструкций `assert` Python преобразует каждую из них примерно в такую конструкцию:\n",
108 | "\n",
109 | "\n",
110 | " if __debug__:\n",
111 | " if not :\n",
112 | " raise AssertionError()\n",
113 | "\n",
114 | "assert работает, если внутренняя константа [__debug__](https://docs.python.org/3/library/constants.html?highlight=__debug__) `is True`."
115 | ]
116 | },
117 | {
118 | "cell_type": "markdown",
119 | "metadata": {
120 | "id": "C7vsZkSTdF69"
121 | },
122 | "source": [
123 | "Тестировать код с помощью `assert` - это как детская игра, весело и забавно, но, допустим, если ваш проект — это не песочница во дворе, а серьезное мероприятие, то тут вы не хотите веселиться с песочком. Писать тесты с нуля, затем отлаживать их, создавать документацию и пытаться объяснить новичкам, что вы там вообще написали — это как попытка пройти через лабиринт из бананов в зоопарке. Да, смешно, но и не очень дёшево в плане времени и усилий."
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "metadata": {},
129 | "source": [
130 | "Как сделать __debug__ == False \n",
131 | "\n",
132 | "https://stackoverflow.com/questions/28608385/assert-asserting-when-debug-false"
133 | ]
134 | },
135 | {
136 | "cell_type": "markdown",
137 | "metadata": {
138 | "id": "wDdFCB6qeKoU"
139 | },
140 | "source": [
141 | "# pytest\n"
142 | ]
143 | },
144 | {
145 | "cell_type": "markdown",
146 | "metadata": {
147 | "id": "yICbpVv28HLt"
148 | },
149 | "source": [
150 | "В продакшене намного удобнее работать с библиотеками, которые были написаны именно для тестирования:\n",
151 | "\n",
152 | "1. **pytest**\n",
153 | "2. **unittest**\n",
154 | "3. **nose2**\n",
155 | "\n",
156 | "В нашем семинаре мы будем рассматривать библиотеку **pytest**.\n",
157 | "\n",
158 | "В чем плюсы **pytest**?\n",
159 | "\n",
160 | "1. **Простота использования**: **pytest** обеспечивает простой и интуитивно понятный синтаксис для написания тестов. Вы можете создавать тесты, используя стандартные функции Python, что делает код более читаемым.\n",
161 | "\n",
162 | "2. **Автоматическое обнаружение тестов**: **pytest** автоматически обнаруживает и запускает тесты, не требуя сложной конфигурации. Вам нужно только создать файлы с префиксом \"test_\" и определить функции, начинающиеся с \"test_\", и **pytest** сделает всю работу за вас.\n",
163 | "\n",
164 | "3. **Мощные ассерты**: **pytest** предоставляет богатый набор ассертов для проверки ожидаемых результатов. Вы можете использовать стандартные ассерты, а также более продвинутые, такие как `assertAlmostEqual`, `assertRaises`.\n",
165 | "\n",
166 | "4. **Модульность и параметризация**: Вы можете легко организовывать ваши тесты в модули и параметризовать их для тестирования разных вариантов входных данных.\n",
167 | "\n",
168 | "5. **Поддержка мокирования и фикстур**: **pytest** предоставляет инструменты для создания фикстур - предварительных настроек и ресурсов, которые могут быть общими для нескольких тестов. Это упрощает и ускоряет написание тестов.\n",
169 | "\n",
170 | "6. **Отчеты о выполнении тестов**: **pytest** предоставляет детальные отчеты о выполнении тестов, включая информацию о том, какие тесты прошли, а какие нет, а также о покрытии кода (coverage)."
171 | ]
172 | },
173 | {
174 | "cell_type": "markdown",
175 | "metadata": {
176 | "id": "iDokxeMp-mKi"
177 | },
178 | "source": [
179 | "## Структура проекта с тестами"
180 | ]
181 | },
182 | {
183 | "cell_type": "markdown",
184 | "metadata": {
185 | "id": "jnWCV24b-tHY"
186 | },
187 | "source": [
188 | "Директория с тестами в проекте, использующем pytest, обычно имеет определенную структуру и именование файлов, чтобы pytest мог автоматически обнаруживать и запускать ваши тесты. Вот общая структура директории с тестами:\n",
189 | "\n",
190 | " de_project/\n",
191 | " ├── de_module.py\n",
192 | " ├── test_de_module.py\n",
193 | " ├── mutation_strategies_modules.py\n",
194 | " ├── test_mutation_strategies_modules.py\n",
195 | " └── conftest.py\n",
196 | "\n",
197 | "Где:\n",
198 | "1. `de_project` - это корневая папка\n",
199 | "\n",
200 | "2. `de_module.py` - это файл с кодом, который вы хотите протестировать. Это может быть один или несколько модулей вашего проекта.\n",
201 | "\n",
202 | "3. `test_my_module.py` - это файл с тестами для de_module.py. Файл с тестами обычно имеет префикс \"test_\" и содержит функции для тестирования соответствующего модуля.\n",
203 | "\n",
204 | "4. `mutation_strategies_modules.py`: Другие модули вашего проекта.\n",
205 | "\n",
206 | "5. `test_mutation_strategies_modules.py`: Файлы с тестами для других модулей в проекте. Каждый модуль, который нужно протестировать, обычно имеет соответствующий файл с тестами.\n",
207 | "\n",
208 | "6. conftest.py: Этот файл используется для определения общих фикстур (fixtures), которые могут быть использованы в нескольких файлах с тестами.\n",
209 | "\n",
210 | "Но лучше разместить тесты по разным директориям, так как:\n",
211 | "\n",
212 | "1. модуль с тестом может быть запущен автономно из командной строки;\n",
213 | "2. код тестов легко отделить от программы;\n",
214 | "3. тестируемый код легче перерабатывать.\n",
215 | "\n",
216 | "\n",
217 | " ├── code\n",
218 | " │ ├── __init__.py \n",
219 | " │ ├── de_module.py # Тестируемые функции живут тут\n",
220 | " │ └── mutation_strategies_modules.py \n",
221 | " └── tests\n",
222 | " ├── __init__.py \n",
223 | " ├── test_de_module.py # А тесты лежат здесь\n",
224 | " ├── test_mutation_strategies_modules.py\n",
225 | " └── conftest.py\n",
226 | "\n"
227 | ]
228 | },
229 | {
230 | "cell_type": "markdown",
231 | "metadata": {
232 | "id": "TRC6AP6oEBPb"
233 | },
234 | "source": [
235 | "## Как написать и запустить тест для программы?"
236 | ]
237 | },
238 | {
239 | "cell_type": "markdown",
240 | "metadata": {
241 | "id": "KVJQ3wf8ELkJ"
242 | },
243 | "source": [
244 | "Давайте разберем классический пример с вычислением факториала числа."
245 | ]
246 | },
247 | {
248 | "cell_type": "code",
249 | "execution_count": 1,
250 | "metadata": {
251 | "id": "w0hOls0CbfoJ"
252 | },
253 | "outputs": [],
254 | "source": [
255 | "def calculate_factorial(n):\n",
256 | " if n < 0:\n",
257 | " raise ValueError(\"Факториал определен только для неотрицательных целых чисел\")\n",
258 | " if n == 0:\n",
259 | " return 1\n",
260 | " result = 1\n",
261 | " for i in range(1, n + 1):\n",
262 | " result *= i\n",
263 | " return result"
264 | ]
265 | },
266 | {
267 | "cell_type": "code",
268 | "execution_count": 2,
269 | "metadata": {
270 | "colab": {
271 | "base_uri": "https://localhost:8080/"
272 | },
273 | "id": "FmYLaGdQEeUZ",
274 | "outputId": "2753f2b2-915b-412f-859b-aa1bd2dde970"
275 | },
276 | "outputs": [
277 | {
278 | "data": {
279 | "text/plain": [
280 | "120"
281 | ]
282 | },
283 | "execution_count": 2,
284 | "metadata": {},
285 | "output_type": "execute_result"
286 | }
287 | ],
288 | "source": [
289 | "calculate_factorial(5)"
290 | ]
291 | },
292 | {
293 | "cell_type": "markdown",
294 | "metadata": {
295 | "id": "qO6dDvCvEio9"
296 | },
297 | "source": [
298 | "Теперь напишем тест для программы:"
299 | ]
300 | },
301 | {
302 | "cell_type": "code",
303 | "execution_count": 3,
304 | "metadata": {
305 | "id": "-cMxHjm9EiUq"
306 | },
307 | "outputs": [],
308 | "source": [
309 | "def test_factorial_positive():\n",
310 | " assert calculate_factorial(0) == 1\n",
311 | " assert calculate_factorial(1) == 1\n",
312 | " assert calculate_factorial(5) == 120\n",
313 | " assert calculate_factorial(10) == 3628800\n",
314 | "\n",
315 | "def test_factorial_negative():\n",
316 | " try:\n",
317 | " calculate_factorial(-1)\n",
318 | " except ValueError as e:\n",
319 | " assert str(e) == \"Факториал определен только для неотрицательных целых чисел\""
320 | ]
321 | },
322 | {
323 | "cell_type": "markdown",
324 | "metadata": {
325 | "id": "aJj6y5LkEuUD"
326 | },
327 | "source": [
328 | "Здесь мы определили две функции тестирования:\n",
329 | "\n",
330 | "1. **test_factorial_positive**: Этот тест проверяет, что факториалы для положительных чисел рассчитываются правильно.\n",
331 | "\n",
332 | "2. **test_factorial_negative**: Этот тест проверяет, что при попытке рассчитать факториал отрицательного числа генерируется исключение `ValueError` с соответствующим сообщением."
333 | ]
334 | },
335 | {
336 | "cell_type": "markdown",
337 | "metadata": {
338 | "id": "WQ2f3SzcE6XP"
339 | },
340 | "source": [
341 | "Для того, чтобы запустить все тесты, надо ввести в нужной директории команду:\n",
342 | "\n",
343 | " pytest\n",
344 | "\n",
345 | "Для того, чтобы запустить именно конкретный тест:\n",
346 | "\n",
347 | " pytest name_of_test.py\n",
348 | "\n",
349 | "Для того, чтобы вывести весь внутренний лог в консоль:\n",
350 | "\n",
351 | " pytest -s\n",
352 | "\n",
353 | "Для того, чтобы вывести всю доп информацию:\n",
354 | "\n",
355 | " pytest -v\n",
356 | "\n",
357 | "Для того, чтобы запустить тесты в режиме повторного выполнения после изменений в коде (режим \"watch\"):\n",
358 | "\n",
359 | " pytest --watch\n",
360 | "\n",
361 | "И для того, чтобы вывести информацию о покрытие кода:\n",
362 | "\n",
363 | " pytest --cov=path_to_dir"
364 | ]
365 | },
366 | {
367 | "cell_type": "markdown",
368 | "metadata": {
369 | "id": "cAT_6xU4F9IK"
370 | },
371 | "source": [
372 | "Давайте потренируемся и напишем сами тесты для вычисления функции Фибоначчи"
373 | ]
374 | },
375 | {
376 | "cell_type": "markdown",
377 | "metadata": {
378 | "id": "it8_gQJGJxoW"
379 | },
380 | "source": [
381 | "## Основные функции pytest"
382 | ]
383 | },
384 | {
385 | "cell_type": "markdown",
386 | "metadata": {
387 | "id": "PSV8zdXKLeXg"
388 | },
389 | "source": [
390 | "**pytest** предоставляет множество встроенных методов для управления тестами, создания фикстур, организации данных и дополнительной настройки тестовых сценариев. Вот некоторые из основных методов **pytest**:\n",
391 | "\n",
392 | "1. `pytest.mark` - этот модуль позволяет применять метки (маркеры) к тестам для их классификации. Например, @pytest.mark.smoke может использоваться для пометки быстрых тестов.\n",
393 | "\n",
394 | "2. `pytest.fixture `- это декоратор, позволяющий создавать и конфигурировать фикстуры. Фикстуры предоставляют предварительные настройки и ресурсы, которые могут использоваться в тестах.\n",
395 | "\n",
396 | "3. `pytest.param `- это метод позволяет параметризовать тесты с разными входными данными. Он позволяет генерировать множество вариантов тестовых сценариев.\n",
397 | "\n",
398 | "4. `pytest.approx` - эозволяет проверять числа с плавающей точкой на равенство с учетом погрешности.\n",
399 | "\n",
400 | "5. `pytest.raises` - этот метод используется для проверки, что функция вызывает ожидаемое исключение.\n",
401 | "\n",
402 | "6. `pytest.mock` - этот метод позволяет создавать моки (заглушки) для функций или объектов, что полезно при тестировании кода, зависящего от внешних ресурсов.\n",
403 | "\n",
404 | "7. `pytest.mark.parametrize` - этот метод позволяет параметризовать тесты, предоставив наборы входных данных и ожидаемых результатов.\n",
405 | "\n",
406 | "8. `pytest.skip` - этот метод позволяет пропустить выполнение теста в зависимости от определенных условий.\n",
407 | "\n",
408 | "9. `pytest.xfail` - этот метод помечает тест как ожидаемо падающий, но не завершает тест с ошибкой, если он действительно падает.\n"
409 | ]
410 | },
411 | {
412 | "cell_type": "markdown",
413 | "metadata": {
414 | "id": "mevRay45NreY"
415 | },
416 | "source": [
417 | "Давайте разберем `pytest.approx`:"
418 | ]
419 | },
420 | {
421 | "cell_type": "code",
422 | "execution_count": 9,
423 | "metadata": {
424 | "colab": {
425 | "base_uri": "https://localhost:8080/"
426 | },
427 | "id": "__tArLnJKNn_",
428 | "outputId": "4fb05f18-cac5-486a-8786-88ab9ad271de"
429 | },
430 | "outputs": [
431 | {
432 | "data": {
433 | "text/plain": [
434 | "True"
435 | ]
436 | },
437 | "execution_count": 9,
438 | "metadata": {},
439 | "output_type": "execute_result"
440 | }
441 | ],
442 | "source": [
443 | "import pytest\n",
444 | "\n",
445 | "0.4 + 0.3 == pytest.approx(0.7)"
446 | ]
447 | },
448 | {
449 | "cell_type": "code",
450 | "execution_count": 10,
451 | "metadata": {
452 | "colab": {
453 | "base_uri": "https://localhost:8080/"
454 | },
455 | "id": "OHn-IMxWN7si",
456 | "outputId": "410ab322-fcfa-41aa-fd4d-2dbeb7092f75"
457 | },
458 | "outputs": [
459 | {
460 | "data": {
461 | "text/plain": [
462 | "True"
463 | ]
464 | },
465 | "execution_count": 10,
466 | "metadata": {},
467 | "output_type": "execute_result"
468 | }
469 | ],
470 | "source": [
471 | "(0.1 + 0.2, 0.2 + 0.4) == pytest.approx((0.3, 0.6))"
472 | ]
473 | },
474 | {
475 | "cell_type": "markdown",
476 | "metadata": {
477 | "id": "HqLKjW_JPCn8"
478 | },
479 | "source": [
480 | "Также в pytest можно сравнивать `numpy arrays`"
481 | ]
482 | },
483 | {
484 | "cell_type": "code",
485 | "execution_count": 11,
486 | "metadata": {
487 | "colab": {
488 | "base_uri": "https://localhost:8080/"
489 | },
490 | "id": "6YZyHO66O9iD",
491 | "outputId": "8a24ee0e-91a0-408c-e7d3-6c430c4b2379"
492 | },
493 | "outputs": [
494 | {
495 | "data": {
496 | "text/plain": [
497 | "True"
498 | ]
499 | },
500 | "execution_count": 11,
501 | "metadata": {},
502 | "output_type": "execute_result"
503 | }
504 | ],
505 | "source": [
506 | "import numpy as np\n",
507 | "\n",
508 | "np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == pytest.approx(np.array([0.3, 0.6]))"
509 | ]
510 | },
511 | {
512 | "cell_type": "markdown",
513 | "metadata": {
514 | "id": "74eP_FNzTSlL"
515 | },
516 | "source": [
517 | "Давайте напишем тест с использованием `pytest.mark.timeout()`"
518 | ]
519 | },
520 | {
521 | "cell_type": "markdown",
522 | "metadata": {
523 | "id": "uUfmgAlEQZO_"
524 | },
525 | "source": [
526 | "## Fixture"
527 | ]
528 | },
529 | {
530 | "cell_type": "markdown",
531 | "metadata": {
532 | "id": "_ioOcI34QauO"
533 | },
534 | "source": [
535 | "Fixture (фикстура) в Pytest - это функция, которая предоставляет предварительные настройки и ресурсы для выполнения тестовых сценариев. Фикстуры позволяют вам инициализировать данные, подготавливать ресурсы и выполнять другие действия, необходимые для проведения тестов. Фикстуры могут использоваться для изоляции тестов, обеспечения конкретного состояния перед выполнением теста и многих других целей.\n",
536 | "\n",
537 | "Вот основные аспекты фикстур в Pytest:\n",
538 | "\n",
539 | "1. Создание фикстуры:\n",
540 | "Для создания фикстуры в Pytest, вы определяете функцию, которая использует декоратор @pytest.fixture. Эта функция может выполнять предварительные настройки, создавать объекты и, при необходимости, освобождать ресурсы после выполнения теста.\n",
541 | "\n",
542 | "2. Использование фикстуры:\n",
543 | "Для использования фикстуры в тестах, вы просто передаете имя фикстуры в качестве аргумента в тестовую функцию. Pytest автоматически обнаруживает и выполняет фикстуры, когда они указаны как аргументы в тестовых функциях.\n",
544 | "\n",
545 | "3. Область видимости:\n",
546 | "Фикстуры могут иметь разные области видимости, такие как функциональная (всего один вызов фикстуры на тест), модульная (одна фикстура на модуль) и глобальная (одна фикстура на всю сессию тестирования). Вы можете настраивать область видимости фикстур в зависимости от ваших потребностей.\n",
547 | "\n",
548 | "4. Параметризация фикстур:\n",
549 | "Вы можете параметризовать фикстуры, предоставляя разные значения в зависимости от тестовых сценариев. Это позволяет одной фикстуре обеспечивать разные данные для разных тестов.\n",
550 | "\n",
551 | "5. Очистка после теста:\n",
552 | "Фикстуры могут выполнять финализацию и очистку после выполнения теста, например, закрывать файлы, удалять временные директории и т. д.\n",
553 | "\n",
554 | "6. Функции-тесты и фикстуры:\n",
555 | "Функции-тесты могут использовать одну или несколько фикстур, что позволяет им получать доступ к предварительно настроенным данным и ресурсам для выполнения проверок.\n"
556 | ]
557 | },
558 | {
559 | "cell_type": "code",
560 | "execution_count": 12,
561 | "metadata": {
562 | "id": "5soE2iwsPCMU"
563 | },
564 | "outputs": [],
565 | "source": [
566 | "import pytest\n",
567 | "import tempfile\n",
568 | "import os\n",
569 | "\n",
570 | "@pytest.fixture\n",
571 | "def temp_dir():\n",
572 | " # Создаем временный каталог\n",
573 | " temp_directory = tempfile.mkdtemp()\n",
574 | " yield temp_directory # Передаем имя каталога в тест\n",
575 | " # После завершения теста удаляем временный каталог\n",
576 | " os.rmdir(temp_directory)\n",
577 | "\n",
578 | "def test_temp_dir_contains_file(temp_dir):\n",
579 | " # Создаем файл во временном каталоге и выполняем тест\n",
580 | " with open(os.path.join(temp_dir, \"test_file.txt\"), \"w\") as file:\n",
581 | " file.write(\"Тестовый файл\")\n",
582 | " assert os.path.isfile(os.path.join(temp_dir, \"test_file.txt\"))"
583 | ]
584 | },
585 | {
586 | "cell_type": "markdown",
587 | "metadata": {
588 | "id": "KnsEIe8pQpDM"
589 | },
590 | "source": [
591 | "Давайте напишем свою фикстуру для Фибоначчи"
592 | ]
593 | },
594 | {
595 | "cell_type": "markdown",
596 | "metadata": {
597 | "id": "HcPa4CHLWS92"
598 | },
599 | "source": [
600 | "## Краткое напоминание про TDD"
601 | ]
602 | },
603 | {
604 | "cell_type": "markdown",
605 | "metadata": {
606 | "id": "MFz94oi9Wps2"
607 | },
608 | "source": [
609 | "Test-Driven Development (TDD) - это методология разработки программного обеспечения, которая подразумевает создание тестов перед написанием собственного кода. Процесс TDD описывается тремя основными шагами: \"***Red-Green-Refactor***\".\n",
610 | "\n",
611 | "\n",
612 | "1. **Red**: На этом этапе начинают с создания теста, который проверяет новую функциональность или модификацию существующей. Тест пишется так, как если бы функциональность уже существовала, но по факту она ещё не реализована. В результате этого этапа тест будет \"провален\" (красный), так как ожидаемое поведение ещё не реализовано в коде.\n",
613 | "\n",
614 | "2. **Green**: На этом этапе разработчик создаёт минимальный необходимый код, чтобы сделать тест \"проходящим\" (зеленый). Цель - сделать так, чтобы тест успешно выполнялся, подтверждая, что функциональность теперь работает правильно.\n",
615 | "\n",
616 | "3. **Refactor**: После того как тест становится \"зеленым,\" можно приступить к улучшению кода. Рефакторинг включает в себя оптимизацию, улучшение читаемости кода, устранение дублирования и т.д. Важно при этом сохранить зеленый статус теста, чтобы убедиться, что изменения не нарушили работу функциональности."
617 | ]
618 | }
619 | ],
620 | "metadata": {
621 | "colab": {
622 | "provenance": []
623 | },
624 | "kernelspec": {
625 | "display_name": "Python 3",
626 | "name": "python3"
627 | },
628 | "language_info": {
629 | "codemirror_mode": {
630 | "name": "ipython",
631 | "version": 3
632 | },
633 | "file_extension": ".py",
634 | "mimetype": "text/x-python",
635 | "name": "python",
636 | "nbconvert_exporter": "python",
637 | "pygments_lexer": "ipython3",
638 | "version": "3.9.18"
639 | }
640 | },
641 | "nbformat": 4,
642 | "nbformat_minor": 0
643 | }
644 |
--------------------------------------------------------------------------------
/week06/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week06/__init__.py
--------------------------------------------------------------------------------
/week06/code/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week06/code/__init__.py
--------------------------------------------------------------------------------
/week06/code/fibonacci.py:
--------------------------------------------------------------------------------
1 | def fibonacci_bottom_up(n, ans=None):
2 | if n in (0, 1):
3 | return 1
4 | ans = [0] * (n + 1)
5 | ans[0] = 1
6 | ans[1] = 1
7 | for i in range(2, len(ans)):
8 | ans[i] = ans[i - 1] + ans[i - 2]
9 | return ans[n]
10 |
11 |
12 | def fibonacci_classic(n):
13 | if n in (0, 1):
14 | return 1
15 | return fibonacci_classic(n - 1) + fibonacci_classic(n - 2)
16 |
--------------------------------------------------------------------------------
/week06/code/inventory_management.py:
--------------------------------------------------------------------------------
1 | """
2 | Шаг 2 - Green.
3 | Давайте напишем класс c добавлением продукта в инвентарь
4 | """
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/week06/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week06/tests/__init__.py
--------------------------------------------------------------------------------
/week06/tests/test_fibonacci.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import time
3 |
4 | from week06.code.fibonacci import fibonacci_bottom_up, fibonacci_classic
5 |
6 |
7 | def test_to_check_correctess_fibonacci_classic():
8 | """
9 | Напишите тут свои тесты, которые проверяют правильность Фибоначчи
10 | """
11 | pass
12 |
13 | @pytest.mark.xfail()
14 | def test_to_check_correctess_fibonacci_bottom_up():
15 | """
16 | Напишите тут свои тесты, которые проверяют правильность Фибоначчи
17 | """
18 | pass
19 |
20 |
21 | # @pytest.mark.timeout(5)
22 | @pytest.mark.skip()
23 | def test_to_timeout_fibonacci_classic():
24 | """
25 | Напишите тут свои тесты, которые проверяют на время Фибоначчи
26 | Воспользуйтесь @pytest.mark.timeout()
27 | """
28 | pass
29 |
30 |
31 | @pytest.fixture
32 | def timer():
33 | pass
34 |
35 |
36 | def test_classic_fibonacci(timer):
37 | pass
--------------------------------------------------------------------------------
/week06/tests/test_inventory_management.py:
--------------------------------------------------------------------------------
1 | import time
2 | import pytest
3 |
4 | from week06.code.inventory_management import Inventory, Product
5 |
6 |
7 | """
8 | Шаг 1 - Red.
9 | Давайте напишем тест для функциональности добавления нового товара.
10 | """
11 |
12 | def test_add_product_to_inventory():
13 | # your code:
14 | pass
15 |
16 |
17 | """
18 | Давайте повторим шаг 1.
19 |
20 | Напишите тест для проверки фунакциональности итоговой суммы всех товаров в инвентаре
21 |
22 | """
23 |
24 | def test_final_price():
25 | # your code:
26 | pass
--------------------------------------------------------------------------------
/week07/Seminar_7_clean.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": []
7 | },
8 | "kernelspec": {
9 | "name": "python3",
10 | "display_name": "Python 3"
11 | },
12 | "language_info": {
13 | "name": "python"
14 | }
15 | },
16 | "cells": [
17 | {
18 | "cell_type": "markdown",
19 | "source": [
20 | "# Продвинутый Python, семинар 7\n",
21 | "\n",
22 | "**Лектор:** Петров Тимур\n",
23 | "\n",
24 | "**Семинаристы:** Петров Тимур, Бузаев Федор, Дешеулин Олег, Коган Александра\n",
25 | "\n",
26 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)"
27 | ],
28 | "metadata": {
29 | "id": "-Ks3uLhkoPG3"
30 | }
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "source": [
35 | "Пилим запросы с помощью Query Builder и радуемся жизни!"
36 | ],
37 | "metadata": {
38 | "id": "zBGkG6OPtB1V"
39 | }
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "source": [
44 | ""
45 | ],
46 | "metadata": {
47 | "id": "xBr9tGvDtnHP"
48 | }
49 | },
50 | {
51 | "cell_type": "code",
52 | "source": [
53 | "!pip install pypika"
54 | ],
55 | "metadata": {
56 | "id": "tqAJViMOt7rL"
57 | },
58 | "execution_count": null,
59 | "outputs": []
60 | },
61 | {
62 | "cell_type": "code",
63 | "source": [
64 | "from pypika import Table, Query, Parameter, Order, Field, Parameter\n",
65 | "import pypika.functions as fn\n",
66 | "\n",
67 | "playlists = Table(\"playlists\")\n",
68 | "genres = Table(\"genres\")\n",
69 | "tracks = Table(\"tracks\")\n",
70 | "artists = Table(\"artists\")\n",
71 | "albums = Table(\"albums\")\n",
72 | "invoice_items = Table(\"invoice_items\")\n",
73 | "invoices = Table(\"invoices\")\n",
74 | "customers = Table(\"customers\")\n",
75 | "playlist_track = Table(\"playlist_track\")"
76 | ],
77 | "metadata": {
78 | "id": "Bu-spJhvuXEB"
79 | },
80 | "execution_count": 13,
81 | "outputs": []
82 | },
83 | {
84 | "cell_type": "code",
85 | "execution_count": 3,
86 | "metadata": {
87 | "id": "XcvFXFMVoJy2"
88 | },
89 | "outputs": [],
90 | "source": [
91 | "import sqlite3\n",
92 | "\n",
93 | "def get_res(query):\n",
94 | " with sqlite3.connect('chinook.db') as connection:\n",
95 | " cursor = connection.cursor()\n",
96 | " cursor.execute(str(query))\n",
97 | " cols = [full_column_info[0] for full_column_info in cursor.description]\n",
98 | " res = cursor.fetchall()\n",
99 | " return [{name: value for name, value in zip(cols, r)} for r in res]"
100 | ]
101 | },
102 | {
103 | "cell_type": "markdown",
104 | "source": [
105 | "### Задание 1\n",
106 | "\n",
107 | "Выведите кол-во треков в каждом плейлисте"
108 | ],
109 | "metadata": {
110 | "id": "bV6MSBL5uCx3"
111 | }
112 | },
113 | {
114 | "cell_type": "code",
115 | "source": [
116 | "result = #YOUR CODE\n",
117 | "get_res(result)"
118 | ],
119 | "metadata": {
120 | "colab": {
121 | "base_uri": "https://localhost:8080/"
122 | },
123 | "id": "F5gjz4miuR2c",
124 | "outputId": "5069bde4-abc8-407d-a1f8-632753303e1c"
125 | },
126 | "execution_count": 6,
127 | "outputs": [
128 | {
129 | "output_type": "execute_result",
130 | "data": {
131 | "text/plain": [
132 | "[{'PlaylistId': 1, 'num_tracks': 8715}]"
133 | ]
134 | },
135 | "metadata": {},
136 | "execution_count": 6
137 | }
138 | ]
139 | },
140 | {
141 | "cell_type": "markdown",
142 | "source": [
143 | "### Задание 2\n",
144 | "\n",
145 | "Выведите кол-во треков по каждому жанру по убыванию (жанр должен быть в виде имени, не Id)"
146 | ],
147 | "metadata": {
148 | "id": "oESnlRetwiRU"
149 | }
150 | },
151 | {
152 | "cell_type": "code",
153 | "source": [
154 | "result = #YOUR CODE\n",
155 | "get_res(result)"
156 | ],
157 | "metadata": {
158 | "colab": {
159 | "base_uri": "https://localhost:8080/"
160 | },
161 | "id": "SpD7RFg6wsVJ",
162 | "outputId": "339ee384-4e89-43c7-92c0-30870f7e9bc6"
163 | },
164 | "execution_count": 12,
165 | "outputs": [
166 | {
167 | "output_type": "execute_result",
168 | "data": {
169 | "text/plain": [
170 | "[{'Name': 'Rock', 'num_tracks': 1297},\n",
171 | " {'Name': 'Latin', 'num_tracks': 579},\n",
172 | " {'Name': 'Metal', 'num_tracks': 374},\n",
173 | " {'Name': 'Alternative & Punk', 'num_tracks': 332},\n",
174 | " {'Name': 'Jazz', 'num_tracks': 130},\n",
175 | " {'Name': 'TV Shows', 'num_tracks': 93},\n",
176 | " {'Name': 'Blues', 'num_tracks': 81},\n",
177 | " {'Name': 'Classical', 'num_tracks': 74},\n",
178 | " {'Name': 'Drama', 'num_tracks': 64},\n",
179 | " {'Name': 'R&B/Soul', 'num_tracks': 61},\n",
180 | " {'Name': 'Reggae', 'num_tracks': 58},\n",
181 | " {'Name': 'Pop', 'num_tracks': 48},\n",
182 | " {'Name': 'Soundtrack', 'num_tracks': 43},\n",
183 | " {'Name': 'Alternative', 'num_tracks': 40},\n",
184 | " {'Name': 'Hip Hop/Rap', 'num_tracks': 35},\n",
185 | " {'Name': 'Electronica/Dance', 'num_tracks': 30},\n",
186 | " {'Name': 'World', 'num_tracks': 28},\n",
187 | " {'Name': 'Heavy Metal', 'num_tracks': 28},\n",
188 | " {'Name': 'Sci Fi & Fantasy', 'num_tracks': 26},\n",
189 | " {'Name': 'Easy Listening', 'num_tracks': 24},\n",
190 | " {'Name': 'Comedy', 'num_tracks': 17},\n",
191 | " {'Name': 'Bossa Nova', 'num_tracks': 15},\n",
192 | " {'Name': 'Science Fiction', 'num_tracks': 13},\n",
193 | " {'Name': 'Rock And Roll', 'num_tracks': 12},\n",
194 | " {'Name': 'Opera', 'num_tracks': 1}]"
195 | ]
196 | },
197 | "metadata": {},
198 | "execution_count": 12
199 | }
200 | ]
201 | },
202 | {
203 | "cell_type": "markdown",
204 | "source": [
205 | "### Задание 3\n",
206 | "\n",
207 | "Выведите названия всех треков по выбранному названию жанра"
208 | ],
209 | "metadata": {
210 | "id": "lMWGOOF0x8eZ"
211 | }
212 | },
213 | {
214 | "cell_type": "code",
215 | "source": [
216 | "def get_res_by_name(query, name):\n",
217 | " with sqlite3.connect('chinook.db') as connection:\n",
218 | " cursor = connection.cursor()\n",
219 | " cursor.execute(str(query), {\"name\": f'%{name}%'})\n",
220 | " cols = [full_column_info[0] for full_column_info in cursor.description]\n",
221 | " res = cursor.fetchall()\n",
222 | " return [{name: value for name, value in zip(cols, r)} for r in res]\n",
223 | "\n",
224 | "our_genre = input()\n",
225 | "result = #YOUR CODE\n",
226 | "get_res_by_name(result, our_genre)"
227 | ],
228 | "metadata": {
229 | "colab": {
230 | "base_uri": "https://localhost:8080/"
231 | },
232 | "id": "5LzHa4kayVpV",
233 | "outputId": "e6371e35-ae8c-45d5-8c9d-cd03fb8bac54"
234 | },
235 | "execution_count": 16,
236 | "outputs": [
237 | {
238 | "name": "stdout",
239 | "output_type": "stream",
240 | "text": [
241 | "Bossa Nova\n"
242 | ]
243 | },
244 | {
245 | "output_type": "execute_result",
246 | "data": {
247 | "text/plain": [
248 | "[{'Name': 'Samba Da Bênção'},\n",
249 | " {'Name': 'Pot-Pourri N.º 4'},\n",
250 | " {'Name': 'Onde Anda Você'},\n",
251 | " {'Name': 'Samba Da Volta'},\n",
252 | " {'Name': 'Canto De Ossanha'},\n",
253 | " {'Name': 'Pot-Pourri N.º 5'},\n",
254 | " {'Name': 'Formosa'},\n",
255 | " {'Name': 'Como É Duro Trabalhar'},\n",
256 | " {'Name': 'Minha Namorada'},\n",
257 | " {'Name': 'Por Que Será'},\n",
258 | " {'Name': 'Berimbau'},\n",
259 | " {'Name': 'Deixa'},\n",
260 | " {'Name': 'Pot-Pourri N.º 2'},\n",
261 | " {'Name': 'Samba Em Prelúdio'},\n",
262 | " {'Name': 'Carta Ao Tom 74'}]"
263 | ]
264 | },
265 | "metadata": {},
266 | "execution_count": 16
267 | }
268 | ]
269 | },
270 | {
271 | "cell_type": "markdown",
272 | "source": [
273 | "### Задание 4\n",
274 | "\n",
275 | "Выведите топ-10 самых дорогих альбомов (цена альбома - сумма оплат по всем трекам)"
276 | ],
277 | "metadata": {
278 | "id": "P80Eh7nS0BXY"
279 | }
280 | },
281 | {
282 | "cell_type": "code",
283 | "source": [
284 | "res = #YOUR CODE\n",
285 | "get_res(res)"
286 | ],
287 | "metadata": {
288 | "colab": {
289 | "base_uri": "https://localhost:8080/"
290 | },
291 | "id": "Pz5bmc4i0YNm",
292 | "outputId": "72db8529-7398-406d-d66f-1bc0f261c4fa"
293 | },
294 | "execution_count": 33,
295 | "outputs": [
296 | {
297 | "output_type": "execute_result",
298 | "data": {
299 | "text/plain": [
300 | "[{'Title': 'The Office, Season 3', 'all_price': 39.8},\n",
301 | " {'Title': 'Battlestar Galactica (Classic), Season 1',\n",
302 | " 'all_price': 35.81999999999999},\n",
303 | " {'Title': 'Minha Historia', 'all_price': 34.649999999999984},\n",
304 | " {'Title': 'Heroes, Season 1', 'all_price': 33.82999999999999},\n",
305 | " {'Title': 'Acústico', 'all_price': 29.69999999999998},\n",
306 | " {'Title': 'Unplugged', 'all_price': 28.709999999999983},\n",
307 | " {'Title': 'Battlestar Galactica, Season 3', 'all_price': 27.859999999999992},\n",
308 | " {'Title': 'Greatest Hits', 'all_price': 27.719999999999985},\n",
309 | " {'Title': 'Lost, Season 2', 'all_price': 25.869999999999994},\n",
310 | " {'Title': 'Greatest Kiss', 'all_price': 25.739999999999988}]"
311 | ]
312 | },
313 | "metadata": {},
314 | "execution_count": 33
315 | }
316 | ]
317 | },
318 | {
319 | "cell_type": "markdown",
320 | "source": [
321 | "Так, помучались с вами немного с Query Builder. А зачем? На самом деле это все основа для ORM, о котором не успели поговорить на лекции. Что же, давайте говорить!"
322 | ],
323 | "metadata": {
324 | "id": "I7MTjDiZ4a3Y"
325 | }
326 | },
327 | {
328 | "cell_type": "markdown",
329 | "source": [
330 | "### Часть 3. ORM"
331 | ],
332 | "metadata": {
333 | "id": "HR7eL8t3UILW"
334 | }
335 | },
336 | {
337 | "cell_type": "markdown",
338 | "source": [
339 | "Ну и теперь вершина - ORM (Object-Relational Mapping)\n",
340 | "\n",
341 | "Плюсы:\n",
342 | "\n",
343 | "* Еще удобнее, чем Query Builder (вообще SQL не почувствуем)\n",
344 | "\n",
345 | "* Таблицы - почти DataClass\n",
346 | "\n",
347 | "* Результат автоматически отображается в красивые объекты\n",
348 | "\n",
349 | "Минусы:\n",
350 | "\n",
351 | "* Иногда нафиг не надо (бывает хуже производительность)\n",
352 | "\n",
353 | "Пример: [SQLAlchemy](https://www.sqlalchemy.org/)"
354 | ],
355 | "metadata": {
356 | "id": "Q8USp8rNiZDB"
357 | }
358 | },
359 | {
360 | "cell_type": "markdown",
361 | "source": [
362 | "Выглядит это вот так:\n",
363 | "\n",
364 | ""
365 | ],
366 | "metadata": {
367 | "id": "G5Tikw7-jsts"
368 | }
369 | },
370 | {
371 | "cell_type": "markdown",
372 | "source": [
373 | "* Python core - все, что про cursor, connection etc\n",
374 | "\n",
375 | "* Core - это похожее на Query Builder структура, возможность создавать запросы\n",
376 | "\n",
377 | "* ORM - уже модуль для маппинга результатов к объектам"
378 | ],
379 | "metadata": {
380 | "id": "PD5vvb4Lj169"
381 | }
382 | },
383 | {
384 | "cell_type": "markdown",
385 | "source": [
386 | "#### Движок"
387 | ],
388 | "metadata": {
389 | "id": "lP1glGlXkJPX"
390 | }
391 | },
392 | {
393 | "cell_type": "code",
394 | "source": [
395 | "!pip install sqlalchemy"
396 | ],
397 | "metadata": {
398 | "id": "XfiVna_AkM0S",
399 | "colab": {
400 | "base_uri": "https://localhost:8080/"
401 | },
402 | "outputId": "c38d23de-7534-4c3d-a168-1705dbd3abcf"
403 | },
404 | "execution_count": 34,
405 | "outputs": [
406 | {
407 | "output_type": "stream",
408 | "name": "stdout",
409 | "text": [
410 | "Requirement already satisfied: sqlalchemy in /usr/local/lib/python3.10/dist-packages (2.0.22)\n",
411 | "Requirement already satisfied: typing-extensions>=4.2.0 in /usr/local/lib/python3.10/dist-packages (from sqlalchemy) (4.5.0)\n",
412 | "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from sqlalchemy) (3.0.0)\n"
413 | ]
414 | }
415 | ]
416 | },
417 | {
418 | "cell_type": "code",
419 | "source": [
420 | "from sqlalchemy import create_engine, Column, Integer, String, Float\n",
421 | "from sqlalchemy.ext.declarative import declarative_base\n",
422 | "\n",
423 | "# Единственное место с подсоединением к СУБД\n",
424 | "engine = create_engine('sqlite+pysqlite:///chinook.db', echo=True)\n",
425 | "Base = declarative_base() # предок для всех моделей (таблиц)"
426 | ],
427 | "metadata": {
428 | "id": "vuAl8VNykLk1",
429 | "colab": {
430 | "base_uri": "https://localhost:8080/"
431 | },
432 | "outputId": "b1fe5fcb-6aa2-4145-fcdd-7e13bebd04dc"
433 | },
434 | "execution_count": 67,
435 | "outputs": [
436 | {
437 | "output_type": "stream",
438 | "name": "stderr",
439 | "text": [
440 | ":6: MovedIn20Warning: The ``declarative_base()`` function is now available as sqlalchemy.orm.declarative_base(). (deprecated since: 2.0) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)\n",
441 | " Base = declarative_base() # предок для всех моделей (таблиц)\n"
442 | ]
443 | }
444 | ]
445 | },
446 | {
447 | "cell_type": "markdown",
448 | "source": [
449 | "#### Модели"
450 | ],
451 | "metadata": {
452 | "id": "AoYJV5G3k207"
453 | }
454 | },
455 | {
456 | "cell_type": "code",
457 | "source": [
458 | "class Tracks(Base):\n",
459 | " __tablename__ = 'tracks' # имя таблицы\n",
460 | "\n",
461 | " track_id = Column(Integer, name='TrackId', primary_key=True) # обязательно должен быть PrimaryKey\n",
462 | " Name = Column(String)\n",
463 | " GenreId = Column(Integer)\n",
464 | " UnitPrice = Column(Float)\n",
465 | " MediaTypeId = Column(Integer)\n",
466 | " Milliseconds = Column(Integer)\n",
467 | "\n",
468 | " def __repr__(self):\n",
469 | " return f\"{self.Name}, {self.GenreId}\"\n",
470 | "\n",
471 | " # создаем колонки, дублируя БД"
472 | ],
473 | "metadata": {
474 | "id": "bgZBQ9hLk4sO"
475 | },
476 | "execution_count": 58,
477 | "outputs": []
478 | },
479 | {
480 | "cell_type": "markdown",
481 | "source": [
482 | "#### Сессия"
483 | ],
484 | "metadata": {
485 | "id": "c_aoJUDNltsv"
486 | }
487 | },
488 | {
489 | "cell_type": "markdown",
490 | "source": [
491 | "Что такое Session? По сути дела Connection в другом виде, содержит в себе объекты моделей, ленивое подключение (когда надо)"
492 | ],
493 | "metadata": {
494 | "id": "0hDEYlxRl-KC"
495 | }
496 | },
497 | {
498 | "cell_type": "code",
499 | "source": [
500 | "from sqlalchemy.orm import sessionmaker\n",
501 | "\n",
502 | "Session = sessionmaker(bind=engine, future=True, expire_on_commit=False) #future - использование второй версии, expire_on_commit - убиваем на commitе\n",
503 | "\n",
504 | "session = Session() #объект сессии\n",
505 | "session_2 = Session()"
506 | ],
507 | "metadata": {
508 | "id": "GyrikIVqlwu1"
509 | },
510 | "execution_count": 59,
511 | "outputs": []
512 | },
513 | {
514 | "cell_type": "markdown",
515 | "source": [
516 | "#### Select"
517 | ],
518 | "metadata": {
519 | "id": "JdUp4U4Cms2g"
520 | }
521 | },
522 | {
523 | "cell_type": "code",
524 | "source": [
525 | "from sqlalchemy import select\n",
526 | "\n",
527 | "query = select(Tracks).where(Tracks.Name.like('%name%'))\n",
528 | "\n",
529 | "for track in session.scalars(query):\n",
530 | " print(track)"
531 | ],
532 | "metadata": {
533 | "id": "Ik_EllNymwUI",
534 | "colab": {
535 | "base_uri": "https://localhost:8080/"
536 | },
537 | "outputId": "428a2079-96de-4ca0-d4f8-0a47321a0534"
538 | },
539 | "execution_count": 60,
540 | "outputs": [
541 | {
542 | "output_type": "stream",
543 | "name": "stdout",
544 | "text": [
545 | "2023-10-31 18:07:46,556 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n"
546 | ]
547 | },
548 | {
549 | "output_type": "stream",
550 | "name": "stderr",
551 | "text": [
552 | "INFO:sqlalchemy.engine.Engine:BEGIN (implicit)\n"
553 | ]
554 | },
555 | {
556 | "output_type": "stream",
557 | "name": "stdout",
558 | "text": [
559 | "2023-10-31 18:07:46,565 INFO sqlalchemy.engine.Engine SELECT tracks.\"TrackId\", tracks.\"Name\", tracks.\"GenreId\", tracks.\"UnitPrice\", tracks.\"MediaTypeId\", tracks.\"Milliseconds\" \n",
560 | "FROM tracks \n",
561 | "WHERE tracks.\"Name\" LIKE ?\n"
562 | ]
563 | },
564 | {
565 | "output_type": "stream",
566 | "name": "stderr",
567 | "text": [
568 | "INFO:sqlalchemy.engine.Engine:SELECT tracks.\"TrackId\", tracks.\"Name\", tracks.\"GenreId\", tracks.\"UnitPrice\", tracks.\"MediaTypeId\", tracks.\"Milliseconds\" \n",
569 | "FROM tracks \n",
570 | "WHERE tracks.\"Name\" LIKE ?\n"
571 | ]
572 | },
573 | {
574 | "output_type": "stream",
575 | "name": "stdout",
576 | "text": [
577 | "2023-10-31 18:07:46,571 INFO sqlalchemy.engine.Engine [generated in 0.00620s] ('%name%',)\n"
578 | ]
579 | },
580 | {
581 | "output_type": "stream",
582 | "name": "stderr",
583 | "text": [
584 | "INFO:sqlalchemy.engine.Engine:[generated in 0.00620s] ('%name%',)\n"
585 | ]
586 | },
587 | {
588 | "output_type": "stream",
589 | "name": "stdout",
590 | "text": [
591 | "Whatsername, 4\n",
592 | "Hallowed Be Thy Name (Live) [Non Album Bonus Track], 1\n",
593 | "Hallowed Be Thy Name, 3\n",
594 | "Hallowed Be Thy Name, 3\n",
595 | "Hallowed Be Thy Name, 1\n",
596 | "Hallowed Be Thy Name, 1\n",
597 | "Hallowed Be Thy Name, 3\n",
598 | "The Unnamed Feeling, 3\n",
599 | "Pride (In The Name Of Love), 1\n",
600 | "Pride (In The Name Of Love), 1\n",
601 | "Where The Streets Have No Name, 1\n",
602 | "You Know My Name, 23\n"
603 | ]
604 | }
605 | ]
606 | },
607 | {
608 | "cell_type": "markdown",
609 | "source": [
610 | "Вообще запросы такие же, как и в Query Builder"
611 | ],
612 | "metadata": {
613 | "id": "rAFw6iprnsOy"
614 | }
615 | },
616 | {
617 | "cell_type": "markdown",
618 | "source": [
619 | "#### Insert"
620 | ],
621 | "metadata": {
622 | "id": "8FH8gsRRnwbo"
623 | }
624 | },
625 | {
626 | "cell_type": "code",
627 | "source": [
628 | "new_track = Tracks(track_id=100000, MediaTypeId=1, Name='new_track', GenreId=1, UnitPrice=1.0, Milliseconds=15)\n",
629 | "session.add(new_track)\n",
630 | "# ничего не сделал..."
631 | ],
632 | "metadata": {
633 | "id": "Mhv3tYSDnkmO"
634 | },
635 | "execution_count": 61,
636 | "outputs": []
637 | },
638 | {
639 | "cell_type": "code",
640 | "source": [
641 | "res = session.execute(select(Tracks).filter_by(Name='new_track')).scalar() #а вот тут случился insert\n",
642 | "res is new_track"
643 | ],
644 | "metadata": {
645 | "colab": {
646 | "base_uri": "https://localhost:8080/"
647 | },
648 | "id": "_8XA8z3XoHDG",
649 | "outputId": "8b570a13-a34e-4fec-b75e-1a0a662317d9"
650 | },
651 | "execution_count": 62,
652 | "outputs": [
653 | {
654 | "output_type": "stream",
655 | "name": "stdout",
656 | "text": [
657 | "2023-10-31 18:08:13,736 INFO sqlalchemy.engine.Engine INSERT INTO tracks (\"TrackId\", \"Name\", \"GenreId\", \"UnitPrice\", \"MediaTypeId\", \"Milliseconds\") VALUES (?, ?, ?, ?, ?, ?)\n"
658 | ]
659 | },
660 | {
661 | "output_type": "stream",
662 | "name": "stderr",
663 | "text": [
664 | "INFO:sqlalchemy.engine.Engine:INSERT INTO tracks (\"TrackId\", \"Name\", \"GenreId\", \"UnitPrice\", \"MediaTypeId\", \"Milliseconds\") VALUES (?, ?, ?, ?, ?, ?)\n"
665 | ]
666 | },
667 | {
668 | "output_type": "stream",
669 | "name": "stdout",
670 | "text": [
671 | "2023-10-31 18:08:13,745 INFO sqlalchemy.engine.Engine [generated in 0.00858s] (100000, 'new_track', 1, 1.0, 1, 15)\n"
672 | ]
673 | },
674 | {
675 | "output_type": "stream",
676 | "name": "stderr",
677 | "text": [
678 | "INFO:sqlalchemy.engine.Engine:[generated in 0.00858s] (100000, 'new_track', 1, 1.0, 1, 15)\n"
679 | ]
680 | },
681 | {
682 | "output_type": "stream",
683 | "name": "stdout",
684 | "text": [
685 | "2023-10-31 18:08:13,753 INFO sqlalchemy.engine.Engine SELECT tracks.\"TrackId\", tracks.\"Name\", tracks.\"GenreId\", tracks.\"UnitPrice\", tracks.\"MediaTypeId\", tracks.\"Milliseconds\" \n",
686 | "FROM tracks \n",
687 | "WHERE tracks.\"Name\" = ?\n"
688 | ]
689 | },
690 | {
691 | "output_type": "stream",
692 | "name": "stderr",
693 | "text": [
694 | "INFO:sqlalchemy.engine.Engine:SELECT tracks.\"TrackId\", tracks.\"Name\", tracks.\"GenreId\", tracks.\"UnitPrice\", tracks.\"MediaTypeId\", tracks.\"Milliseconds\" \n",
695 | "FROM tracks \n",
696 | "WHERE tracks.\"Name\" = ?\n"
697 | ]
698 | },
699 | {
700 | "output_type": "stream",
701 | "name": "stdout",
702 | "text": [
703 | "2023-10-31 18:08:13,759 INFO sqlalchemy.engine.Engine [generated in 0.00605s] ('new_track',)\n"
704 | ]
705 | },
706 | {
707 | "output_type": "stream",
708 | "name": "stderr",
709 | "text": [
710 | "INFO:sqlalchemy.engine.Engine:[generated in 0.00605s] ('new_track',)\n"
711 | ]
712 | },
713 | {
714 | "output_type": "execute_result",
715 | "data": {
716 | "text/plain": [
717 | "True"
718 | ]
719 | },
720 | "metadata": {},
721 | "execution_count": 62
722 | }
723 | ]
724 | },
725 | {
726 | "cell_type": "markdown",
727 | "source": [
728 | "#### Relations (или же Joins)"
729 | ],
730 | "metadata": {
731 | "id": "sAclwkEgpOLf"
732 | }
733 | },
734 | {
735 | "cell_type": "code",
736 | "source": [
737 | "from sqlalchemy import ForeignKey, select\n",
738 | "from sqlalchemy.orm import relationship\n",
739 | "\n",
740 | "class RelatedTrack(Base):\n",
741 | " __tablename__ = 'tracks'\n",
742 | " __table_args__ = {'extend_existing': True} #дабы не было конфликтов\n",
743 | "\n",
744 | "\n",
745 | " track_id = Column(Integer, name='TrackId', primary_key=True) # обязательно должен быть PrimaryKey\n",
746 | " Name = Column(String)\n",
747 | " GenreId = Column(Integer, ForeignKey('genres.GenreId'))\n",
748 | " UnitPrice = Column(Float)\n",
749 | " MediaTypeId = Column(Integer)\n",
750 | " Milliseconds = Column(Integer)\n",
751 | "\n",
752 | " genre = relationship('Genre', back_populates='tracks')\n",
753 | "\n",
754 | "class Genre(Base):\n",
755 | " __tablename__ = 'genres'\n",
756 | " __table_args__ = {'extend_existing': True}\n",
757 | "\n",
758 | " GenreId = Column(Integer, primary_key=True)\n",
759 | " Name = Column(String)\n",
760 | "\n",
761 | " tracks = relationship(\"RelatedTrack\", back_populates='genre', uselist=True) #uselist - список"
762 | ],
763 | "metadata": {
764 | "id": "-ffYgF3fpThO"
765 | },
766 | "execution_count": 63,
767 | "outputs": []
768 | },
769 | {
770 | "cell_type": "code",
771 | "source": [
772 | "genre_1 = session.execute(select(Genre).filter_by(GenreId=1)).scalar()\n",
773 | "len(genre_1.tracks)"
774 | ],
775 | "metadata": {
776 | "id": "-pqRc8w0rPKR"
777 | },
778 | "execution_count": null,
779 | "outputs": []
780 | },
781 | {
782 | "cell_type": "code",
783 | "source": [
784 | "genre_1.tracks[0].Name"
785 | ],
786 | "metadata": {
787 | "colab": {
788 | "base_uri": "https://localhost:8080/",
789 | "height": 36
790 | },
791 | "id": "TUvSrQ2trwR9",
792 | "outputId": "1b8a29a2-c46f-44d5-ae65-11e0a7e7e01f"
793 | },
794 | "execution_count": 65,
795 | "outputs": [
796 | {
797 | "output_type": "execute_result",
798 | "data": {
799 | "text/plain": [
800 | "'For Those About To Rock (We Salute You)'"
801 | ],
802 | "application/vnd.google.colaboratory.intrinsic+json": {
803 | "type": "string"
804 | }
805 | },
806 | "metadata": {},
807 | "execution_count": 65
808 | }
809 | ]
810 | },
811 | {
812 | "cell_type": "code",
813 | "source": [
814 | "genre_1.tracks[0].genre is genre_1 #есть обратная связь"
815 | ],
816 | "metadata": {
817 | "colab": {
818 | "base_uri": "https://localhost:8080/"
819 | },
820 | "id": "c2ZgGF_lrzaA",
821 | "outputId": "2ffa37dc-013e-4004-fbce-b1f30845c8fb"
822 | },
823 | "execution_count": 66,
824 | "outputs": [
825 | {
826 | "output_type": "execute_result",
827 | "data": {
828 | "text/plain": [
829 | "True"
830 | ]
831 | },
832 | "metadata": {},
833 | "execution_count": 66
834 | }
835 | ]
836 | },
837 | {
838 | "cell_type": "markdown",
839 | "source": [
840 | "Зачем это все нужно? На самом деле мы можем загнать абсолютно весь наш датасет со всеми связями, которые нам нужны, таким образом, мы можем спокойно делать запросы сразу по всему! Обнулимся и сделаем по красоте"
841 | ],
842 | "metadata": {
843 | "id": "ckqJDoOg7k4c"
844 | }
845 | },
846 | {
847 | "cell_type": "code",
848 | "source": [
849 | "from sqlalchemy import create_engine, Column, Integer, String, Float\n",
850 | "from sqlalchemy.ext.declarative import declarative_base\n",
851 | "\n",
852 | "# Единственное место с подсоединением к СУБД\n",
853 | "engine = create_engine('sqlite+pysqlite:///chinook.db')\n",
854 | "Base = declarative_base() # предок для всех моделей (таблиц)"
855 | ],
856 | "metadata": {
857 | "colab": {
858 | "base_uri": "https://localhost:8080/"
859 | },
860 | "id": "TSoj4xzi-Y1k",
861 | "outputId": "2b8fb47c-eaa6-49d7-c02f-bb6485457bab"
862 | },
863 | "execution_count": 96,
864 | "outputs": [
865 | {
866 | "output_type": "stream",
867 | "name": "stderr",
868 | "text": [
869 | ":6: MovedIn20Warning: The ``declarative_base()`` function is now available as sqlalchemy.orm.declarative_base(). (deprecated since: 2.0) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)\n",
870 | " Base = declarative_base() # предок для всех моделей (таблиц)\n"
871 | ]
872 | }
873 | ]
874 | },
875 | {
876 | "cell_type": "code",
877 | "source": [
878 | "#YOUR CODE"
879 | ],
880 | "metadata": {
881 | "id": "allCNKh27unM"
882 | },
883 | "execution_count": 97,
884 | "outputs": []
885 | },
886 | {
887 | "cell_type": "code",
888 | "source": [
889 | "genre_1 = session.execute(select(Artists).filter_by(ArtistId=1)).scalar()"
890 | ],
891 | "metadata": {
892 | "colab": {
893 | "base_uri": "https://localhost:8080/"
894 | },
895 | "id": "TOGhHmFS-aYE",
896 | "outputId": "97bcd2ac-f3eb-4c25-8aae-a7a5e9bd591d"
897 | },
898 | "execution_count": 100,
899 | "outputs": [
900 | {
901 | "output_type": "stream",
902 | "name": "stdout",
903 | "text": [
904 | "2023-10-31 18:31:27,878 INFO sqlalchemy.engine.Engine SELECT artists.\"ArtistId\", artists.\"Name\" \n",
905 | "FROM artists \n",
906 | "WHERE artists.\"ArtistId\" = ?\n"
907 | ]
908 | },
909 | {
910 | "output_type": "stream",
911 | "name": "stderr",
912 | "text": [
913 | "INFO:sqlalchemy.engine.Engine:SELECT artists.\"ArtistId\", artists.\"Name\" \n",
914 | "FROM artists \n",
915 | "WHERE artists.\"ArtistId\" = ?\n"
916 | ]
917 | },
918 | {
919 | "output_type": "stream",
920 | "name": "stdout",
921 | "text": [
922 | "2023-10-31 18:31:27,884 INFO sqlalchemy.engine.Engine [cached since 36.28s ago] (1,)\n"
923 | ]
924 | },
925 | {
926 | "output_type": "stream",
927 | "name": "stderr",
928 | "text": [
929 | "INFO:sqlalchemy.engine.Engine:[cached since 36.28s ago] (1,)\n"
930 | ]
931 | }
932 | ]
933 | },
934 | {
935 | "cell_type": "code",
936 | "source": [
937 | "for album in genre_1.albums:\n",
938 | " print(album.tracks[0].Name)"
939 | ],
940 | "metadata": {
941 | "colab": {
942 | "base_uri": "https://localhost:8080/"
943 | },
944 | "id": "2QjnXttWAUho",
945 | "outputId": "67872827-8ea9-4367-cfd6-27127e25ba2b"
946 | },
947 | "execution_count": 101,
948 | "outputs": [
949 | {
950 | "output_type": "stream",
951 | "name": "stdout",
952 | "text": [
953 | "For Those About To Rock (We Salute You)\n",
954 | "Go Down\n"
955 | ]
956 | }
957 | ]
958 | },
959 | {
960 | "cell_type": "markdown",
961 | "source": [
962 | "То есть у вас сразу есть все связи, которые в дальнейшем можно использовать!"
963 | ],
964 | "metadata": {
965 | "id": "NRA-LuCYAXWS"
966 | }
967 | }
968 | ]
969 | }
--------------------------------------------------------------------------------
/week07/chinook.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week07/chinook.db
--------------------------------------------------------------------------------
/week08/Python_2_Seminar_8_solved.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "collapsed_sections": [
8 | "vWJiOPcMkcP_",
9 | "4DDAaggSnMA0",
10 | "qAhYcNYosczT",
11 | "KIGvcP7Bzfx3",
12 | "3p88FrgL0cri"
13 | ]
14 | },
15 | "kernelspec": {
16 | "name": "python3",
17 | "display_name": "Python 3"
18 | },
19 | "language_info": {
20 | "name": "python"
21 | }
22 | },
23 | "cells": [
24 | {
25 | "cell_type": "markdown",
26 | "source": [
27 | "# Продвинутый Python, семинар 8\n",
28 | "\n",
29 | "**Лектор:** Петров Тимур\n",
30 | "\n",
31 | "**Семинаристы:** Петров Тимур, Коган Александра, Бузаев Федор, Дешеулин Олег\n",
32 | "\n",
33 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)"
34 | ],
35 | "metadata": {
36 | "id": "u7ERY9CghiWQ"
37 | }
38 | },
39 | {
40 | "cell_type": "markdown",
41 | "source": [
42 | "Итак, сегодня мы с вами потренируемся работать с запросами на PyMongo (в лишний раз вспомним, что тут делать, как писать фильтры, группировки и все остальное)\n",
43 | "\n",
44 | "И в каждой части будет небольшая шпаргалка"
45 | ],
46 | "metadata": {
47 | "id": "IRYzNaaIhnjb"
48 | }
49 | },
50 | {
51 | "cell_type": "markdown",
52 | "source": [
53 | "## Часть 1. Учимся узнавать информацию про датасет"
54 | ],
55 | "metadata": {
56 | "id": "NiuPZ8rHh-PP"
57 | }
58 | },
59 | {
60 | "cell_type": "markdown",
61 | "source": [
62 | "Шпора:\n",
63 | "\n",
64 | "```\n",
65 | "list_database_names() - вывести список доступных баз данных\n",
66 | "\n",
67 | "list_collection_names() - вывести список доступных коллекций\n",
68 | "\n",
69 | "find_one() - вывести 1 элемент коллекции\n",
70 | "\n",
71 | "find() - найти все\n",
72 | "\n",
73 | "count_documents(filter) - посчитать количество документов в коллекции\n",
74 | "```"
75 | ],
76 | "metadata": {
77 | "id": "V_dnVJnGiBll"
78 | }
79 | },
80 | {
81 | "cell_type": "code",
82 | "source": [
83 | "!pip install pymongo"
84 | ],
85 | "metadata": {
86 | "id": "hltL_hL1kmmp"
87 | },
88 | "execution_count": null,
89 | "outputs": []
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": null,
94 | "metadata": {
95 | "id": "yIvCgqqaheNk"
96 | },
97 | "outputs": [],
98 | "source": [
99 | "import pymongo\n",
100 | "from pymongo import MongoClient\n",
101 | "\n",
102 | "cluster = MongoClient('mongodb+srv://admin:admin@pythontest.l4aoup6.mongodb.net/?retryWrites=true&w=majority')"
103 | ]
104 | },
105 | {
106 | "cell_type": "markdown",
107 | "source": [
108 | "### Задание 1"
109 | ],
110 | "metadata": {
111 | "id": "vWJiOPcMkcP_"
112 | }
113 | },
114 | {
115 | "cell_type": "markdown",
116 | "source": [
117 | "Выберите в качестве базы данных - sample_supplies, а в качестве коллекции - единственную представленную там. Выведите один экземпляр коллекции\n",
118 | "\n",
119 | "После этого посмотрите, сколько данных у нас в коллекции"
120 | ],
121 | "metadata": {
122 | "id": "GUkNkZX-kfPJ"
123 | }
124 | },
125 | {
126 | "cell_type": "code",
127 | "source": [
128 | "col = cluster[\"sample_supplies\"][\"sales\"]\n",
129 | "col.find_one()"
130 | ],
131 | "metadata": {
132 | "id": "fmbv-eZ9k5vP"
133 | },
134 | "execution_count": null,
135 | "outputs": []
136 | },
137 | {
138 | "cell_type": "code",
139 | "source": [
140 | "col = cluster[\"sample_supplies\"][\"sales\"]\n",
141 | "col.count_documents({}) #обратите внимание, что если нужно просто число документов, то надо дополнительно указать пустой фильтр"
142 | ],
143 | "metadata": {
144 | "colab": {
145 | "base_uri": "https://localhost:8080/"
146 | },
147 | "id": "LWMS7j44mrb9",
148 | "outputId": "154317fb-e9be-47eb-ac1e-4081e2748642"
149 | },
150 | "execution_count": null,
151 | "outputs": [
152 | {
153 | "output_type": "execute_result",
154 | "data": {
155 | "text/plain": [
156 | "5000"
157 | ]
158 | },
159 | "metadata": {},
160 | "execution_count": 15
161 | }
162 | ]
163 | },
164 | {
165 | "cell_type": "markdown",
166 | "source": [
167 | "## Часть 2. Учимся делать простые запросы"
168 | ],
169 | "metadata": {
170 | "id": "4DDAaggSnMA0"
171 | }
172 | },
173 | {
174 | "cell_type": "markdown",
175 | "source": [
176 | "Шпора:\n",
177 | "\n",
178 | "```\n",
179 | "\n",
180 | "distinct(field, filter) - вывести все уникальные значения\n",
181 | "\n",
182 | "find(filter, cols) - применяем фильтр, получаем данные с имеющихся cols\n",
183 | "\n",
184 | "Структура cols:\n",
185 | "\n",
186 | "{\n",
187 | " col_1: 0 - не показываем\n",
188 | " col_2: 1 - показываем значения\n",
189 | "}\n",
190 | "```\n",
191 | "\n",
192 | "Фильтры:\n",
193 | "\n",
194 | "```\n",
195 | "Операции:\n",
196 | "\n",
197 | " $eq - =\n",
198 | " $ne - ><\n",
199 | " $lt - <\n",
200 | " $lte - <=\n",
201 | " $gt - >\n",
202 | " $gte - >=\n",
203 | " $in - поиск в массиве (или строке)\n",
204 | " $nin - NOT IN\n",
205 | " $exists - существование (IS NOT NULL)\n",
206 | "\n",
207 | "Логика:\n",
208 | "\n",
209 | " $and - AND\n",
210 | " $not - NOT\n",
211 | " $or - OR\n",
212 | "\n",
213 | "Регулярки (тип LIKE):\n",
214 | "\n",
215 | " $regex - регулярка\n",
216 | "```\n",
217 | "\n",
218 | "Прочие полезные вещи:\n",
219 | "\n",
220 | "```\n",
221 | "col.field - обращаемся к значению col и внутри него к значению field\n",
222 | "\n",
223 | "limit(nums) - выбрать несколько значений\n",
224 | "\n",
225 | "count() - посчитать число результатов\n",
226 | "\n",
227 | "sort() - сортировка\n",
228 | "\n",
229 | "```"
230 | ],
231 | "metadata": {
232 | "id": "hqMeUDSjnYFC"
233 | }
234 | },
235 | {
236 | "cell_type": "markdown",
237 | "source": [
238 | "### Задание 2"
239 | ],
240 | "metadata": {
241 | "id": "uTqkQr5Ro4Kd"
242 | }
243 | },
244 | {
245 | "cell_type": "markdown",
246 | "source": [
247 | "Выведите все уникальные города магазинов (поле storeLocation), а также способы оплаты (purchaseMethod)"
248 | ],
249 | "metadata": {
250 | "id": "_PwCMMUwpQ99"
251 | }
252 | },
253 | {
254 | "cell_type": "code",
255 | "source": [
256 | "col.distinct(\"storeLocation\"), col.distinct(\"purchaseMethod\")"
257 | ],
258 | "metadata": {
259 | "colab": {
260 | "base_uri": "https://localhost:8080/"
261 | },
262 | "id": "LOwKKhCBo5_c",
263 | "outputId": "0d3166ea-8555-4c5c-9dc2-9189d7474701"
264 | },
265 | "execution_count": null,
266 | "outputs": [
267 | {
268 | "output_type": "execute_result",
269 | "data": {
270 | "text/plain": [
271 | "(['Austin', 'Denver', 'London', 'New York', 'San Diego', 'Seattle'],\n",
272 | " ['In store', 'Online', 'Phone'])"
273 | ]
274 | },
275 | "metadata": {},
276 | "execution_count": 19
277 | }
278 | ]
279 | },
280 | {
281 | "cell_type": "markdown",
282 | "source": [
283 | "### Задание 3"
284 | ],
285 | "metadata": {
286 | "id": "9Z7s2LQtpWaj"
287 | }
288 | },
289 | {
290 | "cell_type": "markdown",
291 | "source": [
292 | "Выведите только пользователей, в чьих заказах был использован купон (couponUsed), название города начинается на S или на D, а также метод оплаты - онлайн. Отсортируйте результат по названию города по убыванию"
293 | ],
294 | "metadata": {
295 | "id": "1JI6iQs2pYu2"
296 | }
297 | },
298 | {
299 | "cell_type": "code",
300 | "source": [
301 | "cols = {\"customer\": 1, \"storeLocation\": 1, \"_id\": 0}\n",
302 | "filters = {\"couponUsed\": True, \"purchaseMethod\": \"Online\", \"storeLocation\": {\"$regex\": \"[SD].*\"}}\n",
303 | "result = col.find(filters, cols).sort({\"storeLocation\": pymongo.DESCENDING})\n",
304 | "for k in result:\n",
305 | " print(k)"
306 | ],
307 | "metadata": {
308 | "id": "1EEvBC6up2Ff"
309 | },
310 | "execution_count": null,
311 | "outputs": []
312 | },
313 | {
314 | "cell_type": "markdown",
315 | "source": [
316 | "### Задание 4"
317 | ],
318 | "metadata": {
319 | "id": "DG5Mh5NWrKFY"
320 | }
321 | },
322 | {
323 | "cell_type": "markdown",
324 | "source": [
325 | "Добавьте также ограничение на пол покупателя (выбираем только женщин), а также на оценку (satisfaction должен быть от 1 до 3). Выведите только 10 значений (сортировать не нужно)"
326 | ],
327 | "metadata": {
328 | "id": "bWcLuzn3rY8-"
329 | }
330 | },
331 | {
332 | "cell_type": "code",
333 | "source": [
334 | "cols = {\"customer\": 1, \"storeLocation\": 1, \"_id\": 0}\n",
335 | "matching = {\"couponUsed\": True, \"purchaseMethod\": \"Online\", \"storeLocation\": {\"$regex\": \"[SD].*\"}, \"customer.gender\" : \"F\", \"customer.satisfaction\": {\"$gte\": 1, \"$lte\": 3}}\n",
336 | "result = col.find(matching, cols).limit(10)\n",
337 | "for k in result:\n",
338 | " print(k)"
339 | ],
340 | "metadata": {
341 | "id": "tpvKpj3braVb"
342 | },
343 | "execution_count": null,
344 | "outputs": []
345 | },
346 | {
347 | "cell_type": "markdown",
348 | "source": [
349 | "## Часть 3. Делаем агрегацию"
350 | ],
351 | "metadata": {
352 | "id": "qAhYcNYosczT"
353 | }
354 | },
355 | {
356 | "cell_type": "markdown",
357 | "source": [
358 | "Шпора:\n",
359 | "\n",
360 | "```\n",
361 | "aggregate(pipeline) - сделать агрегацию по pipeline\n",
362 | "\n",
363 | "pipeline - список из словарей\n",
364 | "\n",
365 | "Из каких частей может состоять pipeline:\n",
366 | "\n",
367 | "{\"$unwind\": } - сделать анпакинг (то есть если все в одном списке, то он разобьет на части)\n",
368 | "{\"$match\": {}} - применение where\n",
369 | "{\"$group\": {}} - группирование\n",
370 | "{\"$sort\": {}} - сортировка\n",
371 | "{\"$limit\": {}} - ограничение\n",
372 | "{\"$project\": {}} - выбрать нужные колонки\n",
373 | "```\n",
374 | "\n",
375 | "Как работает $group:\n",
376 | "\n",
377 | "```\n",
378 | "{\n",
379 | " \"_id\" : \"$value\" - по чему аггрегировать (названия идут через $)\n",
380 | " \"name\": {aggregation} - название и как агрегировать\n",
381 | "}\n",
382 | "```\n",
383 | "\n",
384 | "Какие бывают агрегации?\n",
385 | "\n",
386 | "```\n",
387 | " $sum - сумма\n",
388 | " $avg - среднее\n",
389 | " $median - медиана\n",
390 | " $min - минимум (также есть minN)\n",
391 | " $max - максимум (также есть maxN)\n",
392 | " $first - первое значение\n",
393 | " $last - последнее значение\n",
394 | " $addToSet - получить список уникальных\n",
395 | "```"
396 | ],
397 | "metadata": {
398 | "id": "xTH1z3LTt7-7"
399 | }
400 | },
401 | {
402 | "cell_type": "markdown",
403 | "source": [
404 | "### Задание 5"
405 | ],
406 | "metadata": {
407 | "id": "_KpyJCAguc6H"
408 | }
409 | },
410 | {
411 | "cell_type": "markdown",
412 | "source": [
413 | "Посчитайте по каждому заказу:\n",
414 | "\n",
415 | "* Число уникальных товаров\n",
416 | "\n",
417 | "* Число товаров\n",
418 | "\n",
419 | "* Сумму оплаты"
420 | ],
421 | "metadata": {
422 | "id": "UO95F10QugLI"
423 | }
424 | },
425 | {
426 | "cell_type": "code",
427 | "source": [
428 | "u = {\"$unwind\": \"$items\"}\n",
429 | "g = {\"$group\":\n",
430 | " {\n",
431 | " \"_id\": \"$_id\",\n",
432 | " \"unique_items\": {\"$sum\": 1},\n",
433 | " \"items\": {\"$sum\": \"$items.quantity\"},\n",
434 | " \"price\": {\"$sum\": \"$items.price\"}\n",
435 | " }\n",
436 | " }\n",
437 | "for k in col.aggregate([u, g]):\n",
438 | " print(k)"
439 | ],
440 | "metadata": {
441 | "id": "fV0mFdIsu22Y"
442 | },
443 | "execution_count": null,
444 | "outputs": []
445 | },
446 | {
447 | "cell_type": "markdown",
448 | "source": [
449 | "### Задание 6"
450 | ],
451 | "metadata": {
452 | "id": "CFPFl2_sy16n"
453 | }
454 | },
455 | {
456 | "cell_type": "markdown",
457 | "source": [
458 | "Выведите только те заказы, где число товаров больше 30, а сумма оплаты больше 1900"
459 | ],
460 | "metadata": {
461 | "id": "GWLRJ5Puy4mq"
462 | }
463 | },
464 | {
465 | "cell_type": "code",
466 | "source": [
467 | "u = {\"$unwind\": \"$items\"}\n",
468 | "g = {\"$group\":\n",
469 | " {\n",
470 | " \"_id\": \"$_id\",\n",
471 | " \"unique_items\": {\"$sum\": 1},\n",
472 | " \"items\": {\"$sum\": \"$items.quantity\"},\n",
473 | " \"price\": {\"$sum\": \"$items.price\"}\n",
474 | " }\n",
475 | " }\n",
476 | "m = {\"$match\":\n",
477 | " {\n",
478 | " \"items\": {\"$gte\": 30},\n",
479 | " \"price\": {\"$gte\": 1900}\n",
480 | " }\n",
481 | " }\n",
482 | "for k in col.aggregate([u, g, m]):\n",
483 | " print(k)"
484 | ],
485 | "metadata": {
486 | "id": "1VlExcOLy-H1"
487 | },
488 | "execution_count": null,
489 | "outputs": []
490 | },
491 | {
492 | "cell_type": "markdown",
493 | "source": [
494 | "## Часть 4. Соединяем документы"
495 | ],
496 | "metadata": {
497 | "id": "KIGvcP7Bzfx3"
498 | }
499 | },
500 | {
501 | "cell_type": "markdown",
502 | "source": [
503 | "Шпора:\n",
504 | "\n",
505 | "```\n",
506 | "{$lookup: {\n",
507 | " \"from\": collection - с чем связываем\n",
508 | " \"localField\": value - по какому полю из нашей коллекции\n",
509 | " \"foreignField\": value - по какому полю из другой коллекции\n",
510 | " \"as\": name - как назвать колонку\n",
511 | "}} - делаем соединение\n",
512 | "```"
513 | ],
514 | "metadata": {
515 | "id": "OL_eQAMuzosI"
516 | }
517 | },
518 | {
519 | "cell_type": "markdown",
520 | "source": [
521 | "### Задание 7"
522 | ],
523 | "metadata": {
524 | "id": "3p88FrgL0cri"
525 | }
526 | },
527 | {
528 | "cell_type": "markdown",
529 | "source": [
530 | "Соедините две таблицы из базы данных sample_analytics (customers и accounts) и выведите для каждого пользователя все его аккаунты"
531 | ],
532 | "metadata": {
533 | "id": "uz8Aco9G0jJ0"
534 | }
535 | },
536 | {
537 | "cell_type": "code",
538 | "source": [
539 | "col = cluster[\"sample_analytics\"][\"customers\"]"
540 | ],
541 | "metadata": {
542 | "id": "WaUCAWUP0npN"
543 | },
544 | "execution_count": null,
545 | "outputs": []
546 | },
547 | {
548 | "cell_type": "code",
549 | "source": [
550 | "u = {\"$unwind\": \"$accounts\"}\n",
551 | "l = {\"$lookup\":\n",
552 | " {\n",
553 | " \"from\" : \"accounts\",\n",
554 | " \"localField\": \"accounts\",\n",
555 | " \"foreignField\": \"account_id\",\n",
556 | " \"as\": \"accs\"\n",
557 | " }\n",
558 | "}\n",
559 | "m = {\"$project\": {\"username\": 1, \"name\": 1, \"accs\": 1}}\n",
560 | "\n",
561 | "for k in col.aggregate([u, l, m]):\n",
562 | " print(k)"
563 | ],
564 | "metadata": {
565 | "id": "YUraqI2v0tOt"
566 | },
567 | "execution_count": null,
568 | "outputs": []
569 | }
570 | ]
571 | }
--------------------------------------------------------------------------------
/week08/Seminar_8_clean.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": []
7 | },
8 | "kernelspec": {
9 | "name": "python3",
10 | "display_name": "Python 3"
11 | },
12 | "language_info": {
13 | "name": "python"
14 | }
15 | },
16 | "cells": [
17 | {
18 | "cell_type": "markdown",
19 | "source": [
20 | "# Продвинутый Python, семинар 8\n",
21 | "\n",
22 | "**Лектор:** Петров Тимур\n",
23 | "\n",
24 | "**Семинаристы:** Петров Тимур, Коган Александра, Бузаев Федор, Дешеулин Олег\n",
25 | "\n",
26 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)"
27 | ],
28 | "metadata": {
29 | "id": "u7ERY9CghiWQ"
30 | }
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "source": [
35 | "Итак, сегодня мы с вами потренируемся работать с запросами на PyMongo (в лишний раз вспомним, что тут делать, как писать фильтры, группировки и все остальное)\n",
36 | "\n",
37 | "И в каждой части будет небольшая шпаргалка"
38 | ],
39 | "metadata": {
40 | "id": "IRYzNaaIhnjb"
41 | }
42 | },
43 | {
44 | "cell_type": "markdown",
45 | "source": [
46 | "## Часть 1. Учимся узнавать информацию про датасет"
47 | ],
48 | "metadata": {
49 | "id": "NiuPZ8rHh-PP"
50 | }
51 | },
52 | {
53 | "cell_type": "markdown",
54 | "source": [
55 | "Шпора:\n",
56 | "\n",
57 | "```\n",
58 | "list_database_names() - вывести список доступных баз данных\n",
59 | "\n",
60 | "list_collection_names() - вывести список доступных коллекций\n",
61 | "\n",
62 | "find_one() - вывести 1 элемент коллекции\n",
63 | "\n",
64 | "find() - найти все\n",
65 | "\n",
66 | "count_documents(filter) - посчитать количество документов в коллекции\n",
67 | "```"
68 | ],
69 | "metadata": {
70 | "id": "V_dnVJnGiBll"
71 | }
72 | },
73 | {
74 | "cell_type": "code",
75 | "source": [
76 | "!pip install pymongo"
77 | ],
78 | "metadata": {
79 | "id": "hltL_hL1kmmp"
80 | },
81 | "execution_count": null,
82 | "outputs": []
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": null,
87 | "metadata": {
88 | "id": "yIvCgqqaheNk"
89 | },
90 | "outputs": [],
91 | "source": [
92 | "import pymongo\n",
93 | "from pymongo import MongoClient\n",
94 | "\n",
95 | "cluster = MongoClient('mongodb+srv://admin:admin@pythontest.l4aoup6.mongodb.net/?retryWrites=true&w=majority')"
96 | ]
97 | },
98 | {
99 | "cell_type": "markdown",
100 | "source": [
101 | "### Задание 1"
102 | ],
103 | "metadata": {
104 | "id": "vWJiOPcMkcP_"
105 | }
106 | },
107 | {
108 | "cell_type": "markdown",
109 | "source": [
110 | "Выберите в качестве базы данных - sample_supplies, а в качестве коллекции - единственную представленную там. Выведите один экземпляр коллекции\n",
111 | "\n",
112 | "После этого посмотрите, сколько данных у нас в коллекции"
113 | ],
114 | "metadata": {
115 | "id": "GUkNkZX-kfPJ"
116 | }
117 | },
118 | {
119 | "cell_type": "code",
120 | "source": [
121 | "#YOUR CODE"
122 | ],
123 | "metadata": {
124 | "id": "LWMS7j44mrb9"
125 | },
126 | "execution_count": null,
127 | "outputs": []
128 | },
129 | {
130 | "cell_type": "markdown",
131 | "source": [
132 | "## Часть 2. Учимся делать простые запросы"
133 | ],
134 | "metadata": {
135 | "id": "4DDAaggSnMA0"
136 | }
137 | },
138 | {
139 | "cell_type": "markdown",
140 | "source": [
141 | "Шпора:\n",
142 | "\n",
143 | "```\n",
144 | "\n",
145 | "distinct(field, filter) - вывести все уникальные значения\n",
146 | "\n",
147 | "find(filter, cols) - применяем фильтр, получаем данные с имеющихся cols\n",
148 | "\n",
149 | "Структура cols:\n",
150 | "\n",
151 | "{\n",
152 | " col_1: 0 - не показываем\n",
153 | " col_2: 1 - показываем значения\n",
154 | "}\n",
155 | "```\n",
156 | "\n",
157 | "Фильтры:\n",
158 | "\n",
159 | "```\n",
160 | "Операции:\n",
161 | "\n",
162 | " $eq - =\n",
163 | " $ne - ><\n",
164 | " $lt - <\n",
165 | " $lte - <=\n",
166 | " $gt - >\n",
167 | " $gte - >=\n",
168 | " $in - поиск в массиве (или строке)\n",
169 | " $nin - NOT IN\n",
170 | " $exists - существование (IS NOT NULL)\n",
171 | "\n",
172 | "Логика:\n",
173 | "\n",
174 | " $and - AND\n",
175 | " $not - NOT\n",
176 | " $or - OR\n",
177 | "\n",
178 | "Регулярки (тип LIKE):\n",
179 | "\n",
180 | " $regex - регулярка\n",
181 | "```\n",
182 | "\n",
183 | "Прочие полезные вещи:\n",
184 | "\n",
185 | "```\n",
186 | "col.field - обращаемся к значению col и внутри него к значению field\n",
187 | "\n",
188 | "limit(nums) - выбрать несколько значений\n",
189 | "\n",
190 | "count() - посчитать число результатов\n",
191 | "\n",
192 | "sort() - сортировка\n",
193 | "\n",
194 | "```"
195 | ],
196 | "metadata": {
197 | "id": "hqMeUDSjnYFC"
198 | }
199 | },
200 | {
201 | "cell_type": "markdown",
202 | "source": [
203 | "### Задание 2"
204 | ],
205 | "metadata": {
206 | "id": "uTqkQr5Ro4Kd"
207 | }
208 | },
209 | {
210 | "cell_type": "markdown",
211 | "source": [
212 | "Выведите все уникальные города магазинов (поле storeLocation), а также способы оплаты (purchaseMethod)"
213 | ],
214 | "metadata": {
215 | "id": "_PwCMMUwpQ99"
216 | }
217 | },
218 | {
219 | "cell_type": "code",
220 | "source": [
221 | "#YOUR CODE"
222 | ],
223 | "metadata": {
224 | "id": "LOwKKhCBo5_c"
225 | },
226 | "execution_count": null,
227 | "outputs": []
228 | },
229 | {
230 | "cell_type": "markdown",
231 | "source": [
232 | "### Задание 3"
233 | ],
234 | "metadata": {
235 | "id": "9Z7s2LQtpWaj"
236 | }
237 | },
238 | {
239 | "cell_type": "markdown",
240 | "source": [
241 | "Выведите только пользователей, в чьих заказах был использован купон (couponUsed), название города начинается на S или на D, а также метод оплаты - онлайн. Отсортируйте результат по названию города по убыванию"
242 | ],
243 | "metadata": {
244 | "id": "1JI6iQs2pYu2"
245 | }
246 | },
247 | {
248 | "cell_type": "code",
249 | "source": [
250 | "#YOUR CODE"
251 | ],
252 | "metadata": {
253 | "id": "1EEvBC6up2Ff"
254 | },
255 | "execution_count": null,
256 | "outputs": []
257 | },
258 | {
259 | "cell_type": "markdown",
260 | "source": [
261 | "### Задание 4"
262 | ],
263 | "metadata": {
264 | "id": "DG5Mh5NWrKFY"
265 | }
266 | },
267 | {
268 | "cell_type": "markdown",
269 | "source": [
270 | "Добавьте также ограничение на пол покупателя (выбираем только женщин), а также на оценку (satisfaction должен быть от 1 до 3). Выведите только 10 значений (сортировать не нужно)"
271 | ],
272 | "metadata": {
273 | "id": "bWcLuzn3rY8-"
274 | }
275 | },
276 | {
277 | "cell_type": "code",
278 | "source": [
279 | "#YOUR CODE"
280 | ],
281 | "metadata": {
282 | "id": "tpvKpj3braVb"
283 | },
284 | "execution_count": null,
285 | "outputs": []
286 | },
287 | {
288 | "cell_type": "markdown",
289 | "source": [
290 | "## Часть 3. Делаем агрегацию"
291 | ],
292 | "metadata": {
293 | "id": "qAhYcNYosczT"
294 | }
295 | },
296 | {
297 | "cell_type": "markdown",
298 | "source": [
299 | "Шпора:\n",
300 | "\n",
301 | "```\n",
302 | "aggregate(pipeline) - сделать агрегацию по pipeline\n",
303 | "\n",
304 | "pipeline - список из словарей\n",
305 | "\n",
306 | "Из каких частей может состоять pipeline:\n",
307 | "\n",
308 | "{\"$unwind\": } - сделать анпакинг (то есть если все в одном списке, то он разобьет на части)\n",
309 | "{\"$match\": {}} - применение where\n",
310 | "{\"$group\": {}} - группирование\n",
311 | "{\"$sort\": {}} - сортировка\n",
312 | "{\"$limit\": {}} - ограничение\n",
313 | "{\"$project\": {}} - выбрать нужные колонки\n",
314 | "```\n",
315 | "\n",
316 | "Как работает $group:\n",
317 | "\n",
318 | "```\n",
319 | "{\n",
320 | " \"_id\" : \"$value\" - по чему аггрегировать (названия идут через $)\n",
321 | " \"name\": {aggregation} - название и как агрегировать\n",
322 | "}\n",
323 | "```\n",
324 | "\n",
325 | "Какие бывают агрегации?\n",
326 | "\n",
327 | "```\n",
328 | " $sum - сумма\n",
329 | " $avg - среднее\n",
330 | " $median - медиана\n",
331 | " $min - минимум (также есть minN)\n",
332 | " $max - максимум (также есть maxN)\n",
333 | " $first - первое значение\n",
334 | " $last - последнее значение\n",
335 | " $addToSet - получить список уникальных\n",
336 | "```"
337 | ],
338 | "metadata": {
339 | "id": "xTH1z3LTt7-7"
340 | }
341 | },
342 | {
343 | "cell_type": "markdown",
344 | "source": [
345 | "### Задание 5"
346 | ],
347 | "metadata": {
348 | "id": "_KpyJCAguc6H"
349 | }
350 | },
351 | {
352 | "cell_type": "markdown",
353 | "source": [
354 | "Посчитайте по каждому заказу:\n",
355 | "\n",
356 | "* Число уникальных товаров\n",
357 | "\n",
358 | "* Число товаров\n",
359 | "\n",
360 | "* Сумму оплаты"
361 | ],
362 | "metadata": {
363 | "id": "UO95F10QugLI"
364 | }
365 | },
366 | {
367 | "cell_type": "code",
368 | "source": [
369 | "#YOUR CODE"
370 | ],
371 | "metadata": {
372 | "id": "fV0mFdIsu22Y"
373 | },
374 | "execution_count": null,
375 | "outputs": []
376 | },
377 | {
378 | "cell_type": "markdown",
379 | "source": [
380 | "### Задание 6"
381 | ],
382 | "metadata": {
383 | "id": "CFPFl2_sy16n"
384 | }
385 | },
386 | {
387 | "cell_type": "markdown",
388 | "source": [
389 | "Выведите только те заказы, где число товаров больше 30, а сумма оплаты больше 1900"
390 | ],
391 | "metadata": {
392 | "id": "GWLRJ5Puy4mq"
393 | }
394 | },
395 | {
396 | "cell_type": "code",
397 | "source": [
398 | "#YOUR CODE"
399 | ],
400 | "metadata": {
401 | "id": "1VlExcOLy-H1"
402 | },
403 | "execution_count": null,
404 | "outputs": []
405 | },
406 | {
407 | "cell_type": "markdown",
408 | "source": [
409 | "## Часть 4. Соединяем документы"
410 | ],
411 | "metadata": {
412 | "id": "KIGvcP7Bzfx3"
413 | }
414 | },
415 | {
416 | "cell_type": "markdown",
417 | "source": [
418 | "Шпора:\n",
419 | "\n",
420 | "```\n",
421 | "{$lookup: {\n",
422 | " \"from\": collection - с чем связываем\n",
423 | " \"localField\": value - по какому полю из нашей коллекции\n",
424 | " \"foreignField\": value - по какому полю из другой коллекции\n",
425 | " \"as\": name - как назвать колонку\n",
426 | "}} - делаем соединение\n",
427 | "```"
428 | ],
429 | "metadata": {
430 | "id": "OL_eQAMuzosI"
431 | }
432 | },
433 | {
434 | "cell_type": "markdown",
435 | "source": [
436 | "### Задание 7"
437 | ],
438 | "metadata": {
439 | "id": "3p88FrgL0cri"
440 | }
441 | },
442 | {
443 | "cell_type": "markdown",
444 | "source": [
445 | "Соедините две таблицы из базы данных sample_analytics (customers и accounts) и выведите для каждого пользователя все его аккаунты"
446 | ],
447 | "metadata": {
448 | "id": "uz8Aco9G0jJ0"
449 | }
450 | },
451 | {
452 | "cell_type": "code",
453 | "source": [
454 | "#YOUR CODE"
455 | ],
456 | "metadata": {
457 | "id": "YUraqI2v0tOt"
458 | },
459 | "execution_count": null,
460 | "outputs": []
461 | }
462 | ]
463 | }
--------------------------------------------------------------------------------
/week09/Python_2_Seminar_9_clean.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": []
7 | },
8 | "kernelspec": {
9 | "name": "python3",
10 | "display_name": "Python 3"
11 | },
12 | "language_info": {
13 | "name": "python"
14 | }
15 | },
16 | "cells": [
17 | {
18 | "cell_type": "markdown",
19 | "source": [
20 | "# Продвинутый Python, семинар 9\n",
21 | "\n",
22 | "**Лектор:** Петров Тимур\n",
23 | "\n",
24 | "**Семинаристы:** Петров Тимур, Коган Александра, Бузаев Федор, Дешеулин Олег\n",
25 | "\n",
26 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)"
27 | ],
28 | "metadata": {
29 | "id": "AbZhZkEGkvxx"
30 | }
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "source": [
35 | "## Telegram Bot API"
36 | ],
37 | "metadata": {
38 | "id": "WaGTHbgnk7r5"
39 | }
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "source": [
44 | "Итак, мы обсудили API, что это вообще такое. Давайте теперь на практике попрактикуемся с API бота в Telegram (достаточно многим нужен кастомный бот, но не умеют в него, хех)\n",
45 | "\n",
46 | "Конечно же мы не будем делать все это через GET реквесты etc, для этого тоже есть красивая библиотека [pytelegrambot](https://pytba.readthedocs.io/en/latest/index.html) и [aiogram](https://docs.aiogram.dev/en/dev-3.x/index.html), которая будет в домашке\n",
47 | "\n",
48 | "\n",
49 | "Отдельно еще [API](https://core.telegram.org/api#getting-started)"
50 | ],
51 | "metadata": {
52 | "id": "CGW2yB38lAHA"
53 | }
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": null,
58 | "metadata": {
59 | "colab": {
60 | "base_uri": "https://localhost:8080/"
61 | },
62 | "id": "VA28aB7XksQZ",
63 | "outputId": "6fbf05a1-006e-4193-bcb8-dbc42c4fe802"
64 | },
65 | "outputs": [
66 | {
67 | "output_type": "stream",
68 | "name": "stdout",
69 | "text": [
70 | "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n",
71 | "Collecting pytelegrambotapi\n",
72 | " Downloading pyTelegramBotAPI-4.7.0.tar.gz (210 kB)\n",
73 | "\u001b[K |████████████████████████████████| 210 kB 4.3 MB/s \n",
74 | "\u001b[?25hRequirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from pytelegrambotapi) (2.23.0)\n",
75 | "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->pytelegrambotapi) (1.24.3)\n",
76 | "Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->pytelegrambotapi) (3.0.4)\n",
77 | "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->pytelegrambotapi) (2022.9.24)\n",
78 | "Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->pytelegrambotapi) (2.10)\n",
79 | "Building wheels for collected packages: pytelegrambotapi\n",
80 | " Building wheel for pytelegrambotapi (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
81 | " Created wheel for pytelegrambotapi: filename=pyTelegramBotAPI-4.7.0-py3-none-any.whl size=192826 sha256=e5226c5058f88c092ff01614129b3601544d628b534621884ca4dc8d627b07ca\n",
82 | " Stored in directory: /root/.cache/pip/wheels/ba/13/da/8abf941f7cf9f993cde6118a56a7c24e12ed791507acd8ea39\n",
83 | "Successfully built pytelegrambotapi\n",
84 | "Installing collected packages: pytelegrambotapi\n",
85 | "Successfully installed pytelegrambotapi-4.7.0\n"
86 | ]
87 | }
88 | ],
89 | "source": [
90 | "!pip install pytelegrambotapi"
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "source": [
96 | "import telebot\n",
97 | "from telebot import types\n",
98 | "\n",
99 | "TOKEN = '5674479560:AAHI0lWyLHZQUa91Di-6NmNqdWbE7lL_6H8' # указываем токен нашего бота (для этого надо создать бота в @BotFather)\n",
100 | "# Создайте собственного бота, чтобы наши наработки друг друга не перебивали\n",
101 | "\n",
102 | "bot = telebot.TeleBot(TOKEN) # инициализируем нашего бота"
103 | ],
104 | "metadata": {
105 | "id": "iCMjtHHpl5Se"
106 | },
107 | "execution_count": null,
108 | "outputs": []
109 | },
110 | {
111 | "cell_type": "markdown",
112 | "source": [
113 | "Отлично, подключились к нашему боту! Теперь надо задавать его поведение, что делать, обработку сценариев\n",
114 | "\n",
115 | "Для начала приветствие, то есть то, что происходит при нажатии на кнопочку /start:"
116 | ],
117 | "metadata": {
118 | "id": "pYWzxPQynP6b"
119 | }
120 | },
121 | {
122 | "cell_type": "code",
123 | "source": [
124 | "@bot.message_handler(commands=['start'])\n",
125 | "def hello_message(message):\n",
126 | " bot.send_message(message.chat.id,\"Привет ✌️, тренируемся создавать ботов, давай с нами\")"
127 | ],
128 | "metadata": {
129 | "id": "9ZUeWXUAnQqV"
130 | },
131 | "execution_count": null,
132 | "outputs": []
133 | },
134 | {
135 | "cell_type": "code",
136 | "source": [
137 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота"
138 | ],
139 | "metadata": {
140 | "id": "K5T8FQVdnfzu"
141 | },
142 | "execution_count": null,
143 | "outputs": []
144 | },
145 | {
146 | "cell_type": "markdown",
147 | "source": [
148 | "Какие есть базовые функции у бота для коммуникации?\n",
149 | "\n",
150 | "* send_message - отправь сообщение\n",
151 | "\n",
152 | "* send_photo - отправь фотку\n",
153 | "\n",
154 | "* send_voice - отправь войс\n",
155 | "\n",
156 | "* send_poll - отправь голосовалку\n",
157 | "\n",
158 | "* send_sticker - отправь стикер"
159 | ],
160 | "metadata": {
161 | "id": "cPVzodCG0p2t"
162 | }
163 | },
164 | {
165 | "cell_type": "markdown",
166 | "source": [
167 | "Давайте теперь добавим кнопочку, на которую можно будет тыкать.\n",
168 | "\n",
169 | "Так как API у ботов достаточное богатое, то оно разбито по [частям](https://pytba.readthedocs.io/en/latest/types.html), каждая часть лежит в модуле types\n",
170 | "\n",
171 | "В данном случае нам понадобится 2 модуля: KeyBoardButton и ReplyKeyBoardMarkup (первый - это кнопочка, вторая - расположить кнопочку)"
172 | ],
173 | "metadata": {
174 | "id": "FR0apJPWy0SQ"
175 | }
176 | },
177 | {
178 | "cell_type": "code",
179 | "source": [
180 | "@bot.message_handler(commands=['button'])\n",
181 | "def button_message(message):\n",
182 | " markup = types.ReplyKeyboardMarkup(resize_keyboard=True)\n",
183 | " item_1 = types.KeyboardButton(\"Тык\")\n",
184 | " markup.add(item_1)\n",
185 | " bot.send_message(message.chat.id,'Выберите что вам надо',reply_markup=markup)"
186 | ],
187 | "metadata": {
188 | "id": "iHzLVCwszWGX"
189 | },
190 | "execution_count": null,
191 | "outputs": []
192 | },
193 | {
194 | "cell_type": "markdown",
195 | "source": [
196 | "Отлично, вот мы с вами сделали кнопку, она отправляет текст и... ничего не делает! Почему? Потому что мы не настроили сценарий. Давайте настроим"
197 | ],
198 | "metadata": {
199 | "id": "F9bKD9_G117h"
200 | }
201 | },
202 | {
203 | "cell_type": "code",
204 | "source": [
205 | "@bot.message_handler(content_types='text')\n",
206 | "def message_reply(message):\n",
207 | " if message.text==\"Тык\":\n",
208 | " bot.send_message(message.chat.id,\"Ссылка на наш github: https://github.com/Palladain/Deep_Python\")"
209 | ],
210 | "metadata": {
211 | "id": "C7c8Up8u1_Hc"
212 | },
213 | "execution_count": null,
214 | "outputs": []
215 | },
216 | {
217 | "cell_type": "code",
218 | "source": [
219 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота"
220 | ],
221 | "metadata": {
222 | "id": "9JcvGTZ4zoos"
223 | },
224 | "execution_count": null,
225 | "outputs": []
226 | },
227 | {
228 | "cell_type": "markdown",
229 | "source": [
230 | "Итак, магия работает, но откуда магия работает? Правильно, все дело в декораторах, которые мы указываем\n",
231 | "\n",
232 | "Пока мы с вами использовали только 1 такой декоратор:\n",
233 | "\n",
234 | "* bot.message_handler() - обработка сообщений\n",
235 | "\n",
236 | "В качестве аргумента мы передаем функцию, которая обрабатывает message, отправленный пользователем. Message в данном случае - это будет объект типа [Message](https://pytba.readthedocs.io/en/latest/types.html#telebot.types.Message), у которого есть несколько аргументов (самый базовый - text, то, что написал пользователь). Но в качестве аргументв можно передавать почти все, что угодно\n",
237 | "\n",
238 | "Например, кубик!"
239 | ],
240 | "metadata": {
241 | "id": "-yHrINDr2dgf"
242 | }
243 | },
244 | {
245 | "cell_type": "code",
246 | "source": [
247 | "@bot.message_handler(content_types='dice')\n",
248 | "def dice_reply(message):\n",
249 | " bot.send_message(message.dice.value)"
250 | ],
251 | "metadata": {
252 | "id": "8-GgtNhA5oZC"
253 | },
254 | "execution_count": null,
255 | "outputs": []
256 | },
257 | {
258 | "cell_type": "code",
259 | "source": [
260 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота"
261 | ],
262 | "metadata": {
263 | "id": "xvxXfJPD5pc8"
264 | },
265 | "execution_count": null,
266 | "outputs": []
267 | },
268 | {
269 | "cell_type": "markdown",
270 | "source": [
271 | "Давайте реализуем теперь нашего бота следующим образом:"
272 | ],
273 | "metadata": {
274 | "id": "aGcEEqHC5-_d"
275 | }
276 | },
277 | {
278 | "cell_type": "markdown",
279 | "source": [
280 | "### Задание 1\n",
281 | "\n",
282 | "Сделайте бот, который будет на старте выдавать две кнопки: одна с вашим любимым попугаем, вторая - с подкидыванием кубика. В зависимости от кубика мы тоже выводим рандомного попугая!"
283 | ],
284 | "metadata": {
285 | "id": "xnfPe5346DCW"
286 | }
287 | },
288 | {
289 | "cell_type": "code",
290 | "source": [
291 | "TOKEN = '5674479560:AAHI0lWyLHZQUa91Di-6NmNqdWbE7lL_6H8' # указываем токен нашего бота (для этого надо создать бота в @BotFather)\n",
292 | "# Создайте собственного бота, чтобы наши наработки друг друга не перебивали\n",
293 | "\n",
294 | "bot = telebot.TeleBot(TOKEN) # инициализируем нашего бота\n",
295 | "\n",
296 | "parrots = {1: 'https://cindygurmann.files.wordpress.com/2018/06/ea2530ad-e913-4d5b-8036-762b5b227c04.jpeg',\n",
297 | " 2: 'https://cherepah.ru/wp-content/uploads/2/2/8/228937ec782b8755993a3241e1d6c039.jpeg',\n",
298 | " 3: 'https://kotsobaka.com/wp-content/uploads/2018/08/2748131046_8a253489b5_b.jpg',\n",
299 | " 4: 'https://bestpopugai.ru/wp-content/uploads/2022/05/1-5.jpg',\n",
300 | " 5: 'https://i.artfile.ru/1920x1200_952300_[www.ArtFile.ru].jpg',\n",
301 | " 6: 'https://s1.1zoom.ru/big0/612/Parrots_Birds_Ara_(genus)_Hyacinth_macaw_Blue_570002_1280x853.jpg'}\n",
302 | "\n",
303 | "favourite_parrot = 'https://pet7.ru/wp-content/uploads/2017/09/Popugaj-zhako-osobennosti-vida.jpg'"
304 | ],
305 | "metadata": {
306 | "id": "FK11QstZ6dkw"
307 | },
308 | "execution_count": null,
309 | "outputs": []
310 | },
311 | {
312 | "cell_type": "markdown",
313 | "source": [
314 | "Окей, выглядит прикольно! Уже умеем как-то отвечать на запросы. Но что можно улучшить? Например, кнопочки (можно сделать в одной строке). Давайте сделаем"
315 | ],
316 | "metadata": {
317 | "id": "4RGMVqwv-Wno"
318 | }
319 | },
320 | {
321 | "cell_type": "code",
322 | "source": [
323 | "@bot.message_handler(commands=['start'])\n",
324 | "def hello_message(message):\n",
325 | " #\n",
326 | "\n",
327 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота"
328 | ],
329 | "metadata": {
330 | "id": "-UrvYZtL-tfE"
331 | },
332 | "execution_count": null,
333 | "outputs": []
334 | },
335 | {
336 | "cell_type": "markdown",
337 | "source": [
338 | "Чего не хватает? Правильно, дальнейших кнопок!\n",
339 | "\n",
340 | "Давайте добавим так, чтобы при выводе любимого попугая можно было отослать гифку с попугаем"
341 | ],
342 | "metadata": {
343 | "id": "8eLyoqKJBdbB"
344 | }
345 | },
346 | {
347 | "cell_type": "code",
348 | "source": [
349 | "@bot.message_handler(content_types=['text', 'emoji'])\n",
350 | "def message_reply(message):\n",
351 | " #"
352 | ],
353 | "metadata": {
354 | "id": "wVvj7dwwCG9N"
355 | },
356 | "execution_count": null,
357 | "outputs": []
358 | },
359 | {
360 | "cell_type": "markdown",
361 | "source": [
362 | "Что еще можем добавить? Ссылку на саму гифку внутри сообщения!\n",
363 | "\n",
364 | "Есть такая вещь, как InlineKeyboardButton - чтобы вывести кнопку в самом сообщении, другое дело, что она работает по-другому:\n",
365 | "\n",
366 | "* types.InlineKeyboardButton(text, url=None, callback_data=None, web_app=None, switch_inline_query=None, switch_inline_query_current_chat=None, callback_game=None, pay=None, login_url=None)\n",
367 | "\n",
368 | "Один из параметров (и только один) должен быть:\n",
369 | "\n",
370 | "* url - ссылка\n",
371 | "\n",
372 | "* callback_data - то, что отправляется назад при нажатии на кнопку\n",
373 | "\n",
374 | "* web_app - запуск app\n",
375 | "\n",
376 | "* switch_inline_query - вызов для выбора чатика, куда вставляется адрес бота и сообщение\n",
377 | "\n",
378 | "* switch_inline_query_current_chat - вызов внутри чата (скажем, полезно для встроенных ботов в чате)\n",
379 | "\n",
380 | "* callback_game - запуск игры\n",
381 | "\n",
382 | "* pay - кнопка оплаты (работает только для кнопок Invoice)"
383 | ],
384 | "metadata": {
385 | "id": "T1rI5B_xEam7"
386 | }
387 | },
388 | {
389 | "cell_type": "code",
390 | "source": [
391 | "TOKEN = '5674479560:AAHI0lWyLHZQUa91Di-6NmNqdWbE7lL_6H8' # указываем токен нашего бота (для этого надо создать бота в @BotFather)\n",
392 | "# Создайте собственного бота, чтобы наши наработки друг друга не перебивали\n",
393 | "\n",
394 | "bot = telebot.TeleBot(TOKEN) # инициализируем нашего бота\n",
395 | "\n",
396 | "parrots = {1: 'https://cindygurmann.files.wordpress.com/2018/06/ea2530ad-e913-4d5b-8036-762b5b227c04.jpeg',\n",
397 | " 2: 'https://cherepah.ru/wp-content/uploads/2/2/8/228937ec782b8755993a3241e1d6c039.jpeg',\n",
398 | " 3: 'https://kotsobaka.com/wp-content/uploads/2018/08/2748131046_8a253489b5_b.jpg',\n",
399 | " 4: 'https://bestpopugai.ru/wp-content/uploads/2022/05/1-5.jpg',\n",
400 | " 5: 'https://i.artfile.ru/1920x1200_952300_[www.ArtFile.ru].jpg',\n",
401 | " 6: 'https://s1.1zoom.ru/big0/612/Parrots_Birds_Ara_(genus)_Hyacinth_macaw_Blue_570002_1280x853.jpg'}\n",
402 | "\n",
403 | "favourite_parrot = 'https://pet7.ru/wp-content/uploads/2017/09/Popugaj-zhako-osobennosti-vida.jpg'\n",
404 | "\n",
405 | "parrot_gif = 'https://i1.wp.com/cdn.dribbble.com/users/104127/screenshots/2589080/parrots.gif'\n",
406 | "\n",
407 | "#\n",
408 | "\n",
409 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота"
410 | ],
411 | "metadata": {
412 | "id": "FRnY6R62CyV4"
413 | },
414 | "execution_count": null,
415 | "outputs": []
416 | },
417 | {
418 | "cell_type": "markdown",
419 | "source": [
420 | "Давайте добавим еще кнопку, с помощью которой можно автоматически переслать ссылку на попуга!"
421 | ],
422 | "metadata": {
423 | "id": "4RcuS62DHJKs"
424 | }
425 | },
426 | {
427 | "cell_type": "code",
428 | "source": [
429 | "TOKEN = '5674479560:AAHI0lWyLHZQUa91Di-6NmNqdWbE7lL_6H8' # указываем токен нашего бота (для этого надо создать бота в @BotFather)\n",
430 | "# Создайте собственного бота, чтобы наши наработки друг друга не перебивали\n",
431 | "\n",
432 | "bot = telebot.TeleBot(TOKEN) # инициализируем нашего бота\n",
433 | "\n",
434 | "parrots = {1: 'https://cindygurmann.files.wordpress.com/2018/06/ea2530ad-e913-4d5b-8036-762b5b227c04.jpeg',\n",
435 | " 2: 'https://cherepah.ru/wp-content/uploads/2/2/8/228937ec782b8755993a3241e1d6c039.jpeg',\n",
436 | " 3: 'https://kotsobaka.com/wp-content/uploads/2018/08/2748131046_8a253489b5_b.jpg',\n",
437 | " 4: 'https://bestpopugai.ru/wp-content/uploads/2022/05/1-5.jpg',\n",
438 | " 5: 'https://i.artfile.ru/1920x1200_952300_[www.ArtFile.ru].jpg',\n",
439 | " 6: 'https://kipmu.ru/wp-content/uploads/pchppgks.jpg'}\n",
440 | "\n",
441 | "favourite_parrot = 'https://pet7.ru/wp-content/uploads/2017/09/Popugaj-zhako-osobennosti-vida.jpg'\n",
442 | "\n",
443 | "parrot_gif = 'https://i1.wp.com/cdn.dribbble.com/users/104127/screenshots/2589080/parrots.gif'\n",
444 | "\n",
445 | "#\n",
446 | "\n",
447 | "\n",
448 | "\n",
449 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота"
450 | ],
451 | "metadata": {
452 | "id": "V8Eznr-EHNDn"
453 | },
454 | "execution_count": null,
455 | "outputs": []
456 | },
457 | {
458 | "cell_type": "markdown",
459 | "source": [
460 | "Давайте добавим еще функциональности, чтобы его можно было вызывать из строки!"
461 | ],
462 | "metadata": {
463 | "id": "BNJDhuOHvbpI"
464 | }
465 | },
466 | {
467 | "cell_type": "code",
468 | "source": [
469 | "from random import seed, randrange\n",
470 | "from time import time\n",
471 | "\n",
472 | "@bot.inline_handler(func=lambda query: len(query.query) > 0)\n",
473 | "def query_text(query):\n",
474 | " try:\n",
475 | " seed(int(time()))\n",
476 | " size = randrange(1, 100)\n",
477 | " print(size)\n",
478 | " r_sum = types.InlineQueryResultArticle(\n",
479 | " id='1', title=\"Папуг!\",\n",
480 | " input_message_content = types.InputTextMessageContent(message_text=\"Ваш папуг \" + str(size) + \" размера\" )\n",
481 | " )\n",
482 | " bot.answer_inline_query(query.id, [r_sum])\n",
483 | " except Exception as e:\n",
484 | " print(\"{!s}\\n{!s}\".format(type(e), str(e)))"
485 | ],
486 | "metadata": {
487 | "id": "df3rW8rdvfsU"
488 | },
489 | "execution_count": null,
490 | "outputs": []
491 | },
492 | {
493 | "cell_type": "markdown",
494 | "source": [
495 | "Ну хорошо, умеем отвечать, делать кнопочки, даже открывать ссылки и что-то пересылать. Что еще нужно для всей красоты?\n",
496 | "\n",
497 | "Ну вот как видите, у нас бот работает только тогда, когда мы тут запускаем всю работу, а больше мы ничего не можем делать :с\n",
498 | "\n",
499 | "Давайте его закинем в контейнер dockerа, чтобы можно было его запустить на фоне и жить счастливо!\n"
500 | ],
501 | "metadata": {
502 | "id": "y-g48t5xH5ii"
503 | }
504 | },
505 | {
506 | "cell_type": "markdown",
507 | "source": [
508 | "### Делаем бота сильным и независимым"
509 | ],
510 | "metadata": {
511 | "id": "DUV2mVoEJkGk"
512 | }
513 | },
514 | {
515 | "cell_type": "code",
516 | "source": [
517 | "%%writefile bot.py"
518 | ],
519 | "metadata": {
520 | "colab": {
521 | "base_uri": "https://localhost:8080/"
522 | },
523 | "id": "BwpoeQWFJlje",
524 | "outputId": "4997b1c1-67e2-4f50-a300-14f85aeb37d6"
525 | },
526 | "execution_count": null,
527 | "outputs": [
528 | {
529 | "output_type": "stream",
530 | "name": "stdout",
531 | "text": [
532 | "Writing bot.py\n"
533 | ]
534 | }
535 | ]
536 | },
537 | {
538 | "cell_type": "markdown",
539 | "source": [
540 | "Вспоминаем, как работать с докером:"
541 | ],
542 | "metadata": {
543 | "id": "Vo4Sv_JdMXHG"
544 | }
545 | },
546 | {
547 | "cell_type": "code",
548 | "source": [
549 | "docker pull python:3-onbuild\n",
550 | "\n",
551 | "vim Dockerfile"
552 | ],
553 | "metadata": {
554 | "id": "mZ8FSyy_MaDo"
555 | },
556 | "execution_count": null,
557 | "outputs": []
558 | },
559 | {
560 | "cell_type": "code",
561 | "source": [
562 | "FROM python:3-onbuild #указываем образ, который надо использовать\n",
563 | "\n",
564 | "#благодаря тому, что у нас onbuild, нам не надо копировать файлы и устанавливать зависимости здесь\n",
565 | "\n",
566 | "EXPOSE 8888 #говорим, на какой порт это все отправлять\n",
567 | "\n",
568 | "CMD [\"python\", \"bot.py\"] # команды для запуска (то есть что надо сделать)"
569 | ],
570 | "metadata": {
571 | "id": "93MA-dsKNZYn"
572 | },
573 | "execution_count": null,
574 | "outputs": []
575 | },
576 | {
577 | "cell_type": "code",
578 | "source": [
579 | "docker build -t palladain7/catgif . #здесь надо зарегаться на Docker Hub (это быстро) и в качестве user ввести свой ник\n",
580 | "docker run -d -P -p 8888:8888 palladain/catgif #собрали-запустили\n",
581 | "docker images #проверяем образ"
582 | ],
583 | "metadata": {
584 | "id": "akWgl2w3NcmW"
585 | },
586 | "execution_count": null,
587 | "outputs": []
588 | }
589 | ]
590 | }
--------------------------------------------------------------------------------
/week11/Aliexpress.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week11/Aliexpress.zip
--------------------------------------------------------------------------------
/week11/Aliexpress/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week11/Aliexpress/__init__.py
--------------------------------------------------------------------------------
/week11/Aliexpress/items.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Palladain/Deep_Python_2023/24ca5a6a75380abd81edc82b027386965550ba1c/week11/Aliexpress/items.json
--------------------------------------------------------------------------------
/week11/Aliexpress/items.py:
--------------------------------------------------------------------------------
1 | # Define here the models for your scraped items
2 | #
3 | # See documentation in:
4 | # https://docs.scrapy.org/en/latest/topics/items.html
5 |
6 | import scrapy
7 |
8 |
9 | class AliexpressItem(scrapy.Item):
10 | name = scrapy.Field()
11 | category = scrapy.Field()
12 | price = scrapy.Field()
13 | sale_price = scrapy.Field()
14 | delivery = scrapy.Field()
15 | rating = scrapy.Field()
16 |
--------------------------------------------------------------------------------
/week11/Aliexpress/middlewares.py:
--------------------------------------------------------------------------------
1 | # Define here the models for your spider middleware
2 | #
3 | # See documentation in:
4 | # https://docs.scrapy.org/en/latest/topics/spider-middleware.html
5 |
6 | from scrapy import signals
7 |
8 | # useful for handling different item types with a single interface
9 | from itemadapter import is_item, ItemAdapter
10 |
11 |
12 | class AliexpressSpiderMiddleware:
13 | # Not all methods need to be defined. If a method is not defined,
14 | # scrapy acts as if the spider middleware does not modify the
15 | # passed objects.
16 |
17 | @classmethod
18 | def from_crawler(cls, crawler):
19 | # This method is used by Scrapy to create your spiders.
20 | s = cls()
21 | crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
22 | return s
23 |
24 | def process_spider_input(self, response, spider):
25 | # Called for each response that goes through the spider
26 | # middleware and into the spider.
27 |
28 | # Should return None or raise an exception.
29 | return None
30 |
31 | def process_spider_output(self, response, result, spider):
32 | # Called with the results returned from the Spider, after
33 | # it has processed the response.
34 |
35 | # Must return an iterable of Request, or item objects.
36 | for i in result:
37 | yield i
38 |
39 | def process_spider_exception(self, response, exception, spider):
40 | # Called when a spider or process_spider_input() method
41 | # (from other spider middleware) raises an exception.
42 |
43 | # Should return either None or an iterable of Request or item objects.
44 | pass
45 |
46 | def process_start_requests(self, start_requests, spider):
47 | # Called with the start requests of the spider, and works
48 | # similarly to the process_spider_output() method, except
49 | # that it doesn’t have a response associated.
50 |
51 | # Must return only requests (not items).
52 | for r in start_requests:
53 | yield r
54 |
55 | def spider_opened(self, spider):
56 | spider.logger.info('Spider opened: %s' % spider.name)
57 |
58 |
59 | class AliexpressDownloaderMiddleware:
60 | # Not all methods need to be defined. If a method is not defined,
61 | # scrapy acts as if the downloader middleware does not modify the
62 | # passed objects.
63 |
64 | @classmethod
65 | def from_crawler(cls, crawler):
66 | # This method is used by Scrapy to create your spiders.
67 | s = cls()
68 | crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
69 | return s
70 |
71 | def process_request(self, request, spider):
72 | # Called for each request that goes through the downloader
73 | # middleware.
74 |
75 | # Must either:
76 | # - return None: continue processing this request
77 | # - or return a Response object
78 | # - or return a Request object
79 | # - or raise IgnoreRequest: process_exception() methods of
80 | # installed downloader middleware will be called
81 | return None
82 |
83 | def process_response(self, request, response, spider):
84 | # Called with the response returned from the downloader.
85 |
86 | # Must either;
87 | # - return a Response object
88 | # - return a Request object
89 | # - or raise IgnoreRequest
90 | return response
91 |
92 | def process_exception(self, request, exception, spider):
93 | # Called when a download handler or a process_request()
94 | # (from other downloader middleware) raises an exception.
95 |
96 | # Must either:
97 | # - return None: continue processing this exception
98 | # - return a Response object: stops process_exception() chain
99 | # - return a Request object: stops process_exception() chain
100 | pass
101 |
102 | def spider_opened(self, spider):
103 | spider.logger.info('Spider opened: %s' % spider.name)
104 |
--------------------------------------------------------------------------------
/week11/Aliexpress/pipelines.py:
--------------------------------------------------------------------------------
1 | # Define your item pipelines here
2 | #
3 | # Don't forget to add your pipeline to the ITEM_PIPELINES setting
4 | # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
5 |
6 |
7 | # useful for handling different item types with a single interface
8 | from itemadapter import ItemAdapter
9 | import json
10 |
11 |
12 | class AliexpressPipeline:
13 |
14 | def open_spider(self, spider):
15 | self.file = open("items.json", "w")
16 |
17 | def close_spider(self, spider):
18 | self.file.close()
19 |
20 | def process_item(self, item, spider):
21 | if item["rating"] >= 4.7:
22 | line = json.dumps(ItemAdapter(item).asdict()) + '\n'
23 | self.file.write(line)
24 | return item
25 |
--------------------------------------------------------------------------------
/week11/Aliexpress/settings.py:
--------------------------------------------------------------------------------
1 | # Scrapy settings for Aliexpress project
2 | #
3 | # For simplicity, this file contains only settings considered important or
4 | # commonly used. You can find more settings consulting the documentation:
5 | #
6 | # https://docs.scrapy.org/en/latest/topics/settings.html
7 | # https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
8 | # https://docs.scrapy.org/en/latest/topics/spider-middleware.html
9 |
10 | BOT_NAME = 'Aliexpress'
11 |
12 | SPIDER_MODULES = ['Aliexpress.spiders']
13 | NEWSPIDER_MODULE = 'Aliexpress.spiders'
14 |
15 |
16 | # Crawl responsibly by identifying yourself (and your website) on the user-agent
17 | #USER_AGENT = 'Aliexpress (+http://www.yourdomain.com)'
18 |
19 | # Obey robots.txt rules
20 | ROBOTSTXT_OBEY = True
21 |
22 | # Configure maximum concurrent requests performed by Scrapy (default: 16)
23 | #CONCURRENT_REQUESTS = 32
24 |
25 | # Configure a delay for requests for the same website (default: 0)
26 | # See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
27 | # See also autothrottle settings and docs
28 | #DOWNLOAD_DELAY = 3
29 | # The download delay setting will honor only one of:
30 | #CONCURRENT_REQUESTS_PER_DOMAIN = 16
31 | #CONCURRENT_REQUESTS_PER_IP = 16
32 |
33 | # Disable cookies (enabled by default)
34 | #COOKIES_ENABLED = False
35 |
36 | # Disable Telnet Console (enabled by default)
37 | #TELNETCONSOLE_ENABLED = False
38 |
39 | # Override the default request headers:
40 | #DEFAULT_REQUEST_HEADERS = {
41 | # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
42 | # 'Accept-Language': 'en',
43 | #}
44 |
45 | # Enable or disable spider middlewares
46 | # See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
47 | #SPIDER_MIDDLEWARES = {
48 | # 'Aliexpress.middlewares.AliexpressSpiderMiddleware': 543,
49 | #}
50 |
51 | # Enable or disable downloader middlewares
52 | # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
53 | #DOWNLOADER_MIDDLEWARES = {
54 | # 'Aliexpress.middlewares.AliexpressDownloaderMiddleware': 543,
55 | #}
56 |
57 | # Enable or disable extensions
58 | # See https://docs.scrapy.org/en/latest/topics/extensions.html
59 | #EXTENSIONS = {
60 | # 'scrapy.extensions.telnet.TelnetConsole': None,
61 | #}
62 |
63 | # Configure item pipelines
64 | # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
65 | ITEM_PIPELINES = {
66 | 'Aliexpress.pipelines.AliexpressPipeline': 300,
67 | }
68 |
69 | # Enable and configure the AutoThrottle extension (disabled by default)
70 | # See https://docs.scrapy.org/en/latest/topics/autothrottle.html
71 | #AUTOTHROTTLE_ENABLED = True
72 | # The initial download delay
73 | #AUTOTHROTTLE_START_DELAY = 5
74 | # The maximum download delay to be set in case of high latencies
75 | #AUTOTHROTTLE_MAX_DELAY = 60
76 | # The average number of requests Scrapy should be sending in parallel to
77 | # each remote server
78 | #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
79 | # Enable showing throttling stats for every response received:
80 | #AUTOTHROTTLE_DEBUG = False
81 |
82 | # Enable and configure HTTP caching (disabled by default)
83 | # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
84 | #HTTPCACHE_ENABLED = True
85 | #HTTPCACHE_EXPIRATION_SECS = 0
86 | #HTTPCACHE_DIR = 'httpcache'
87 | #HTTPCACHE_IGNORE_HTTP_CODES = []
88 | #HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
89 |
90 | # Set settings whose default value is deprecated to a future-proof value
91 | REQUEST_FINGERPRINTER_IMPLEMENTATION = '2.7'
92 | TWISTED_REACTOR = 'twisted.internet.asyncioreactor.AsyncioSelectorReactor'
93 |
--------------------------------------------------------------------------------
/week11/Aliexpress/spiders/AliexpressSpider.py:
--------------------------------------------------------------------------------
1 | import scrapy
2 | from urllib.parse import urlencode
3 | from urllib.parse import urlparse
4 | from urllib.parse import urljoin
5 | import re
6 | import json
7 | from Aliexpress.items import AliexpressItem
8 |
9 | queries = ['Видеокарта']
10 | API = ''
11 |
12 |
13 | def get_url(url):
14 | payload = {'api_key': API, 'url': url}
15 | proxy_url = 'http://api.scraperapi.com/?' + urlencode(payload)
16 | return proxy_url
17 |
18 |
19 | class AliexpressSpider(scrapy.Spider):
20 | name = 'AliexpressSpider'
21 | page = 1
22 |
23 | def start_requests(self):
24 | for query in queries:
25 | url = 'https://aliexpress.ru/wholesale?' + urlencode({'g': 'n', 'SearchText': query, 'page': str(self.page)})
26 | yield scrapy.Request(url=get_url(url), callback=self.parse_keyword_response)
27 |
28 | def parse_keyword_response(self, response):
29 | products = set()
30 | for res in response.xpath('//a[contains(@target, _target)]/@href').extract():
31 | if 'sku_id' in res:
32 | products.add(res)
33 |
34 | for product in products:
35 | product_url = 'https://aliexpress.ru' + product
36 | yield scrapy.Request(url=get_url(product_url), callback=self.parse_product_page)
37 |
38 | self.page += 1
39 | if self.page <= 1:
40 | url = urlparse(response.url[-1]) + str(self.page)
41 | yield scrapy.Request(url=get_url(url), callback=self.parse_keyword_response)
42 |
43 | def parse_product_page(self, response):
44 | item = AliexpressItem()
45 | title = response.xpath('//h1/text()').extract()
46 | category = response.xpath('//ol[contains(@class, SnowBreadcrumbs_SnowBreadcrumbs__list__1xzrg)]/li/a/text()').extract()
47 | price = response.xpath('//div[contains(@class, snow-price_SnowPrice__secondPrice__18x8np)][1]/text()').extract()
48 | sale_price = response.xpath('//div[contains(@class, snow-price_SnowPrice__mainM__18x8np)][1]/text()').extract()
49 | delivery = response.xpath('//div[contains(@class, SnowProductDelivery_SnowProductDelivery__item__y5v67)[0]/span[1]/text()').extract()
50 | rating = response.xpath('//p[contains(@class, SnowReviews_ProductRating__ratingAverage__17pz0)[0]/text()').extract()
51 | item["title"] = ''.join(title).strip()
52 | item["category"] = '/'.join(category).strip()
53 | item["price"] = ''.join(price).strip()
54 | item["sale_price"] = ''.join(sale_price).strip()
55 | item["delivery"] = ''.join(delivery).strip()
56 | item["rating"] = ''.join(rating).strip()
57 | yield item
58 |
59 |
--------------------------------------------------------------------------------
/week11/Aliexpress/spiders/__init__.py:
--------------------------------------------------------------------------------
1 | # This package will contain the spiders of your Scrapy project
2 | #
3 | # Please refer to the documentation for information on how to create and manage
4 | # your spiders.
5 |
--------------------------------------------------------------------------------
/week11/Python_2_Seminar_11.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": []
7 | },
8 | "kernelspec": {
9 | "name": "python3",
10 | "display_name": "Python 3"
11 | },
12 | "language_info": {
13 | "name": "python"
14 | }
15 | },
16 | "cells": [
17 | {
18 | "cell_type": "markdown",
19 | "source": [
20 | "# Продвинутый Python, семинар 11\n",
21 | "\n",
22 | "**Лектор:** Петров Тимур\n",
23 | "\n",
24 | "**Семинаристы:** Петров Тимур, Коган Александра, Бузаев Федор, Дешеулин Олег\n",
25 | "\n",
26 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)"
27 | ],
28 | "metadata": {
29 | "id": "5EWo4vpdHvHb"
30 | }
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "source": [
35 | "## Углубляемся в парсинг"
36 | ],
37 | "metadata": {
38 | "id": "VuWlixt0Hxn9"
39 | }
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "source": [
44 | "Научились с вами парсить странички, получать товар и даже обходить блокировку роботов, которым ничего нельзя. Что же, давайте добавим динамики\n",
45 | "\n",
46 | "Если в прошлый раз мы с вами просто брали и получали результат по заданным страничкам, то теперь хотим брать страницу, из нее забирать ссылки, ходить по ним и также получать информацию о товаре. Таким образом получим реального паука"
47 | ],
48 | "metadata": {
49 | "id": "KnV-0Z1cH0Wt"
50 | }
51 | },
52 | {
53 | "cell_type": "code",
54 | "execution_count": null,
55 | "metadata": {
56 | "id": "sGvu0jrRHr8_"
57 | },
58 | "outputs": [],
59 | "source": [
60 | "!pip install scrapy"
61 | ]
62 | },
63 | {
64 | "cell_type": "markdown",
65 | "source": [
66 | "Сегодня в качестве нашей жертвы выберем другой маркетплейс, а именно [Алиэкспресс](https://aliexpress.ru)\n",
67 | "\n",
68 | "И сформулируем задачу так:\n",
69 | "\n",
70 | "Хотим взять все заказы по запросу \"Видеокарта\" (вот такие мы маленькие любители помайнить, видимо)"
71 | ],
72 | "metadata": {
73 | "id": "40R2-LA_IT8S"
74 | }
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "source": [
79 | "Перезходим по ссылке: https://aliexpress.ru/wholesale?SearchText=Видеокарта&g=n&page=1\n",
80 | "\n",
81 | "Ииии... тут нет никаких страниц! Есть кнопка загрузить еще и он не делает новую страницу, он просто догружает, что же делать?\n",
82 | "\n",
83 | "Ответ: внимательнее посмотреть на url-ссылку!"
84 | ],
85 | "metadata": {
86 | "id": "heh_WoC5I5Ze"
87 | }
88 | },
89 | {
90 | "cell_type": "markdown",
91 | "source": [
92 | "## URL-ссылки и как они работают"
93 | ],
94 | "metadata": {
95 | "id": "j1TXOKiYJFuf"
96 | }
97 | },
98 | {
99 | "cell_type": "markdown",
100 | "source": [
101 | "URL (Uniform Resource Locator) - это адрес всему, что выложено в интернетах.\n",
102 | "\n",
103 | "Сам по себе URL состоит из нескольких частей:\n",
104 | "\n",
105 | "* Протокол - какой протокол должен использовать запрос. Самый частый пример: ```https//:```, но бывают и другие, например, ```mailto:```, открывающий почтовый клиент. В нашем случае все банально и просто\n",
106 | "\n",
107 | "* Domain - полное доменное имя (пример: ```example.com```), то есть к какому веб ресурсу необходимо подключиться\n",
108 | "\n",
109 | "Обратите внимание: ```vk.com``` и ```m.vk.com``` - в чем разница? В уровнях домена (m - это дополнительный уровень, который вас отсылает именно к мобильной версии сайта, а тот же com - это доменная зона)\n",
110 | "\n",
111 | "* Port - технический параметр (порт для доступа к веб-ресурсу, пример: ```:443```), обычно никто ничего не ставит, потому что он устанваливается по дефолту (80 для HTTP и 443 для HTTPS)\n",
112 | "\n",
113 | "* Parameters - параметры для передачи ресурсу. Каждый сервер их обрабатывает по-своему, в зависимости от того, как владелец это обрабатывает. Как они выглядят:\n",
114 | "\n",
115 | "```\n",
116 | "?key1=value1&key_2=value_2 - ? - начинаются параметры, через & идет перечисление\n",
117 | "```\n",
118 | "\n",
119 | "* Anchor - якорь, который вас отсылает к какой-то конкретной части (например, к какой-то плашке уже на странице etc), пример ```#anchor```\n",
120 | "\n",
121 | "* Path - может быть просто путь к файлу (```a/b/c```)"
122 | ],
123 | "metadata": {
124 | "id": "tKcrkJVNKG9o"
125 | }
126 | },
127 | {
128 | "cell_type": "markdown",
129 | "source": [
130 | "Имея вот такие знания, давайте обратимся к адресу:\n",
131 | "\n",
132 | "```\n",
133 | "https://aliexpress.ru/wholesale?SearchText=Видеокарта&g=n&page=1\n",
134 | "\n",
135 | "Параметры:\n",
136 | "\n",
137 | "SearchText=Видеокарта - наш поисковый запрос\n",
138 | "g = n - ???\n",
139 | "page = 1 - страница\n",
140 | "```\n",
141 | "\n",
142 | "Давайте тестировать, что за g. Посмотрели и ничего не увидели, ну ладно\n",
143 | "\n",
144 | "Но что увидели? Что изменение page показывает только нужную страницу, а не только с самого начала, ура, значит можно парсить через изменение данного параметра"
145 | ],
146 | "metadata": {
147 | "id": "wx-lQw3COAiL"
148 | }
149 | },
150 | {
151 | "cell_type": "markdown",
152 | "source": [
153 | "## Смотрим, как перейти и как вытащить информацию"
154 | ],
155 | "metadata": {
156 | "id": "3znUqGGRPZ1T"
157 | }
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "source": [
162 | "Открываем и видмм более красивую и приятную картинку, чем в Amazon, кто-то постарался на славу"
163 | ],
164 | "metadata": {
165 | "id": "tiM6uWsZPeHv"
166 | }
167 | },
168 | {
169 | "cell_type": "code",
170 | "source": [
171 | "!wget https://github.com/Palladain/Deep_Python/raw/main/Seminars/Seminars_8/Aliexpress.txt\n",
172 | "!wget https://github.com/Palladain/Deep_Python/raw/main/Seminars/Seminars_8/Aliexpress_item.txt"
173 | ],
174 | "metadata": {
175 | "id": "kWDetM2rOLM6"
176 | },
177 | "execution_count": null,
178 | "outputs": []
179 | },
180 | {
181 | "cell_type": "markdown",
182 | "source": [
183 | "### Задание 1\n",
184 | "\n",
185 | "Имея страничку поиска (Aliexpress.txt) или с помощью навигации по сайту вычлените все ссылки на на карточки товаров"
186 | ],
187 | "metadata": {
188 | "id": "8_J622-qQsVQ"
189 | }
190 | },
191 | {
192 | "cell_type": "code",
193 | "source": [
194 | "from bs4 import BeautifulSoup\n",
195 | "\n",
196 | "s = \"\"\n",
197 | "with open(\"Aliexpress.txt\", 'r') as f:\n",
198 | " s = f.read()\n",
199 | "soup = BeautifulSoup(s, 'html.parser') # указываем парсер\n",
200 | "print(soup.prettify()) # выглядит уже более структурно"
201 | ],
202 | "metadata": {
203 | "id": "onmj9SLXQ1_Z"
204 | },
205 | "execution_count": null,
206 | "outputs": []
207 | },
208 | {
209 | "cell_type": "markdown",
210 | "source": [
211 | "### Задание 2\n",
212 | "\n",
213 | "Имея страницу (Aliexpress_item.txt) вычлените следующую информацию о товаре:\n",
214 | "\n",
215 | "* Название\n",
216 | "\n",
217 | "* Категорию\n",
218 | "\n",
219 | "* Цену\n",
220 | "\n",
221 | "* Цену без скидки (если есть скидка)\n",
222 | "\n",
223 | "* Стоимость доставки\n",
224 | "\n",
225 | "* Средний отзыв"
226 | ],
227 | "metadata": {
228 | "id": "RbGzgh-rSxJO"
229 | }
230 | },
231 | {
232 | "cell_type": "code",
233 | "source": [
234 | "from bs4 import BeautifulSoup\n",
235 | "\n",
236 | "s = \"\"\n",
237 | "with open(\"Aliexpress_item.txt\", 'r') as f:\n",
238 | " s = f.read()\n",
239 | "soup = BeautifulSoup(s, 'html.parser') # указываем парсер\n",
240 | "print(soup.prettify()) # выглядит уже более структурно"
241 | ],
242 | "metadata": {
243 | "id": "mK1jsmh6TRpD"
244 | },
245 | "execution_count": null,
246 | "outputs": []
247 | },
248 | {
249 | "cell_type": "markdown",
250 | "source": [
251 | "Название:"
252 | ],
253 | "metadata": {
254 | "id": "VH2O8Q9RVjb-"
255 | }
256 | },
257 | {
258 | "cell_type": "code",
259 | "source": [],
260 | "metadata": {
261 | "id": "NIjcVTIqVQOn"
262 | },
263 | "execution_count": null,
264 | "outputs": []
265 | },
266 | {
267 | "cell_type": "markdown",
268 | "source": [
269 | "Категория:"
270 | ],
271 | "metadata": {
272 | "id": "KuiCJf4sVk7r"
273 | }
274 | },
275 | {
276 | "cell_type": "code",
277 | "source": [],
278 | "metadata": {
279 | "id": "0kvibbIrVntI"
280 | },
281 | "execution_count": null,
282 | "outputs": []
283 | },
284 | {
285 | "cell_type": "markdown",
286 | "source": [
287 | "Цена:"
288 | ],
289 | "metadata": {
290 | "id": "aNUZHfVzWIAP"
291 | }
292 | },
293 | {
294 | "cell_type": "code",
295 | "source": [],
296 | "metadata": {
297 | "id": "beUfrtLOWKqM"
298 | },
299 | "execution_count": null,
300 | "outputs": []
301 | },
302 | {
303 | "cell_type": "markdown",
304 | "source": [
305 | "Цена без скидки:"
306 | ],
307 | "metadata": {
308 | "id": "kk05CEl0WhXm"
309 | }
310 | },
311 | {
312 | "cell_type": "code",
313 | "source": [],
314 | "metadata": {
315 | "id": "A-81kKALWkjZ"
316 | },
317 | "execution_count": null,
318 | "outputs": []
319 | },
320 | {
321 | "cell_type": "markdown",
322 | "source": [
323 | "Стоимость доставки:"
324 | ],
325 | "metadata": {
326 | "id": "Sq59ASpFWwfZ"
327 | }
328 | },
329 | {
330 | "cell_type": "code",
331 | "source": [],
332 | "metadata": {
333 | "id": "l156_dgdW0PK"
334 | },
335 | "execution_count": null,
336 | "outputs": []
337 | },
338 | {
339 | "cell_type": "markdown",
340 | "source": [
341 | "Средний отзыв (посмотрите по коду, почему ничего не получилось):"
342 | ],
343 | "metadata": {
344 | "id": "WTOXiiTMWyQ7"
345 | }
346 | },
347 | {
348 | "cell_type": "code",
349 | "source": [],
350 | "metadata": {
351 | "id": "AbDa8Y1MW0oL"
352 | },
353 | "execution_count": null,
354 | "outputs": []
355 | },
356 | {
357 | "cell_type": "markdown",
358 | "source": [
359 | "### Задание 3"
360 | ],
361 | "metadata": {
362 | "id": "LB0szNstY_lh"
363 | }
364 | },
365 | {
366 | "cell_type": "markdown",
367 | "source": [
368 | "Выведите все данные с помощью XPath"
369 | ],
370 | "metadata": {
371 | "id": "Di9CYTQ1ZBtw"
372 | }
373 | },
374 | {
375 | "cell_type": "code",
376 | "source": [
377 | "from lxml import etree\n",
378 | "\n",
379 | "dom = etree.HTML(str(soup))"
380 | ],
381 | "metadata": {
382 | "id": "q92LUkAIZGB8"
383 | },
384 | "execution_count": null,
385 | "outputs": []
386 | },
387 | {
388 | "cell_type": "code",
389 | "source": [],
390 | "metadata": {
391 | "id": "tP4TLnkGZNQC"
392 | },
393 | "execution_count": null,
394 | "outputs": []
395 | },
396 | {
397 | "cell_type": "markdown",
398 | "source": [
399 | "Отлично, мы знаем как парсить! Дело осталось за малым - подготовить нашего паука и научить его ходить по ссылкам"
400 | ],
401 | "metadata": {
402 | "id": "RwMKiMdTTSU9"
403 | }
404 | },
405 | {
406 | "cell_type": "markdown",
407 | "source": [
408 | "## Готовим паука"
409 | ],
410 | "metadata": {
411 | "id": "T3FYkiZ7Tajl"
412 | }
413 | },
414 | {
415 | "cell_type": "markdown",
416 | "source": [
417 | "На самом деле структура для такого не слишком сложная:\n",
418 | "\n",
419 | "(1) Пишем код для начала парсинга (ввести нужный запрос и пройти по нему) - первая функция\n",
420 | "\n",
421 | "(1) Пишем код для парсинга страницы поиска - вторая функция\n",
422 | "\n",
423 | "(2) Получив ссылки, пишем код для парсинга товара, который будет возвращать ответы - третья функция\n",
424 | "\n",
425 | "Ну в целом все..."
426 | ],
427 | "metadata": {
428 | "id": "VNwjYS25l415"
429 | }
430 | },
431 | {
432 | "cell_type": "markdown",
433 | "source": [
434 | "А теперь давайте писать павука!"
435 | ],
436 | "metadata": {
437 | "id": "glE0renQmk_A"
438 | }
439 | },
440 | {
441 | "cell_type": "markdown",
442 | "source": [
443 | "### Задание 3"
444 | ],
445 | "metadata": {
446 | "id": "a9GkOBsumm-5"
447 | }
448 | },
449 | {
450 | "cell_type": "markdown",
451 | "source": [
452 | "Запишите в items.py класс для товаров"
453 | ],
454 | "metadata": {
455 | "id": "CnrkZ4oOmtoo"
456 | }
457 | },
458 | {
459 | "cell_type": "markdown",
460 | "source": [
461 | "### Задание 4"
462 | ],
463 | "metadata": {
464 | "id": "nKJjJVzamrdN"
465 | }
466 | },
467 | {
468 | "cell_type": "markdown",
469 | "source": [
470 | "Запишите в pipeline.py функцию по обработке данных:\n",
471 | "\n",
472 | "(1) Все данные сохраняем в csv\n",
473 | "\n",
474 | "(2) Берем только товары с оценкой выше 4.7"
475 | ],
476 | "metadata": {
477 | "id": "0ZEACDJsmynN"
478 | }
479 | },
480 | {
481 | "cell_type": "markdown",
482 | "source": [
483 | "### Задание 5"
484 | ],
485 | "metadata": {
486 | "id": "Va_DsHTpnAJN"
487 | }
488 | },
489 | {
490 | "cell_type": "markdown",
491 | "source": [
492 | "Поправьте settings, чтобы включить pipeline"
493 | ],
494 | "metadata": {
495 | "id": "JVNOr6S1nJqZ"
496 | }
497 | },
498 | {
499 | "cell_type": "markdown",
500 | "source": [
501 | "### Задание 6"
502 | ],
503 | "metadata": {
504 | "id": "KbEdhUOznYLB"
505 | }
506 | },
507 | {
508 | "cell_type": "markdown",
509 | "source": [
510 | "Напишите паука, который будет автоматически ходить по запросу в товары и забирать оттуда данные"
511 | ],
512 | "metadata": {
513 | "id": "g52TV5NwnbPT"
514 | }
515 | },
516 | {
517 | "cell_type": "code",
518 | "source": [
519 | "import scrapy\n",
520 | "from urllib.parse import urlencode\n",
521 | "from urllib.parse import urljoin\n",
522 | "import re\n",
523 | "import json\n",
524 | "queries = ['Видеокарта'] ##Запросы (что ищем)\n",
525 | "API = '' ##API ключ для ScraperAPI\n",
526 | "\n",
527 | "def get_url(url):\n",
528 | " payload = {'api_key': API, 'url': url, 'country_code': 'us'}\n",
529 | " proxy_url = 'http://api.scraperapi.com/?' + urlencode(payload)\n",
530 | " return proxy_url\n",
531 | "\n",
532 | "class AliexpressSpider(scrapy.Spider):\n",
533 | " name = 'Aliexpress'\n",
534 | "\n",
535 | " def start_requests(self):\n",
536 | " for query in queries:\n",
537 | " url = 'https://aliexpress.ru/wholesale?' + urlencode({'g': 'n', 'SearchText': query, 'page': '1'})\n",
538 | " yield scrapy.Request(url=get_url(url), callback=self.parse_keyword_response)\n",
539 | "\n",
540 | " def parse_keyword_response(self, response):\n",
541 | " products = #вставить поиск ссылкок\n",
542 | "\n",
543 | " for product in products:\n",
544 | " product_url = #строим ссылку для продукта\n",
545 | " yield scrapy.Request(url=get_url(product_url), callback=self.parse_product_page)\n",
546 | "\n",
547 | " next_page = #ссылка для следующей страницы\n",
548 | " if next_page:\n",
549 | " url = #делаем ссылку\n",
550 | " yield scrapy.Request(url=get_url(url), callback=self.parse_keyword_response)\n",
551 | "\n",
552 | " def parse_product_page(self, response):\n",
553 | " #парсим продукт"
554 | ],
555 | "metadata": {
556 | "id": "CXBPYL_vbN00"
557 | },
558 | "execution_count": null,
559 | "outputs": []
560 | },
561 | {
562 | "cell_type": "markdown",
563 | "source": [
564 | "## Паук дня"
565 | ],
566 | "metadata": {
567 | "id": "uT-JIRE0ng26"
568 | }
569 | },
570 | {
571 | "cell_type": "markdown",
572 | "source": [
573 | ""
574 | ],
575 | "metadata": {
576 | "id": "5j8TkInooEX9"
577 | }
578 | },
579 | {
580 | "cell_type": "markdown",
581 | "source": [
582 | "Это паук Maratus Volans, живут они в Австралии (неядовитые) и просто очень красивые!\n",
583 | "\n",
584 | "Как известно, разноцетный окрас не особо хорош для скрытия, но хорош для другого: для привлечения самок! Поэтому они такие цветные\n",
585 | "\n",
586 | "Одни из немногих пауков, которые исполняют брачный танец, чтобы привлечь самку (но если самке не понравится, то она его съест). На картинке - поза паука при танце\n",
587 | "\n",
588 | "А также они видят сильно больше цветов, чем человек (например, ультрафиолет)"
589 | ],
590 | "metadata": {
591 | "id": "Z4Cm55mtoGJQ"
592 | }
593 | },
594 | {
595 | "cell_type": "markdown",
596 | "source": [
597 | ""
598 | ],
599 | "metadata": {
600 | "id": "A9sHIppTpGvf"
601 | }
602 | }
603 | ]
604 | }
--------------------------------------------------------------------------------
/week13/Python_2_Lecture_13.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "collapsed_sections": [
8 | "PEoDN6Wn3Rjx",
9 | "owJSkKaNUjHW",
10 | "Jd4u_e9YmdcZ"
11 | ]
12 | },
13 | "kernelspec": {
14 | "name": "python3",
15 | "display_name": "Python 3"
16 | },
17 | "language_info": {
18 | "name": "python"
19 | }
20 | },
21 | "cells": [
22 | {
23 | "cell_type": "markdown",
24 | "source": [
25 | "# Продвинутый Python, лекция 13\n",
26 | "\n",
27 | "**Лектор:** Петров Тимур\n",
28 | "\n",
29 | "**Семинаристы:** Петров Тимур, Коган Александра, Бузаев Федор, Дешеулин Олег\n",
30 | "\n",
31 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)"
32 | ],
33 | "metadata": {
34 | "id": "wAjy7jcIsEE-"
35 | }
36 | },
37 | {
38 | "cell_type": "markdown",
39 | "source": [
40 | "## Асинхронность"
41 | ],
42 | "metadata": {
43 | "id": "PEoDN6Wn3Rjx"
44 | }
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "source": [
49 | "Что такое асинхронность? Представим себе программу, которая запрашивает данные на каком-либо сервере. Отправляется запрос... ждем ... ждем ... и получаем ответ.\n",
50 | "\n",
51 | "Что происходит в момент ожидания? Ничего, мы просто ждем результата. А вообще было бы классно не тратить время попусту (как и ресурсы), а делать что-то еще (как будто наше ожидание происходит на фоне). В этом и суть асинхронности!\n",
52 | "\n",
53 | "У нас есть несколько функций, которые мы вызываем, но не ожидаем результат прямо сейчас. Внутри асинхронного программирования - идея конкурентности (concurrency) — две или более задачи могут запускаться, выполняться и завершаться в перекрывающиеся периоды времени.\n",
54 | "\n",
55 | "Для этого в Python есть библиотека [asyncio](https://docs.python.org/3/library/asyncio.html), которая основана на корутинах. Корутины - это некоторая функция, которая может начинаться, приостанавливаться и завершаться в произвольный момент времени.\n",
56 | "\n",
57 | "Давайте на примере:"
58 | ],
59 | "metadata": {
60 | "id": "pCqLIv3CtWt0"
61 | }
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "metadata": {
67 | "id": "eu63f4cgr7rw"
68 | },
69 | "outputs": [],
70 | "source": [
71 | "import asyncio\n",
72 | "\n",
73 | "async def compute(a, b):\n",
74 | " print('Compute...')\n",
75 | " await asyncio.sleep(1.0)\n",
76 | " return a + b\n",
77 | "\n",
78 | "async def print_sum(a, b):\n",
79 | " result = await compute(a, b)\n",
80 | " print('{} + {} = {}'.format(a, b, result))"
81 | ]
82 | },
83 | {
84 | "cell_type": "code",
85 | "source": [
86 | "import asyncio\n",
87 | "\n",
88 | "async def compute(a, b):\n",
89 | " print('Compute...')\n",
90 | " await asyncio.sleep(1.0)\n",
91 | " return a + b\n",
92 | "\n",
93 | "async def main():\n",
94 | " a, b = 1, 2\n",
95 | " result = await compute(a, b)\n",
96 | " print('{} + {} = {}'.format(a, b, result))\n",
97 | "\n",
98 | "asyncio.run(main())"
99 | ],
100 | "metadata": {
101 | "id": "P9gEJjzqCToK"
102 | },
103 | "execution_count": null,
104 | "outputs": []
105 | },
106 | {
107 | "cell_type": "markdown",
108 | "source": [
109 | "Что здесь происходит?\n",
110 | "\n",
111 | "Мы создали две корутины, compute и print_sum (указывается с помощью слова async)\n",
112 | "\n",
113 | "Далее мы запустили наше выполнение с помощью так называемого event_loop, который берет нашу функцию, и запускает ее. В свою очередь, наша корутина compute запускается и находится в режиме ожидания, пока не выполнится compute() (это делается с помощью слова await). После ожидания в секунду, compute выводит ответ, который передается к print_sum (напоминаю, все это время print_sum выполняется) и далее выводит ответ\n",
114 | "\n",
115 | "Общая схема выглядит вот так:\n",
116 | "\n",
117 | ""
118 | ],
119 | "metadata": {
120 | "id": "Tu6Iw_QGy8tV"
121 | }
122 | },
123 | {
124 | "cell_type": "markdown",
125 | "source": [
126 | "Понятное дело, что сейчас собрали такой игрушечный пример (какой сейчас был смсл так сделать). Но таким образом можно запустить выполнение и ждать результата, например, от нескольких функций etc. Давайте на вот таком примере:"
127 | ],
128 | "metadata": {
129 | "id": "Aiatj43I0ggu"
130 | }
131 | },
132 | {
133 | "cell_type": "code",
134 | "source": [
135 | "import asyncio\n",
136 | "\n",
137 | "async def factorial(name, number):\n",
138 | " f = 1\n",
139 | " for i in range(2, number + 1):\n",
140 | " print(f\"Task {name}: Compute factorial({i})...\")\n",
141 | " await asyncio.sleep(1)\n",
142 | " f *= i\n",
143 | " print(f\"Task {name}: factorial({number}) = {f}\")\n",
144 | " return f\n",
145 | "\n",
146 | "res = await asyncio.gather(\n",
147 | " factorial(\"A\", 2),\n",
148 | " factorial(\"B\", 3),\n",
149 | " factorial(\"C\", 4),\n",
150 | " return_exceptions= True # как обрабатывать ошибки, если что-то упадет\n",
151 | ")\n",
152 | "print(res)\n"
153 | ],
154 | "metadata": {
155 | "id": "5f4c8kYd1gCl",
156 | "colab": {
157 | "base_uri": "https://localhost:8080/"
158 | },
159 | "outputId": "e3c88adf-9110-4530-a672-b2ac1bfeeb5c"
160 | },
161 | "execution_count": null,
162 | "outputs": [
163 | {
164 | "output_type": "stream",
165 | "name": "stdout",
166 | "text": [
167 | "Task A: Compute factorial(2)...\n",
168 | "Task B: Compute factorial(2)...\n",
169 | "Task C: Compute factorial(2)...\n",
170 | "Task A: factorial(2) = 2\n",
171 | "Task B: Compute factorial(3)...\n",
172 | "Task C: Compute factorial(3)...\n",
173 | "Task B: factorial(3) = 6\n",
174 | "Task C: Compute factorial(4)...\n",
175 | "Task C: factorial(4) = 24\n",
176 | "[2, 6, 24]\n"
177 | ]
178 | }
179 | ]
180 | },
181 | {
182 | "cell_type": "markdown",
183 | "source": [
184 | "Что у нас происходит здесь и что добавилось?\n",
185 | "\n",
186 | "* gather - запусти все, что мы перечислили, асихнронно\n",
187 | "\n",
188 | "Что мы видим по выводу?\n",
189 | "\n",
190 | "У нас есть таска A, B, C. В линейной логике мы бы вначале посчитали A, потом B, потом C (и все время ожидания сложилось бы). Как это здесь работает?\n",
191 | "\n",
192 | "Идем в задачу A, что-то сделали, уходим в режим ожидания. Пока ожидаем, можно взять и что-то сделать в таске B, уходим, можем сделать что-то в таске С. Дождались внутри A, делаем ее и так далее. Таким образом, мы делаем 3 таски в одно время"
193 | ],
194 | "metadata": {
195 | "id": "N7iwjnmM13vY"
196 | }
197 | },
198 | {
199 | "cell_type": "markdown",
200 | "source": [
201 | ""
202 | ],
203 | "metadata": {
204 | "id": "RMF52HUSJjsF"
205 | }
206 | },
207 | {
208 | "cell_type": "markdown",
209 | "source": [
210 | "### Ограничение ожидания"
211 | ],
212 | "metadata": {
213 | "id": "CK2kOCcu3QuB"
214 | }
215 | },
216 | {
217 | "cell_type": "markdown",
218 | "source": [
219 | "Отлично, можем делать что-то асинхронно, но что, если у нас один из вызовов затупил настолько, что мы не хотим ждать и пойти делать дальше что-то без него (скажем, какой-то сервер упал, ответа от него мы не дождемся)?\n",
220 | "\n",
221 | "Для этого есть timeout - ограничение на время ответа"
222 | ],
223 | "metadata": {
224 | "id": "C-t1HE3S3Vgq"
225 | }
226 | },
227 | {
228 | "cell_type": "code",
229 | "source": [
230 | "import asyncio\n",
231 | "\n",
232 | "async def eternity():\n",
233 | " await asyncio.sleep(3600)\n",
234 | " print('yay!')\n",
235 | "\n",
236 | "\n",
237 | "# Wait for at most 1 second\n",
238 | "try:\n",
239 | " await asyncio.wait_for(eternity(), timeout=1.0) # Отдельная функция wait_for, то есть ждем функцию, но ограничиваем по времени\n",
240 | "except asyncio.TimeoutError: # если выплюнул ошибку ожидания, то делаем то или иное\n",
241 | " print('timeout!')\n"
242 | ],
243 | "metadata": {
244 | "id": "OfyRqyIX3s9P",
245 | "colab": {
246 | "base_uri": "https://localhost:8080/"
247 | },
248 | "outputId": "bb103748-a217-4078-ae5e-d307e817df84"
249 | },
250 | "execution_count": null,
251 | "outputs": [
252 | {
253 | "output_type": "stream",
254 | "name": "stdout",
255 | "text": [
256 | "timeout!\n"
257 | ]
258 | }
259 | ]
260 | },
261 | {
262 | "cell_type": "markdown",
263 | "source": [
264 | "Что происходит на уровне корутины? Он ловит ошибку, убивается и закрывается. Но внутри той же самой корутины можно сделать что-то еще с этой ошибкой (и это нормально)"
265 | ],
266 | "metadata": {
267 | "id": "5uI_S1UZ_tSW"
268 | }
269 | },
270 | {
271 | "cell_type": "code",
272 | "source": [
273 | "import asyncio\n",
274 | "\n",
275 | "async def eternity():\n",
276 | " try:\n",
277 | " await asyncio.sleep(3600)\n",
278 | " print('yay!')\n",
279 | " except asyncio.CancelledError:\n",
280 | " print(\"closed\")\n",
281 | " await asyncio.sleep(5)\n",
282 | "\n",
283 | "# Wait for at most 1 second\n",
284 | "try:\n",
285 | " await asyncio.wait_for(eternity(), timeout=1.0) # Отдельная функция wait_for, то есть ждем функцию, но ограничиваем по времени\n",
286 | "except asyncio.TimeoutError: # если выплюнул ошибку ожидания, то делаем то или иное\n",
287 | " print('timeout!')\n"
288 | ],
289 | "metadata": {
290 | "colab": {
291 | "base_uri": "https://localhost:8080/"
292 | },
293 | "id": "5-qozhb3_s_z",
294 | "outputId": "83d08600-c213-4e48-b9c5-db70e9b6bbb3"
295 | },
296 | "execution_count": null,
297 | "outputs": [
298 | {
299 | "output_type": "stream",
300 | "name": "stdout",
301 | "text": [
302 | "closed\n",
303 | "timeout!\n"
304 | ]
305 | }
306 | ]
307 | },
308 | {
309 | "cell_type": "markdown",
310 | "source": [
311 | "А ежели мы хотим сделать так, чтобы она все-таки доработала, но скинула ошибку того, что она работает долго? На этот случай есть shield (защита от отмены таска):"
312 | ],
313 | "metadata": {
314 | "id": "XmasZweoBKnN"
315 | }
316 | },
317 | {
318 | "cell_type": "code",
319 | "source": [
320 | "import asyncio\n",
321 | "\n",
322 | "async def eternity():\n",
323 | " await asyncio.sleep(5)\n",
324 | " print('yay!')\n",
325 | "\n",
326 | "# Wait for at most 1 second\n",
327 | "try:\n",
328 | " await asyncio.wait_for(asyncio.shield(eternity()), timeout=1.0) # Отдельная функция wait_for, то есть ждем функцию, но ограничиваем по времени\n",
329 | "except asyncio.TimeoutError: # если выплюнул ошибку ожидания, то делаем то или иное\n",
330 | " print('timeout!')\n",
331 | " await asyncio.sleep(5)"
332 | ],
333 | "metadata": {
334 | "colab": {
335 | "base_uri": "https://localhost:8080/"
336 | },
337 | "id": "-XU5Vov0BVES",
338 | "outputId": "1f73df1c-3441-4d12-e8b0-4375fd4efed3"
339 | },
340 | "execution_count": null,
341 | "outputs": [
342 | {
343 | "output_type": "stream",
344 | "name": "stdout",
345 | "text": [
346 | "timeout!\n",
347 | "yay!\n"
348 | ]
349 | }
350 | ]
351 | },
352 | {
353 | "cell_type": "markdown",
354 | "source": [
355 | "### Таски"
356 | ],
357 | "metadata": {
358 | "id": "upB53OvdKBwl"
359 | }
360 | },
361 | {
362 | "cell_type": "markdown",
363 | "source": [
364 | "У нас есть ожидание, есть запуски и так далее. А нельзя это как-то обернуть в сущность типа задачи, которую можно вызвать позже сразу как результат? Можно! Для этого есть объект Task, который создается с помощью функции create_task() (и отсчет начинается уже в момент создания таски):"
365 | ],
366 | "metadata": {
367 | "id": "ANp0ZwTPKDh4"
368 | }
369 | },
370 | {
371 | "cell_type": "code",
372 | "source": [
373 | "import asyncio\n",
374 | "import time\n",
375 | "\n",
376 | "async def say(word, delay):\n",
377 | " print(\"heh\" + word)\n",
378 | " await asyncio.sleep(delay)\n",
379 | " print(word)\n",
380 | "\n",
381 | "t_1 = asyncio.create_task(say(\"Hi_1\", 2))\n",
382 | "t_2 = asyncio.create_task(say(\"Hi_2\", 1))\n",
383 | "\n",
384 | "print(time.strftime(\"%X\"))\n",
385 | "\n",
386 | "await t_1\n",
387 | "await t_2\n",
388 | "\n",
389 | "print(time.strftime(\"%X\"))"
390 | ],
391 | "metadata": {
392 | "colab": {
393 | "base_uri": "https://localhost:8080/"
394 | },
395 | "id": "iOufky448bk7",
396 | "outputId": "416948b7-cc50-4b71-95e8-89a5c1e374e3"
397 | },
398 | "execution_count": null,
399 | "outputs": [
400 | {
401 | "output_type": "stream",
402 | "name": "stdout",
403 | "text": [
404 | "16:41:22\n",
405 | "hehHi_1\n",
406 | "hehHi_2\n",
407 | "Hi_2\n",
408 | "Hi_1\n",
409 | "16:41:24\n"
410 | ]
411 | }
412 | ]
413 | },
414 | {
415 | "cell_type": "markdown",
416 | "source": [
417 | "### Забираем результаты как придут"
418 | ],
419 | "metadata": {
420 | "id": "RlKfadD8DNXl"
421 | }
422 | },
423 | {
424 | "cell_type": "markdown",
425 | "source": [
426 | "Хорошо, у нас есть несколько корутин, умеем запускать с помощью gather, а как сразу забирать результаты, не ожидая окончания каждого из них? На это есть as_completed:"
427 | ],
428 | "metadata": {
429 | "id": "COWMCrJ6DRbR"
430 | }
431 | },
432 | {
433 | "cell_type": "code",
434 | "source": [
435 | "async def fact(number):\n",
436 | " res = 1\n",
437 | " for i in range(2, number + 1):\n",
438 | " await asyncio.sleep(1)\n",
439 | " res *= i\n",
440 | " return number, res\n",
441 | "\n",
442 | "for i, future in enumerate(asyncio.as_completed([fact(4), fact(3), fact(2)])):\n",
443 | " number, result = await future\n",
444 | " print(f\"Factorial({number}) = {result}\")"
445 | ],
446 | "metadata": {
447 | "colab": {
448 | "base_uri": "https://localhost:8080/"
449 | },
450 | "id": "JijfXtPbDgWF",
451 | "outputId": "58667af4-540c-4873-b8cd-354af1b52972"
452 | },
453 | "execution_count": null,
454 | "outputs": [
455 | {
456 | "output_type": "stream",
457 | "name": "stdout",
458 | "text": [
459 | "Factorial(2) = 2\n",
460 | "Factorial(3) = 6\n",
461 | "Factorial(4) = 24\n"
462 | ]
463 | }
464 | ]
465 | },
466 | {
467 | "cell_type": "markdown",
468 | "source": [
469 | "## Комбинируем потоки и корутины"
470 | ],
471 | "metadata": {
472 | "id": "owJSkKaNUjHW"
473 | }
474 | },
475 | {
476 | "cell_type": "markdown",
477 | "source": [
478 | "Зачем это нужно?\n",
479 | "\n",
480 | "Допустим, что вы настраиваете работу системы, допустим, системы доставки. Вам что необходимо? Вы ждете заказов от пользователя, но пока вы их ждете, то у вас же все не простаивает на месте: заказы собираются, курьеры возят. И все это синхронно. Давайте осуществлять мечту"
481 | ],
482 | "metadata": {
483 | "id": "SqjDFA9PUoHw"
484 | }
485 | },
486 | {
487 | "cell_type": "code",
488 | "execution_count": null,
489 | "metadata": {
490 | "id": "3CjD2eVDTejn"
491 | },
492 | "outputs": [],
493 | "source": [
494 | "import time\n",
495 | "import asyncio\n",
496 | "\n",
497 | "\n",
498 | "def blocking_io():\n",
499 | " print(f\"{time.strftime('%X')} block IO\")\n",
500 | " time.sleep(5) # снимает GIL\n",
501 | " print(f\"{time.strftime('%X')} unblock IO\")\n",
502 | "\n",
503 | "non_block = asyncio.to_thread(blocking_io) # без скобочек!!! аргументы просто через пробел, версия 3.9+\n",
504 | "\n",
505 | "async def main():\n",
506 | " print(f\"{time.strftime('%X')} start async\")\n",
507 | " _ = await asyncio.gather(non_block, asyncio.sleep(5))\n",
508 | " print(f\"{time.strftime('%X')} finish async\")\n",
509 | "\n",
510 | "asyncio.run(main())"
511 | ]
512 | },
513 | {
514 | "cell_type": "markdown",
515 | "source": [
516 | "Аналогично с теми же самими факториалами (опять-таки, это нам не даст ускорения по сравнению с просто корутинами, потому что слишком малые рассчеты) и опять-таки - внутри Python паралеллить вычисления - дело такое себе"
517 | ],
518 | "metadata": {
519 | "id": "QllSst9HaPte"
520 | }
521 | },
522 | {
523 | "cell_type": "code",
524 | "source": [
525 | "import time\n",
526 | "import asyncio\n",
527 | "\n",
528 | "def factorial(name, number):\n",
529 | " f = 1\n",
530 | " for i in range(2, number + 1):\n",
531 | " print(f\"Task {name}: Compute factorial({i})...\")\n",
532 | " time.sleep(0.1)\n",
533 | " f *= i\n",
534 | " print(f\"Task {name}: factorial({number}) = {f}\")\n",
535 | " return f\n",
536 | "\n",
537 | "\n",
538 | "fact_1 = asyncio.to_thread(factorial, \"A\", 2)\n",
539 | "fact_2 = asyncio.to_thread(factorial, \"B\", 3)\n",
540 | "fact_3 = asyncio.to_thread(factorial, \"C\", 4)\n",
541 | "\n",
542 | "async def main():\n",
543 | " print(f\"{time.time()} beg\")\n",
544 | " res = await asyncio.gather(\n",
545 | " fact_1,\n",
546 | " fact_2,\n",
547 | " fact_3\n",
548 | " )\n",
549 | " print(f\"{time.time()} end\")\n",
550 | "\n",
551 | "asyncio.run(main())\n"
552 | ],
553 | "metadata": {
554 | "id": "3X6EjqUAawqS"
555 | },
556 | "execution_count": null,
557 | "outputs": []
558 | },
559 | {
560 | "cell_type": "markdown",
561 | "source": [
562 | "## А где работать с асихнронностью?"
563 | ],
564 | "metadata": {
565 | "id": "P-icj441lHQR"
566 | }
567 | },
568 | {
569 | "cell_type": "markdown",
570 | "source": [
571 | "### Aioschedule"
572 | ],
573 | "metadata": {
574 | "id": "Jd4u_e9YmdcZ"
575 | }
576 | },
577 | {
578 | "cell_type": "markdown",
579 | "source": [
580 | "Первый пример - это шедулинг! Собственно, мы говорили о том, что ожидание делает работу медленнее, а наличие корутин ее ускоряет (потому что занимается чем-то еще, помимо самого ожидания)\n",
581 | "\n",
582 | "\n",
583 | "Одна из вещей, которая очень полезна внутри асинхронности - это шедулинг (нам часто нужно что-то делать раз в какое-то время, например, раз в день нам точно надо)"
584 | ],
585 | "metadata": {
586 | "id": "N8xUZqDnmo_k"
587 | }
588 | },
589 | {
590 | "cell_type": "code",
591 | "source": [
592 | "!pip install aioschedule"
593 | ],
594 | "metadata": {
595 | "colab": {
596 | "base_uri": "https://localhost:8080/"
597 | },
598 | "id": "JoVSkAZnnwz8",
599 | "outputId": "315af2fc-f06f-4e26-d06e-5c5c9c653c07"
600 | },
601 | "execution_count": null,
602 | "outputs": [
603 | {
604 | "output_type": "stream",
605 | "name": "stdout",
606 | "text": [
607 | "Collecting aioschedule\n",
608 | " Downloading aioschedule-0.5.2.tar.gz (12 kB)\n",
609 | " Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
610 | "Building wheels for collected packages: aioschedule\n",
611 | " Building wheel for aioschedule (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
612 | " Created wheel for aioschedule: filename=aioschedule-0.5.2-py3-none-any.whl size=8479 sha256=b516ad79a82809ceae31650bb3440b7ce8a31c0132960b2e432ecdf74af1b2b3\n",
613 | " Stored in directory: /root/.cache/pip/wheels/f5/b2/2a/ee29c2ee340b4c4552cda20998765a51eb48563c97c2bb2577\n",
614 | "Successfully built aioschedule\n",
615 | "Installing collected packages: aioschedule\n",
616 | "Successfully installed aioschedule-0.5.2\n"
617 | ]
618 | }
619 | ]
620 | },
621 | {
622 | "cell_type": "markdown",
623 | "source": [
624 | "Давайте на примере:"
625 | ],
626 | "metadata": {
627 | "id": "lN5N6QWKn7b-"
628 | }
629 | },
630 | {
631 | "cell_type": "code",
632 | "source": [
633 | "import asyncio\n",
634 | "import aioschedule as schedule\n",
635 | "import time\n",
636 | "\n",
637 | "async def job():\n",
638 | " print(\"HaHa\")\n",
639 | "\n",
640 | "async def new_job():\n",
641 | " print(\"Heh\")\n",
642 | "\n",
643 | "schedule.every(1).seconds.do(job) # В шедулере говорим как часто использовать и что\n",
644 | "schedule.every(2).seconds.do(new_job)\n",
645 | "\n",
646 | "loop = asyncio.new_event_loop()\n",
647 | "while True:\n",
648 | " loop.run_until_complete(schedule.run_pending()) # запускаем loop\n",
649 | " time.sleep(0.1)"
650 | ],
651 | "metadata": {
652 | "id": "WktO3kFqn88B"
653 | },
654 | "execution_count": null,
655 | "outputs": []
656 | },
657 | {
658 | "cell_type": "markdown",
659 | "source": [
660 | "### Aiogram"
661 | ],
662 | "metadata": {
663 | "id": "JNE6ov11lc0z"
664 | }
665 | },
666 | {
667 | "cell_type": "markdown",
668 | "source": [
669 | "Зачем в Телеграм боте нужна асинхронность, спросите вы? Ответ прост: это гораздо быстрее работает (особенно чем больше нагрузка, тем больше вам нужна асинхронность), ну а также это позволяет более эффективно использовать параллельность (которая, сама собой, и так есть в телеграм-боте)\n",
670 | "\n",
671 | "В чем разница? По существу, вместо requests используется [aiohttp](https://docs.aiohttp.org/en/stable/) - асинхронная версия requests, если так можно высказаться"
672 | ],
673 | "metadata": {
674 | "id": "5r7CmuOpdJx3"
675 | }
676 | },
677 | {
678 | "cell_type": "code",
679 | "source": [
680 | "import asyncio\n",
681 | "import aioschedule\n",
682 | "from telebot.async_telebot import AsyncTeleBot\n",
683 | "\n",
684 | "API_TOKEN = ''\n",
685 | "bot = AsyncTeleBot(API_TOKEN)\n",
686 | "\n",
687 | "\n",
688 | "async def beep(chat_id):\n",
689 | " \"\"\"Send the beep message.\"\"\"\n",
690 | " await bot.send_message(chat_id, text='Beep!')\n",
691 | " aioschedule.clear(chat_id)\n",
692 | "\n",
693 | "\n",
694 | "@bot.message_handler(commands=['help', 'start'])\n",
695 | "async def send_welcome(message):\n",
696 | " await bot.reply_to(message, \"Hi! Use /set to set a timer\")\n",
697 | "\n",
698 | "\n",
699 | "@bot.message_handler(commands=['set'])\n",
700 | "async def set_timer(message):\n",
701 | " args = message.text.split()\n",
702 | " if len(args) > 1 and args[1].isdigit():\n",
703 | " sec = int(args[1])\n",
704 | " aioschedule.every(sec).seconds.do(beep, message.chat.id).tag(message.chat.id)\n",
705 | " else:\n",
706 | " await bot.reply_to(message, 'Usage: /set ')\n",
707 | "\n",
708 | "\n",
709 | "@bot.message_handler(commands=['unset'])\n",
710 | "def unset_timer(message):\n",
711 | " aioschedule.clean(message.chat.id)\n",
712 | "\n",
713 | "\n",
714 | "async def scheduler():\n",
715 | " while True:\n",
716 | " await aioschedule.run_pending()\n",
717 | " await asyncio.sleep(1)\n",
718 | "\n",
719 | "\n",
720 | "async def main():\n",
721 | " await asyncio.gather(bot.infinity_polling(), scheduler())\n",
722 | "\n",
723 | "\n",
724 | "if __name__ == '__main__':\n",
725 | " asyncio.run(main())"
726 | ],
727 | "metadata": {
728 | "id": "4N3_uv7rj3GA"
729 | },
730 | "execution_count": null,
731 | "outputs": []
732 | },
733 | {
734 | "cell_type": "markdown",
735 | "source": [
736 | "Зачем еще нужна асинхронность? Опять-таки для подключения к БД. Допустим, что вы храните информацию по юзерам: как их зовут и так далее (та самая проблема, которую вы получали на ДЗ для обработки бана юзера и так далее). Так вот, для создания более сложных ботов (например, магазина), вам нужно подключаться к БД, делать асинхронного бота, и таким образом, запрос еще ждать. А остальные ждут и ждут..."
737 | ],
738 | "metadata": {
739 | "id": "TDUINjIfmyQD"
740 | }
741 | },
742 | {
743 | "cell_type": "markdown",
744 | "source": [
745 | "## Животное дня"
746 | ],
747 | "metadata": {
748 | "id": "Vt295navnN5Z"
749 | }
750 | },
751 | {
752 | "cell_type": "markdown",
753 | "source": [
754 | "Итак, сегодня у нас ламы! На самом деле ламы - это 4 отдельных вида, которых стоит различать. Какие есть виды?\n",
755 | "\n",
756 | "* Гуанако\n",
757 | "\n",
758 | "* Викунья\n",
759 | "\n",
760 | "* Лама\n",
761 | "\n",
762 | "* Альпака"
763 | ],
764 | "metadata": {
765 | "id": "one_AAvXnSZ2"
766 | }
767 | },
768 | {
769 | "cell_type": "markdown",
770 | "source": [
771 | ""
772 | ],
773 | "metadata": {
774 | "id": "Y-FHNstwtimQ"
775 | }
776 | },
777 | {
778 | "cell_type": "markdown",
779 | "source": [
780 | "Это гуанако. Они дикие и прыгучие, а одомашненный вид - это лама\n",
781 | "\n",
782 | ""
783 | ],
784 | "metadata": {
785 | "id": "bIlMhQOntqzJ"
786 | }
787 | },
788 | {
789 | "cell_type": "markdown",
790 | "source": [
791 | "Викунья - дикая, меньше и стройнее, чем гуанако (различить сложно)\n",
792 | "\n",
793 | ""
794 | ],
795 | "metadata": {
796 | "id": "DKJgR8ZeuC85"
797 | }
798 | },
799 | {
800 | "cell_type": "markdown",
801 | "source": [
802 | "А одомашненный вид - это альпака. Она пышная и пушистая!\n",
803 | "\n",
804 | ""
805 | ],
806 | "metadata": {
807 | "id": "OSz2XJRTuMut"
808 | }
809 | },
810 | {
811 | "cell_type": "markdown",
812 | "source": [
813 | "Лам используют в качестве вьючных животных (но они очень гордые, положи на них больше - они пошлют)\n",
814 | "\n",
815 | "А альпак - для того, чтобы их стричь и из шерсти делать одежду и так далее\n",
816 | "\n",
817 | "Одомашнили примерно 5000 тыс лет назад предками инков и прочих народов Анд"
818 | ],
819 | "metadata": {
820 | "id": "2WMmp862uciW"
821 | }
822 | }
823 | ]
824 | }
--------------------------------------------------------------------------------
|