├── .gitignore ├── 1-10_classes ├── 1-10_classes.ipynb └── testme.py ├── 1-11_modules ├── 1-11_modules.ipynb └── testme.py ├── 1-1_cells ├── 1_1-cells.ipynb └── testme.py ├── 1-2_numbers ├── 1_2-numbers.ipynb └── testme.py ├── 1-3_booleans └── 1-3_booleans.ipynb ├── 1-4_strings ├── 1-4_strings.ipynb └── testme.py ├── 1-5_lists-tuples ├── 1-5_lists-tuples.ipynb └── testme.py ├── 1-6_dictionaries └── 1-6_dictionaries.ipynb ├── 1-7_conditionals └── 1-7_conditionals.ipynb ├── 1-8_loops ├── 1-8_loops.ipynb ├── names.txt ├── rockyou-50.txt └── testme.py ├── 1-9_functions ├── 1-9_functions.ipynb └── testme.py ├── LICENSE ├── README.md ├── poetry.lock ├── pyproject.toml └── setup.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /1-10_classes/1-10_classes.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "d990c0f4-da9a-4643-9e66-34100343a2b5", 6 | "metadata": {}, 7 | "source": [ 8 | "# 1-10: Classes\n", 9 | "\n", 10 | "Python is what's known as an **Object-Oriented** language. When we work with Python code, we deal with structures meant to usefully describe our data with characteristics and capabilities that make data easier to reason about.\n", 11 | "\n", 12 | "Computers don't know anything. At the very base, a computer is only good at detecting the presence or absence of electicity. Everything else is just layers of abstraction above that. But that's not how people think about the universe, and modern programming languages have concepts that seek to more closely align computer mechanics with human thought. That's what objects are.\n", 13 | "\n", 14 | "Think of a kind of object. Let's take a car, for example. What makes a car a car?\n", 15 | "\n", 16 | "**A Meditation on Carness**\n", 17 | "\n", 18 | "* Has 4 wheels\n", 19 | "* Was built in a year\n", 20 | "* Was built by a company\n", 21 | "* Has a model name\n", 22 | "* Has a top speed, probably\n", 23 | "* Can move\n", 24 | "\n", 25 | "Most, if not all, cars are going to have these characteristics. If we wanted to define cars in Python, we would start by creating a car **Class**. \n", 26 | "\n", 27 | "Classes are blueprints for objects in Python. Any given individual object, like a specific car, would be an **instance** of the class.\n", 28 | "\n", 29 | "Classes are incredibly useful for structuring data, but making them takes some getting used to." 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "id": "173a1094-2d49-4f13-ac51-be0b0aeebe8b", 35 | "metadata": {}, 36 | "source": [ 37 | "## Syntax\n", 38 | "\n", 39 | "To create a class, we use the `class` keyword followed by the class name. Convention dictates these names are capitalized. Like so:\n", 40 | "\n", 41 | "```python\n", 42 | "class Car:\n", 43 | " # do stuff\n", 44 | "```\n", 45 | "\n", 46 | "But that's not all. To _really_ make a class, we need to give it a special function known as a **constructor**. This is the function that will take in arguments and produce an **instance** of the class. In Python, the constructor function is `__init__()`. Yes, those are 2 underscores on either side of `init`. This pattern is common in Python and sometimes referred to as \"dunders,\" short for \"double underscores.\"\n", 47 | "\n", 48 | "But that's not all! `__init__()` also _must_ take a special argument as its first: `self`. This argument refers to the object being created, and you have to provide it to the constructor in order for it to work.\n", 49 | "\n", 50 | "```python\n", 51 | "class Car:\n", 52 | " \n", 53 | " def __init__(self):\n", 54 | " pass\n", 55 | "```\n", 56 | "\n", 57 | "The constructor is commonly where we assign **properties** to an object. \n", 58 | "\n", 59 | "Wait, what?" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "id": "380a1c24-ae05-4a4c-9591-3a7067a4f4e1", 65 | "metadata": {}, 66 | "source": [ 67 | "## Properties\n", 68 | "\n", 69 | "Properties are a class's characteristics—things that describe object or instance of a class. These are a little different from **methods**, which we'll cover shortly. Properties are single values. To demonstrate properties, let's finally make a real-deal `Car` class." 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 1, 75 | "id": "c086e61a-055a-492c-867e-57cbe195723f", 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "# Our first class! How exciting!\n", 80 | "class Car:\n", 81 | " \n", 82 | " def __init__(self, year, make, model, top_speed):\n", 83 | " self.year = year\n", 84 | " self.make = make\n", 85 | " self.model = model\n", 86 | " self.top_speed = top_speed " 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "id": "95a8abab-54ab-4381-a3fd-c7a3bdef8585", 92 | "metadata": {}, 93 | "source": [ 94 | "So in addition to the built-in `self` argument, our constructor takes 4 other args which will define `Car`s for us. The constructor takes those function args and assigns them to the newly-created object directly, creating our properties. \n", 95 | "\n", 96 | "**Note:** This is not the _best_ way to make properties, but it's good enough for now.\n", 97 | "\n", 98 | "With a `Car` class defined, let's make some instances of it!\n", 99 | "\n", 100 | "![kitt](https://i.pinimg.com/originals/49/50/73/495073b26b5f1bc1f697476ef6c7f9e8.gif) ![ecto1](https://i.pinimg.com/originals/32/1e/b5/321eb5a58314aa1b48a99a1b4e964129.gif)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 2, 106 | "id": "629f8fc7-e58a-48d3-a61a-1ca8730b70b5", 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "# If we're gonna make cars, we're gonna make awesome cars\n", 111 | "kitt = Car(1982, \"Pontiac\", \"Firebird\", 124)\n", 112 | "\n", 113 | "ecto = Car(1959, \"Cadillac\", \"Professional Chassis\", 75)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "id": "6c2a3ca8-9ca6-43ba-a039-10a4c664f642", 119 | "metadata": {}, 120 | "source": [ 121 | "Okay, so we've made Kitt and the Ecto, but what now? What happens when we try to display them?" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 3, 127 | "id": "ad64f55b-d056-4cef-908a-93e03e436b74", 128 | "metadata": {}, 129 | "outputs": [ 130 | { 131 | "name": "stdout", 132 | "output_type": "stream", 133 | "text": [ 134 | "<__main__.Car object at 0x7fc8cc4787f0>\n" 135 | ] 136 | } 137 | ], 138 | "source": [ 139 | "print(kitt)" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 4, 145 | "id": "3a243ad0-2111-44ee-a234-f9d58c033391", 146 | "metadata": {}, 147 | "outputs": [ 148 | { 149 | "name": "stdout", 150 | "output_type": "stream", 151 | "text": [ 152 | "<__main__.Car object at 0x7fc8cc478fd0>\n" 153 | ] 154 | } 155 | ], 156 | "source": [ 157 | "print(ecto)" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "id": "2e8e129a-e37a-4139-9cac-0aeeda3abeff", 163 | "metadata": {}, 164 | "source": [ 165 | "Uhhh, that's not so helpful. We didn't give Python an ideas about how to display our new `Car` objects, so it defaults to showing us the Class and the object's address in memory. Can we?\n", 166 | "\n", 167 | "You betcha.\n", 168 | "\n", 169 | "To do so, we need to take advantage of what `print()` actually does in the background. \n", 170 | "\n", 171 | "Back to the dunders! It turns out that every built-in Python object also has a built-in `__str__()` method that returns a string representation of the object. That's what `print()` is accessing.\n", 172 | "\n", 173 | "Don't believe me? Watch this:" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 5, 179 | "id": "68bc7589-21d7-4bfb-af63-17c764007fca", 180 | "metadata": {}, 181 | "outputs": [ 182 | { 183 | "data": { 184 | "text/plain": [ 185 | "'3.14'" 186 | ] 187 | }, 188 | "execution_count": 5, 189 | "metadata": {}, 190 | "output_type": "execute_result" 191 | } 192 | ], 193 | "source": [ 194 | "# Floats have...methods???\n", 195 | "3.14.__str__()" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "id": "71845433-b2c3-4982-aa5e-3d747f2cd42f", 201 | "metadata": {}, 202 | "source": [ 203 | "And so, if we provide a method with that name in our class, Python will know what to do. Let's rebuild the `Car` class with this in mind:" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": 6, 209 | "id": "25d1a325-8787-413e-aac0-edf98d838ba7", 210 | "metadata": {}, 211 | "outputs": [], 212 | "source": [ 213 | "# Our first class! How exciting!\n", 214 | "class Car:\n", 215 | " \n", 216 | " def __init__(self, year, make, model, top_speed):\n", 217 | " self.year = year\n", 218 | " self.make = make\n", 219 | " self.model = model\n", 220 | " self.top_speed = top_speed \n", 221 | " \n", 222 | " # All object methods must take self as an arg\n", 223 | " def __str__(self):\n", 224 | " return f\"I am a {self.year} {self.make} {self.model} that can go up to {self.top_speed} mph.\"\n", 225 | " \n", 226 | "# Let's also reinstantiate our cars\n", 227 | "kitt = Car(1982, \"Pontiac\", \"Firebird\", 124)\n", 228 | "\n", 229 | "ecto = Car(1959, \"Cadillac\", \"Professional Chassis\", 75)" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 7, 235 | "id": "5493385c-2f1e-4ff9-b755-d51bc6edea22", 236 | "metadata": {}, 237 | "outputs": [ 238 | { 239 | "name": "stdout", 240 | "output_type": "stream", 241 | "text": [ 242 | "I am a 1982 Pontiac Firebird that can go up to 124 mph.\n", 243 | "I am a 1959 Cadillac Professional Chassis that can go up to 75 mph.\n" 244 | ] 245 | } 246 | ], 247 | "source": [ 248 | "# And now, let's see 'em.\n", 249 | "print(kitt)\n", 250 | "print(ecto)" 251 | ] 252 | }, 253 | { 254 | "cell_type": "markdown", 255 | "id": "cab92de0-fdf4-48ae-8e90-b2c4ec5dce71", 256 | "metadata": {}, 257 | "source": [ 258 | "## Methods\n", 259 | "\n", 260 | "**Methods**, which we've already kinda seen with `__str__()`, are things that an object can _do_. I like to think of properties and classes like stats and abilities for an RPG character.\n", 261 | "\n", 262 | "Methods are functions attached to an object. That means each instance of the object will have that function available. Let's once more rebuild our `Car` class, but this time, give it some more functionality.\n", 263 | "\n", 264 | "To do this, we'll add a `current_speed` property, and a `gas()` and `brake()` method, which will increase or decrease the speed, respectively.\n", 265 | "\n", 266 | "For `gas()`, we'll use a built-in constant of `10` for our acceleration rate to make it easy. But we will have to make sure we don't exceed the car's `top_speed`. And for `brake()`, we'll have to make sure we don't decrease below `0`!" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 8, 272 | "id": "6e2ac634-dbac-4f2c-b5fe-57c9840787c6", 273 | "metadata": {}, 274 | "outputs": [], 275 | "source": [ 276 | "# Adding real methods\n", 277 | "class Car:\n", 278 | " \"\"\"\n", 279 | " A simple car. Can accelerate and decelerate.\n", 280 | " \n", 281 | " Parameters\n", 282 | " ----------\n", 283 | " \n", 284 | " year: int\n", 285 | " Year the car was made\n", 286 | " make: str\n", 287 | " Manufacturer\n", 288 | " model: str\n", 289 | " Model name\n", 290 | " top_speed: int\n", 291 | " Top speed in miles per hour\n", 292 | " \"\"\"\n", 293 | " \n", 294 | " \n", 295 | " def __init__(self, year, make, model, top_speed):\n", 296 | " self.year: int = year\n", 297 | " self.make: str = make\n", 298 | " self.model: str = model\n", 299 | " self.top_speed: int = top_speed \n", 300 | " self.current_speed: int = 0\n", 301 | " \n", 302 | " # All object methods must take self as an arg\n", 303 | " def __str__(self):\n", 304 | " return f\"I am a {self.year} {self.make} {self.model} that can go up to {self.top_speed} mph.\"\n", 305 | " \n", 306 | " def gas(self) -> int:\n", 307 | " \"\"\"\n", 308 | " Increases current speed safely.\n", 309 | " \"\"\"\n", 310 | " self.current_speed += 10\n", 311 | " # Sanity check for top speed\n", 312 | " if self.current_speed > self.top_speed:\n", 313 | " self.current_speed = self.top_speed\n", 314 | " return self.current_speed\n", 315 | " \n", 316 | " def brake(self) -> int:\n", 317 | " \"\"\"\n", 318 | " Decreases current speed safely\n", 319 | " \"\"\"\n", 320 | " self.current_speed -= 10\n", 321 | " if self.current_speed < 0:\n", 322 | " self.current_speed = 0\n", 323 | " return self.current_speed\n", 324 | " \n", 325 | "# Let's also reinstantiate our cars\n", 326 | "kitt = Car(1982, \"Pontiac\", \"Firebird\", 124)\n", 327 | "\n", 328 | "ecto = Car(1959, \"Cadillac\", \"Professional Chassis\", 75)" 329 | ] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "id": "3025d93f-5923-47f5-b563-479d0c522c66", 334 | "metadata": {}, 335 | "source": [ 336 | "### Docstrings\n", 337 | "\n", 338 | "You might have noticed a new kind of comment in our methods and beneath our class definition. Triple-quoted comments in Python are multi-line. You can have them anywhere you like in code. But if you put them directly underneath a function definition or class definition, you have yourself a **docstring**. These explain how to use the class/function. The details on styling these comments are somewhat involved, and I encourage you to check out [this excellent guide from Pandas](https://pandas.pydata.org/docs/development/contributing_docstring.html).\n", 339 | "\n", 340 | "We can access this information at any time with the built-in `help()` function. But in Jupyter, we can also use the `?` shortcut." 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": 9, 346 | "id": "7d56ee27-456a-48c1-99ce-63f176ae0d91", 347 | "metadata": {}, 348 | "outputs": [ 349 | { 350 | "name": "stdout", 351 | "output_type": "stream", 352 | "text": [ 353 | "Help on class Car in module __main__:\n", 354 | "\n", 355 | "class Car(builtins.object)\n", 356 | " | Car(year, make, model, top_speed)\n", 357 | " | \n", 358 | " | A simple car. Can accelerate and decelerate.\n", 359 | " | \n", 360 | " | Parameters\n", 361 | " | ----------\n", 362 | " | \n", 363 | " | year: int\n", 364 | " | Year the car was made\n", 365 | " | make: str\n", 366 | " | Manufacturer\n", 367 | " | model: str\n", 368 | " | Model name\n", 369 | " | top_speed: int\n", 370 | " | Top speed in miles per hour\n", 371 | " | \n", 372 | " | Methods defined here:\n", 373 | " | \n", 374 | " | __init__(self, year, make, model, top_speed)\n", 375 | " | Initialize self. See help(type(self)) for accurate signature.\n", 376 | " | \n", 377 | " | __str__(self)\n", 378 | " | Return str(self).\n", 379 | " | \n", 380 | " | brake(self) -> int\n", 381 | " | Decreases current speed safely\n", 382 | " | \n", 383 | " | gas(self) -> int\n", 384 | " | Increases current speed safely.\n", 385 | " | \n", 386 | " | ----------------------------------------------------------------------\n", 387 | " | Data descriptors defined here:\n", 388 | " | \n", 389 | " | __dict__\n", 390 | " | dictionary for instance variables (if defined)\n", 391 | " | \n", 392 | " | __weakref__\n", 393 | " | list of weak references to the object (if defined)\n", 394 | "\n" 395 | ] 396 | } 397 | ], 398 | "source": [ 399 | "# Get help for the car\n", 400 | "help(Car)" 401 | ] 402 | }, 403 | { 404 | "cell_type": "code", 405 | "execution_count": 10, 406 | "id": "2b5115d3-ff4d-4177-ac00-d2b40e9342e6", 407 | "metadata": {}, 408 | "outputs": [ 409 | { 410 | "data": { 411 | "text/plain": [ 412 | "\u001b[0;31mInit signature:\u001b[0m \u001b[0mCar\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0myear\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmake\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtop_speed\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 413 | "\u001b[0;31mDocstring:\u001b[0m \n", 414 | "A simple car. Can accelerate and decelerate.\n", 415 | "\n", 416 | "Parameters\n", 417 | "----------\n", 418 | "\n", 419 | "year: int\n", 420 | " Year the car was made\n", 421 | "make: str\n", 422 | " Manufacturer\n", 423 | "model: str\n", 424 | " Model name\n", 425 | "top_speed: int\n", 426 | " Top speed in miles per hour\n", 427 | "\u001b[0;31mType:\u001b[0m type\n", 428 | "\u001b[0;31mSubclasses:\u001b[0m \n" 429 | ] 430 | }, 431 | "metadata": {}, 432 | "output_type": "display_data" 433 | } 434 | ], 435 | "source": [ 436 | "# Jupyer style\n", 437 | "Car?" 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": 11, 443 | "id": "15d22997-5dbb-487b-93fb-bb27c6228bae", 444 | "metadata": {}, 445 | "outputs": [ 446 | { 447 | "data": { 448 | "text/plain": [ 449 | "\u001b[0;31mSignature:\u001b[0m \u001b[0mecto\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgas\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 450 | "\u001b[0;31mDocstring:\u001b[0m Increases current speed safely.\n", 451 | "\u001b[0;31mFile:\u001b[0m /tmp/ipykernel_6767/4174123333.py\n", 452 | "\u001b[0;31mType:\u001b[0m method\n" 453 | ] 454 | }, 455 | "metadata": {}, 456 | "output_type": "display_data" 457 | } 458 | ], 459 | "source": [ 460 | "# And for specific methods\n", 461 | "ecto.gas?" 462 | ] 463 | }, 464 | { 465 | "cell_type": "markdown", 466 | "id": "a9c29f7e-10ba-4d84-81f2-a65f9d429327", 467 | "metadata": {}, 468 | "source": [ 469 | "Okay with the docstrings sorted, let's actually use our new methods!" 470 | ] 471 | }, 472 | { 473 | "cell_type": "code", 474 | "execution_count": 12, 475 | "id": "dde06543-de74-4103-8250-f8c39ad940f1", 476 | "metadata": {}, 477 | "outputs": [ 478 | { 479 | "name": "stdout", 480 | "output_type": "stream", 481 | "text": [ 482 | "GO!\n", 483 | "Ecto: 10 | Kitt: 10\n", 484 | "Ecto: 20 | Kitt: 20\n", 485 | "Ecto: 30 | Kitt: 30\n", 486 | "Ecto: 40 | Kitt: 40\n", 487 | "Ecto: 50 | Kitt: 50\n", 488 | "Ecto: 60 | Kitt: 60\n", 489 | "Ecto: 70 | Kitt: 70\n", 490 | "Ecto: 75 | Kitt: 80\n", 491 | "Ecto: 75 | Kitt: 90\n", 492 | "Ecto: 75 | Kitt: 100\n", 493 | "Ecto: 75 | Kitt: 110\n", 494 | "Ecto: 75 | Kitt: 120\n", 495 | "Ecto: 75 | Kitt: 124\n", 496 | "STOP!\n", 497 | "Ecto: 65 | Kitt: 114\n", 498 | "Ecto: 55 | Kitt: 104\n", 499 | "Ecto: 45 | Kitt: 94\n", 500 | "Ecto: 35 | Kitt: 84\n", 501 | "Ecto: 25 | Kitt: 74\n", 502 | "Ecto: 15 | Kitt: 64\n", 503 | "Ecto: 5 | Kitt: 54\n", 504 | "Ecto: 0 | Kitt: 44\n", 505 | "Ecto: 0 | Kitt: 34\n", 506 | "Ecto: 0 | Kitt: 24\n", 507 | "Ecto: 0 | Kitt: 14\n", 508 | "Ecto: 0 | Kitt: 4\n", 509 | "Ecto: 0 | Kitt: 0\n" 510 | ] 511 | } 512 | ], 513 | "source": [ 514 | "# Set speed for replays\n", 515 | "ecto.current_speed = 0\n", 516 | "kitt.current_speed = 0\n", 517 | "\n", 518 | "# Accelerate both cars and see what happens\n", 519 | "print(\"GO!\")\n", 520 | "for i in range(13):\n", 521 | " ecto.gas()\n", 522 | " kitt.gas()\n", 523 | " print(f\"Ecto: {ecto.current_speed} | Kitt: {kitt.current_speed}\")\n", 524 | " \n", 525 | "# And decelerate\n", 526 | "print(\"STOP!\")\n", 527 | "for i in range(13):\n", 528 | " ecto.brake()\n", 529 | " kitt.brake()\n", 530 | " print(f\"Ecto: {ecto.current_speed} | Kitt: {kitt.current_speed}\")\n", 531 | " " 532 | ] 533 | }, 534 | { 535 | "cell_type": "markdown", 536 | "id": "7d86387c-a93b-4c0a-a133-de9c330df963", 537 | "metadata": {}, 538 | "source": [ 539 | "### Extra Credit!\n", 540 | "\n", 541 | "If you're so inclined, modify the `Car` class to include an `accel_rate` and `decel_rate` property, and then make the `gas()` and `brake()` methods reference these properties. Then race your cars!" 542 | ] 543 | }, 544 | { 545 | "cell_type": "markdown", 546 | "id": "e3797a65-da8c-48ab-9e0d-09e87f3d7387", 547 | "metadata": {}, 548 | "source": [ 549 | "## Inheritance\n", 550 | "\n", 551 | "Since objects are a way to represent how humans reason about the world, we should also think a little about hierarchies. A `Car`, for example, is a type of vehicle. We haven't made a `Vehicle` class, but if we did, `Car` migh well be a **subclass** of it. Python allows us to define super/sub classes and allow what's known as **inheritance**. Inheritance means subclasses can _inherit_ the properties/methods of their parents.\n", 552 | "\n", 553 | "To demonstrate this, let's bring this back to the realm of cyber defense with a useful class: `Indicator`s.\n", 554 | "\n", 555 | "Atomic Indicators of Compromise like domain names and IP addresses are not high-fidelity, but will be routinely available and worth using in many situations. To represent them in Python, we'll make a base `Indicator` class, then 3 subclasses for `IPv4Indicator`, `URLIndicator`, and `DomainIndicator`. We'll also create a `defang()` method that may be handy for safely passing indicators around to teammates and documentation.\n", 556 | "\n", 557 | "To start, let's build the base class." 558 | ] 559 | }, 560 | { 561 | "cell_type": "code", 562 | "execution_count": 13, 563 | "id": "b8377a80-0fc8-44ef-aa8f-74bf72f9e981", 564 | "metadata": {}, 565 | "outputs": [], 566 | "source": [ 567 | "class Indicator:\n", 568 | " \"\"\"\n", 569 | " Atomic Indicators of Compromise.\n", 570 | " \n", 571 | " Parameters\n", 572 | " ----------\n", 573 | " \n", 574 | " value: str\n", 575 | " Value of indicator\n", 576 | " \"\"\"\n", 577 | " \n", 578 | " def __init__(self, value):\n", 579 | " self.value: str = value\n", 580 | " \n", 581 | " def defang(self) -> str:\n", 582 | " \"\"\"\n", 583 | " Defangs the indicator\n", 584 | " \n", 585 | " Implemented in subclasses\n", 586 | " \"\"\"\n", 587 | " pass" 588 | ] 589 | }, 590 | { 591 | "cell_type": "markdown", 592 | "id": "3ca37f4f-4eb9-4560-aebc-8c0e9ae29b1a", 593 | "metadata": {}, 594 | "source": [ 595 | "Not a lot going on, but it's a start. Now let's build one of our subclasses.\n", 596 | "\n", 597 | "To properly inherit, not only will we need to add something to our `class` line, but we'll also need to take advantage of the built-in `super()` function, which returns the parent class. From there, we can access it's `__init__()` constructor and pass in our own constructor's arguments to inherit properties without reinventing them." 598 | ] 599 | }, 600 | { 601 | "cell_type": "code", 602 | "execution_count": 14, 603 | "id": "3304fc69-8b15-4ac3-ad59-2f78a3a14ef5", 604 | "metadata": {}, 605 | "outputs": [], 606 | "source": [ 607 | "# Notice the parens? Parens for Parents!\n", 608 | "class IPv4Indicator(Indicator):\n", 609 | " \n", 610 | " def __init__(self, value):\n", 611 | " # Instantiate parent properties/methods\n", 612 | " super().__init__(value)\n", 613 | " \n", 614 | " # Overwrite the `defang()` method for our purposes\n", 615 | " def defang(self) -> str:\n", 616 | " \"\"\"\n", 617 | " Defangs the indicator\n", 618 | " \n", 619 | " Brackets the dots\n", 620 | " \"\"\"\n", 621 | " return self.value.replace(\".\", \"[.]\")" 622 | ] 623 | }, 624 | { 625 | "cell_type": "code", 626 | "execution_count": 15, 627 | "id": "4340a84d-15b6-43a2-a3a1-18a0a761848c", 628 | "metadata": {}, 629 | "outputs": [ 630 | { 631 | "data": { 632 | "text/plain": [ 633 | "'192[.]168[.]1[.]11'" 634 | ] 635 | }, 636 | "execution_count": 15, 637 | "metadata": {}, 638 | "output_type": "execute_result" 639 | } 640 | ], 641 | "source": [ 642 | "# Make a bad IP and defang it\n", 643 | "bad_ip = IPv4Indicator(\"192.168.1.11\")\n", 644 | "bad_ip.defang()" 645 | ] 646 | }, 647 | { 648 | "cell_type": "markdown", 649 | "id": "c8b72f1e-f2e6-4bed-9fe8-57b7b8058892", 650 | "metadata": {}, 651 | "source": [ 652 | "Let's do one more together, then it's up to you. We'll do `DomainIndicator`." 653 | ] 654 | }, 655 | { 656 | "cell_type": "code", 657 | "execution_count": 16, 658 | "id": "dbf6e178-98bd-4e6c-a487-f19b41b9e86a", 659 | "metadata": {}, 660 | "outputs": [], 661 | "source": [ 662 | "# Notice the parens? Parens for Parents!\n", 663 | "class DomainIndicator(Indicator):\n", 664 | " \n", 665 | " def __init__(self, value):\n", 666 | " # Instantiate parent properties/methods\n", 667 | " super().__init__(value)\n", 668 | " \n", 669 | " # Overwrite the `defang()` method for our purposes\n", 670 | " def defang(self) -> str:\n", 671 | " \"\"\"\n", 672 | " Defangs the indicator\n", 673 | " \n", 674 | " Brackets the dots\n", 675 | " \"\"\"\n", 676 | " return self.value.replace(\".\", \"[.]\")" 677 | ] 678 | }, 679 | { 680 | "cell_type": "code", 681 | "execution_count": 17, 682 | "id": "2a2f2ab1-a395-461a-b01b-c8ddd2d5fa8f", 683 | "metadata": {}, 684 | "outputs": [ 685 | { 686 | "data": { 687 | "text/plain": [ 688 | "'evil[.]online'" 689 | ] 690 | }, 691 | "execution_count": 17, 692 | "metadata": {}, 693 | "output_type": "execute_result" 694 | } 695 | ], 696 | "source": [ 697 | "# Make a bad IP and defang it\n", 698 | "bad_domain = DomainIndicator(\"evil.online\")\n", 699 | "bad_domain.defang()" 700 | ] 701 | }, 702 | { 703 | "cell_type": "markdown", 704 | "id": "22d4bb06-b6db-40e8-8aba-f0c348186719", 705 | "metadata": {}, 706 | "source": [ 707 | "## Check For Understanding\n", 708 | "\n", 709 | "Time to write a subclass!\n", 710 | "\n", 711 | "### Objectives\n", 712 | "\n", 713 | "1. Create a `URLIndicator` subclass of `Indicator`. This is for full URLs like `https://github.com/mttaggart/OffensiveNotion`.\n", 714 | "2. Modify the `defang()` method to not just bracket dots, but replace `http` with `hxxp` in the URL scheme.\n", 715 | "3. Create an `evil_url` object that's an instance of your new `URLIndicator` class.\n", 716 | "4. Send the `evil_url` to `testme()`." 717 | ] 718 | }, 719 | { 720 | "cell_type": "code", 721 | "execution_count": null, 722 | "id": "22b4d80e-c44b-462d-b03a-5f3f1981d281", 723 | "metadata": {}, 724 | "outputs": [], 725 | "source": [ 726 | "# Don't delete this!\n", 727 | "from testme import *\n", 728 | "\n", 729 | "# Create a URLIndicator subclass of Indicator\n", 730 | "class ______(_____):\n", 731 | " \n", 732 | " # Modify the defang() method to not just bracket dots, but replace http with hxxp in the URL scheme.\n", 733 | " pass\n", 734 | "\n", 735 | "evil_url = ____()\n", 736 | "\n", 737 | "# Send the evil_url to testme(). Leave the other args alone.\n", 738 | "testme(___, Indicator, URLIndicator)" 739 | ] 740 | } 741 | ], 742 | "metadata": { 743 | "kernelspec": { 744 | "display_name": "Python 3 (ipykernel)", 745 | "language": "python", 746 | "name": "python3" 747 | }, 748 | "language_info": { 749 | "codemirror_mode": { 750 | "name": "ipython", 751 | "version": 3 752 | }, 753 | "file_extension": ".py", 754 | "mimetype": "text/x-python", 755 | "name": "python", 756 | "nbconvert_exporter": "python", 757 | "pygments_lexer": "ipython3", 758 | "version": "3.8.10" 759 | } 760 | }, 761 | "nbformat": 4, 762 | "nbformat_minor": 5 763 | } 764 | -------------------------------------------------------------------------------- /1-10_classes/testme.py: -------------------------------------------------------------------------------- 1 | def testme(evil_url, indicator_class, url_indicator_class): 2 | 3 | print("[+] Testing for class inheritance") 4 | if isinstance(evil_url, indicator_class): 5 | print("[+] object is an Indicator") 6 | else: 7 | print("[-] object is not an Indicator! It needs to inherit from that parent class!") 8 | return 9 | print("[+] Testing for URLIndicatorness") 10 | try: 11 | if isinstance(evil_url, url_indicator_class): 12 | print("[+] object is a URLIndicator") 13 | else: 14 | print("[-] object is not a URLIndicator!") 15 | return 16 | except: 17 | print("[!] Whoah! Did you make sure your class is named correctly?") 18 | return 19 | 20 | print("[+] Testing for defanging") 21 | 22 | try: 23 | if evil_url.defang() == evil_url.value.replace("http","hxxp").replace(".","[.]"): 24 | print("[+] Defanging successful!") 25 | else: 26 | print("[-] Defanging didn't seem to work.") 27 | except: 28 | print("[!] Whoah! Did you make sure your class has a defang method?") 29 | return 30 | -------------------------------------------------------------------------------- /1-11_modules/1-11_modules.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "f2b4cf6e-569c-44c4-94b0-e5840bdee085", 6 | "metadata": {}, 7 | "source": [ 8 | "# 1-11: Modules\n", 9 | "\n", 10 | "So far, we have stuck to Python code that we've written ourselves, or functions built in to Python. But there's a wide universe of Python code out there to explore and take advantage of. We may even want to package some of our code the same way. That's why we need to understand Python **modules**.\n", 11 | "\n", 12 | "Simply put, modules are packages of Python code that we can **import** into our project or notebook.\n", 13 | "\n", 14 | "We'll begin with an out-of-the-box Python module, like `random`." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "id": "ecc18e1c-9a3b-4a6d-ab34-c8b89ae2ac40", 20 | "metadata": {}, 21 | "source": [ 22 | "## Syntax\n", 23 | "\n", 24 | "Python has plenty of additional modules, which you can review in the [Library Reference](https://docs.python.org/3/library/index.html). We'll play with `random` for the moment.\n", 25 | "\n", 26 | "To import an entire module, we can simply `import` the module name." 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "3eee3be6-bc7a-4fca-9dac-1d4ee229b93f", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "# Import random\n", 37 | "import random\n", 38 | "\n", 39 | "# What's your deal, random?\n", 40 | "random?" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "id": "c0781adb-f78d-45e5-903a-e19ee4482c98", 46 | "metadata": {}, 47 | "source": [ 48 | "The `random` module is useful for, y'know, random stuff. Like random integers/floats, or even a random choice from a sequence.\n", 49 | "\n", 50 | "When the entire module is imported, its constants and functions reside under the `random` **namespace**. That means to access the `choice()` function, we would refer to `random.choice()`." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "id": "96f9433c-d45d-4ce1-a3f1-f1a6a32e6d91", 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "# A random, namespaced choice\n", 61 | "random.choice([\"Klingons\", \"Romulans\", \"Borg\", \"Oh my!\"])" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "id": "0076e5fa-f6d4-422e-93e0-467f6ae15e1f", 67 | "metadata": {}, 68 | "source": [ 69 | "And that's fine, but we may want to be more specific and less verbose with our imports. To do that, we can use the `from...import` syntax. Instead of importing everything, we just import what we want. When constants and functions are imported this way, they are not namespaced—instead, they are directly available." 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "id": "720ff753-4e3a-4867-945a-d2bedce1bda3", 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "# Instead of importing everything, just get what we need.\n", 80 | "from random import choice\n", 81 | "\n", 82 | "# Look Ma! No namespace!\n", 83 | "choice([\"Klingons\", \"Romulans\", \"Borg\", \"Oh my!\"])" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "id": "5ecadbd2-049d-46db-affb-7c9c21cdd1b4", 89 | "metadata": {}, 90 | "source": [ 91 | "## Installing Modules\n", 92 | "\n", 93 | "As rad as Python is out of the box, odds are you're going to want to install somebody else's code eventually. It's not hard! In fact, you can even do it from directly within a Notebook if you want.\n", 94 | "\n", 95 | "This is kind of an aside, but a cool trick that Jupyter can do is executing shell commands from within a notebook. All it takes is prepending the command with a `!` (bang). Watch:" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "cb6bd50e-2296-406b-8c2e-6575d46fe6b7", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "# List the contents of our repo\n", 106 | "! ls ../" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "id": "2cee6872-75ce-4e5f-995e-bab7807c56c3", 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "# We can even save the result to a variable!\n", 117 | "stuff: list = ! ls ../\n", 118 | "stuff" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "id": "07977b1d-5773-4f89-a29f-87f91a0e1101", 124 | "metadata": {}, 125 | "source": [ 126 | "So that's amazing, right? More on that later. But for our purposes, that means we can install packages directly from notebooks using the `pip3` package manager. As an example, let's say we wanted to install the `requests` module for interacting with the web. We can simply enter `! pip3 install requests`" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": null, 132 | "id": "a74ba0cf-c6f0-4087-ba14-57c624e4167e", 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "! pip3 install requests" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "id": "97167b7f-a351-4638-9ad1-cd3b3cfc0410", 142 | "metadata": {}, 143 | "source": [ 144 | "In our case it was already there, but the principle is sound. Once installed we can import whatever we need." 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "id": "71a9e2a8-420c-4a94-8920-2c35dec8fe94", 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "# A very naive web request\n", 155 | "import requests\n", 156 | "\n", 157 | "r = requests.get(\"https://taggart-tech.com\")\n", 158 | "r.text" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "id": "05479180-1994-4dc4-b1f1-9054c781f9aa", 164 | "metadata": {}, 165 | "source": [ 166 | "## Creating Modules\n", 167 | "\n", 168 | "Not all our Python code has to live in the Notebook. In fact, it's probably a good idea that most utility functions, classes, etc. live elsewhere. A good rule of thumb is: if the code should be repeated by users on each run of the notebook, leave it in. If however, the code never changes and just needs to exist for the Notebook to work properly, get it out.\n", 169 | "\n", 170 | "The good news is, basic module creation is as simple as making a new `.py` file and chucking your code in there. You can them import with `import filename` without the extension. Just try not to use the same filename as a real module you'll also want to import, or a function you're already using. For example, do not under any circumstances make a `print.py` file. \n", 171 | "\n", 172 | "```python\n", 173 | "\n", 174 | "import print\n", 175 | "\n", 176 | "# Sure you have a stuff function in there, but AT WHAT COST?!\n", 177 | "print.stuff()\n", 178 | "\n", 179 | "print(\"This won't work now\")\n", 180 | "```\n", 181 | "\n", 182 | "You'll have a bad time." 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "id": "bb59620b-892e-4125-9e6d-7aac5a140caa", 188 | "metadata": {}, 189 | "source": [ 190 | "### `__init__.py`\n", 191 | "\n", 192 | "Hey look! Dunders again! \n", 193 | "\n", 194 | "If you want to make a folder full of Python files but only import with a single statement, it can be helpful to use the [Package structure](https://docs.python.org/3/tutorial/modules.html#packages). There's a lot to this, but broadly, `__init__.py` inside a folder will allow you to group your Python files into a single import. It can also help structure subfolder for dot-notation import. \n", 195 | "\n", 196 | "But that's all a bit advanced. For now, let's stick to single-file imports.\n", 197 | "\n" 198 | ] 199 | }, 200 | { 201 | "attachments": { 202 | "e85427c1-9d69-4841-a4d5-62456d2fd7a1.png": { 203 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHMAAAB0CAYAAAC2Rg1eAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAdNSURBVHhe7Z1fbFNVHMd/bUc3uz9uy9xkMOaDsgQMGgYIiiYSHzCoiS8mxgQNEQEhPumDog40xAd9MuIfSEbARBMS/4FDjAqGmIjAENGoQx9EENxcYK7bZIN23u/pPaXt2t3uX3vOr79PcnZvz71t7+6n53fO+d3bzVdRM32YBBb43aXAAJHJCJHJCJHJCJHJCJHJCM+pic/nc9eEfDA8nP3MMa3MmECnWmagZqDak89T7AiZEFkSKqey6lry+wNurZBPotEI9V3ooksD4VGFJslULdLZuabhRurpOkuXBy+5W4R8Mq24hCprZ1L3md8hKaPQlAHQsBKKFikizQEu4CTe/WUgWaaz31g6XCG3KDej6InLjFkfdV8hz2g32lUqI+aZmXYU8o+XG0kaMEJkMkJkMkJkMkJkMkJkMkJkMkJkMkJkMiJ+1URlF5D7c5a1s2ZT5+kOtYMtBPxFFLqmlAKBABX5AxQoKnK3XGVwaJAikQgNXh6koSG7LiTUNTZR15+n4o7S5dCtb5mQWFlRTXU106m8tIJCJaUUDJao+tSCbdinpvI6Z3mt+wp8sFomBEEiJI0VSMWHgBNWy6yqqHLXxoduxVywVuZkiSieVuyu2Y+1MoNBPhImC+sHQMJVrJWJKQamF14lEr3iPoM/1soM9/9L3T3/eJbIFZFZkGBQlVgw9bEJozNAarJfVDRixIksjxfIAgGvEW+4v1e1cj1nTQQhGlmjnt4Lbk3+sDoDBAk4ubFsTUVSSW1B6QqeP9Gpi84a2ZJcMFImJECiKegPh+kYKTNUEnLXzKEiVO6umYuRMosNTAikuwpjGgU/msXlMGCDLC8KWqZOLAA9+rWZgpWJKQmSChoT++mxwkIm5oMDl/qzKpDY2X1ezS01kzGNMQGrZUIiWhfkYGKfTYHExHwt5pI2jFSzwVqZ6OsgUfd54wEicYGbQ6sE1srsHQi7azEgBvf1oCBj41XqaxtUhomLSGClTPR9iS0SAvUNXdmm+zhipcyhodjcEOjcrWCpzCvRiFqihXEKkxPFSJleF5T19kAgd1kbGy5yWzsAyjU6GpiMkTJTR6omEO7rddfMxUiZGKkiU2MK6l4iC24MMzbMDvwXS71NFHwwxlMSU394bAPGfwsMyYDU65s40SCWJMg8LYGExGS6zbD4Fli6JLqQHtajWVxwRlLBq3D5eh9vmU6I1pe3RivZ3LppA6xlFhoi0yEx12szVsvUN2NNBD3A4oDVMvV8cCJc7L3ortmP9WEWabbxJBfwQbApIZAN1stEmMR9PbGbtHpVS4Ug1CcW3Yp1VseWFN1YYPN3gLhTEH8HSLiKyGSEyGSEyGSEyGSEyGSEyGSEyGSEyGSEyGSEyGSEyGSEyGSEyGSEyGSEyGREzmVuf+sNOtl+OF7weCysX/sEtX3yAS1a2OzWTC73r7iXvj10IH587Ye/Ue+J4/xo9/tqH6yP9bhzQV5a5ndHjtG85sX0+Nr1dENjI21+YaO7ZSSQBnk4obmir6+fNr64WR1j8+KltPXtbbR63QZ68KGH3T3MJK9h9sjRdvrj9Gmqr0/+o73C+DCiz5wxo54OffV5UutDSPtw93u0ZfMmapg5g9asXhUPc2DDujVJYVCDfXSI/GLf3ng4Rljc1bpN1aVuy4ZMoRWvoV8TZbQoM9XkVSb6p6bZN1Hbvv3Uceo3uvWWefH6srIy2rHzXdrYsonOnP2L3tneGg9z19fV0eDgkAqDx78/Qfcsu1vVa9moR0Grf/aZp1UdmDtnDn28Z68K72DF8uVqmUpZWSltealFyUH/ieNJB0S2PP+cek28H0Lz7UsWZ9x/qsmLzNsWLVAnCifs4NeHVJ904oeTKtziBC2YP9/pt/pob9tn7jOS+buzk7a37lDreF6wOEiPrXxEPW7duUstwZ5P29SHQp9ciMd7eYX3xD5zyV3LMh5HXW0tVVdVqaihf5/q6iqa1dDg7pFb8joAQml5eYuqw0kOh/toYXMzNTXNpi8PHFT1ppMoHkUPmPKBEX2mpqPjFN259A4qDgbpaHu7W5sdP//yq1quenSlWoIH7lsxagufKJ1dXWqJ9zEBo2QeO36cGmc10Mkff1KhEGB57tz5EQOgdLzy6msqrOrBCKY9qJsqcGyvb32Tbp47J/6eYx1YTSZG3dGOvu2p9U+qEzRVrclWrLujfarDIneMkIkWiSnAVIdF7sgXhyxBvjhUYIhMRohMRohMRohMRohMRohMRohMRohMRohMRohMRohMRohMRohMRoyQme7SimAGXm7iMhN3jEYjNK1Y/mGaKcAFnGgySY1fnAa4Pk3Oo1B5JYUqa8jP4D+kcwAiB3q6aSDc40iCTHdDCikyfeTDns7S73fW8Rh2hbwxDAOOk2jU8eAshzPcZQCSZAJIhD80UoCNqsUKOUe1K72OH84DJTUDI2SCmFDnU5D5eUIOUY3J+TGaSJBWZiLqRi8hb3iNYBPxlCnYAtH/LUiuwZds5JwAAAAASUVORK5CYII=" 204 | } 205 | }, 206 | "cell_type": "markdown", 207 | "id": "3beb53b8-6ded-4fb0-884c-0f00db3f3bb0", 208 | "metadata": {}, 209 | "source": [ 210 | "## Check For Understanding Modularizing `Indicator`\n", 211 | "\n", 212 | "Hey remember our `Indicator` classes from last module? Those were the days. Anyway, to make that code more useful (and take up less space in our notebook), let's turn all that into a module. \n", 213 | "\n", 214 | "This begins your **Check For Understanding** for this lesson, so not a lot has been pregamed for you.\n", 215 | "\n", 216 | "### Objectives\n", 217 | "\n", 218 | "1. Create an `indicator.py` file in this directory. You can use the built-in editor in JupyterLab!\n", 219 | "\n", 220 | "![image.png](attachment:e85427c1-9d69-4841-a4d5-62456d2fd7a1.png)\n", 221 | "\n", 222 | "2. Copy the `Indicator` class and all the subclasses we built in the last lesson into that file. So you should have an `Indicator`, `URLIndicator`, `IPv4Indicator`, and `DomainIndicator` class in there.\n", 223 | "\n", 224 | "3. Run `testme()` to confirm you got everything in there." 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "id": "7420bcf7-3430-45e3-b53b-30093e478bfe", 231 | "metadata": {}, 232 | "outputs": [], 233 | "source": [ 234 | "# Don't delete me!\n", 235 | "from testme import testme\n", 236 | "\n", 237 | "testme()" 238 | ] 239 | } 240 | ], 241 | "metadata": { 242 | "kernelspec": { 243 | "display_name": "Python 3 (ipykernel)", 244 | "language": "python", 245 | "name": "python3" 246 | }, 247 | "language_info": { 248 | "codemirror_mode": { 249 | "name": "ipython", 250 | "version": 3 251 | }, 252 | "file_extension": ".py", 253 | "mimetype": "text/x-python", 254 | "name": "python", 255 | "nbconvert_exporter": "python", 256 | "pygments_lexer": "ipython3", 257 | "version": "3.8.10" 258 | } 259 | }, 260 | "nbformat": 4, 261 | "nbformat_minor": 5 262 | } 263 | -------------------------------------------------------------------------------- /1-11_modules/testme.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def testme(): 4 | # Check to see if the file exists 5 | print("[+] Making sure we have indicator.py") 6 | try: 7 | os.stat("indicator.py") 8 | print("[+] Module found") 9 | except FileNotFoundError: 10 | print("[!] Hey what gives?! I don't even have an indicator.py file!") 11 | return 12 | 13 | # Import the stuff we want 14 | print("[+] Importing our classes") 15 | try: 16 | from indicator import URLIndicator, DomainIndicator, IPv4Indicator 17 | except ImportError as e: 18 | print(f"[!] {e}") 19 | return 20 | 21 | # Make sane indicators 22 | print("[+] Making IP indicator") 23 | try: 24 | bad_ip = IPv4Indicator("192.168.99.1") 25 | print(f"[+] {bad_ip.defang()}") 26 | except: 27 | print("[!] Couldn't make/defang IP indicator!") 28 | return 29 | 30 | print("[+] Making domain indicator") 31 | try: 32 | bad_domain = DomainIndicator("evil.com") 33 | print(f"[+] {bad_ip.defang()}") 34 | except: 35 | print("[!] Couldn't make/defang domain indicator!") 36 | return 37 | 38 | print("[+] Making URL indicator") 39 | try: 40 | bad_url = URLIndicator("https://taggart-tech.com") 41 | print(f"[+] {bad_url.defang()}") 42 | except: 43 | print("[!] Couldn't make/defang URL indicator!") 44 | return 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /1-1_cells/1_1-cells.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "fd677004-a15c-4204-adfe-762fc09da96a", 6 | "metadata": {}, 7 | "source": [ 8 | "# 1-1: Cells\n", 9 | "\n", 10 | "In Jupyter, every section of code or writing is known as a **cell**. Cells are moveable and editable. They execute independently of each other, but contribute to the state of the entire Notebook.\n", 11 | "\n", 12 | "This here is a **Markdown** cell. Markdown is a simplified markup syntax that allows easy creation of rich text.\n", 13 | "\n", 14 | "To learn a _lot_ more about Markdown, visit the [commmonmark](https://commonmark.org/) page. Maybe take their tutorial!" 15 | ] 16 | }, 17 | { 18 | "attachments": { 19 | "a41a690b-5fc5-4de7-ab5a-6542e48bc916.png": { 20 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAACEAAAAbCAIAAAB9Z3HzAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAA00lEQVRIS+3VMQ5EUBCA4WE5AFFJJBRqiVC+aziRUzjDnOIVSjdQiE6hICQaUe/Ey87Lxhab95UzI38hwkqSBB5m08EDTIPPNPi4jaqqbJt7THAf832/rus8z+mC4eV5Hp3dKYoiTdOyLIMg6Pv+OA56oabRCMMQAKIoEkJs2zYMAz1S0G4AgOu6WZbFcTyO47qu74c3uO/jGw4dMOz7johSSrpQ0G60bYuIy7LQhZpGY5omROy6ji4+4TbmeW6a5jxPumCwzH+QzTT4/qXh/OD7uACEKTjEO3EymQAAAABJRU5ErkJggg==" 21 | } 22 | }, 23 | "cell_type": "markdown", 24 | "id": "79aeebf4-ed9e-49f1-8f46-ad92e48a0019", 25 | "metadata": {}, 26 | "source": [ 27 | "But of course, not every cell can be plaintext, can it? The next cell is going to be code. Click into, then press the \"play\" button in the top bar, or press `Ctrl+Enter` to run it.\n", 28 | "\n", 29 | "_This is the play button_ ![Screenshot_20220809_194104.png](attachment:a41a690b-5fc5-4de7-ab5a-6542e48bc916.png)" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "fdfd97df-8c28-4c95-9eb6-eb4a8bacead5", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "# See this? It's a comment inside of code. It is just for humans like you.\n", 40 | "# The lines below are for the computer.\n", 41 | "name: str = input(\"Hey! What's your name?\")\n", 42 | "print(\"Nice to meetcha, \" + name)" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "id": "6eb5ae3b-610e-48c5-8ed1-737f37d7fecf", 48 | "metadata": {}, 49 | "source": [ 50 | "So a lot just happened there. Let's break it down piece by piece.\n", 51 | "\n", 52 | "```python\n", 53 | "name: str = input(\"Hey! What's your name?\")\n", 54 | "```\n", 55 | "\n", 56 | "On the left of the `=` is the name of a **variable**—appropriately, called `name`. **Variables** are buckets to hold information that we want to refer to later.\n", 57 | "\n", 58 | "We _assign_ values to variables with the `=` operator. \n", 59 | "\n", 60 | "You'll also notice there's a `: str` on there. That is a **type hint**. It tells us, the programmers, a little about the kind of data stored in the variable. In this case, the hint informs us that `name` contains a string, or `str`. Strings are text data.\n", 61 | "\n", 62 | "Before we go any further, I want to be very clear: **TYPE HINTS ARE OPTIONAL!** The following would have worked just as well:\n", 63 | "\n", 64 | "```python\n", 65 | "name = input(\"Hey! What's your name?\")\n", 66 | "```\n", 67 | "\n", 68 | "Python doesn't care either way. So why use type hints? I am an advocate of type hints because they serve as in-line documentation. They help us understand what kinds of data we're working with as we go. Yeah it's a little more syntax, but I think the clarity is well worth it. And for this course, we'll be using type hints as a standard.\n", 69 | "\n", 70 | "On the right of the `=` is a **function call**. The `input()` function is built into Python, and reads input from the user with the provided prompt. Functions are _called_ with the function name, parentheses, and any **arguments** to the function inside those parens. Once that function has executed, the resulting value is saved in `name`.\n", 71 | "\n", 72 | "```python\n", 73 | "print(\"Nice to meetcha, \" + name)\n", 74 | "```\n", 75 | "\n", 76 | "Finally, we use the `print()` function to print our greeting. The greeting is a combination of 2 strings: `\"Nice to meetcha, \"` and our `name` variable. The `+` operator works on strings in Python as well as numbers. On strings, in _concatenates_, or joins, the strings together. That's why we left that extra space after the comma." 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "id": "0fe839b3-496e-45b1-a28d-4e92a89ed8e0", 82 | "metadata": {}, 83 | "source": [ 84 | "## Your Turn\n", 85 | "\n", 86 | "Now it's your turn! In the next cell, you have 2 objectives:\n", 87 | "\n", 88 | "1. Create a `str` variable called `whiz` and assign it the value `\"Bang!\"`\n", 89 | "2. Call the function `testme()`, passing `whiz` as an argument.\n", 90 | "\n", 91 | "If all goes according to plan, you'll see a passed test!" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "id": "bf8c4fd2-9d5e-47ba-876b-e00df6a469be", 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "# Don't delete this line!\n", 102 | "from testme import testme\n", 103 | "\n", 104 | "# Define a str variable called whiz\n", 105 | "\n", 106 | "whiz = \"\"\n", 107 | "\n", 108 | "# Call testme() with whiz as an argument\n", 109 | "\n", 110 | "______(____)" 111 | ] 112 | } 113 | ], 114 | "metadata": { 115 | "kernelspec": { 116 | "display_name": "Python 3 (ipykernel)", 117 | "language": "python", 118 | "name": "python3" 119 | }, 120 | "language_info": { 121 | "codemirror_mode": { 122 | "name": "ipython", 123 | "version": 3 124 | }, 125 | "file_extension": ".py", 126 | "mimetype": "text/x-python", 127 | "name": "python", 128 | "nbconvert_exporter": "python", 129 | "pygments_lexer": "ipython3", 130 | "version": "3.8.10" 131 | } 132 | }, 133 | "nbformat": 4, 134 | "nbformat_minor": 5 135 | } 136 | -------------------------------------------------------------------------------- /1-1_cells/testme.py: -------------------------------------------------------------------------------- 1 | def testme(whiz): 2 | try: 3 | print(whiz) 4 | if whiz == "Bang!": 5 | print("Congrats! You did it!") 6 | else: 7 | print(f"Hmm. {whiz} isn't quite the value I'm looking for...") 8 | except ValueError: 9 | print("Uh-oh. Did whiz get defined?") -------------------------------------------------------------------------------- /1-2_numbers/1_2-numbers.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "97992163-cfba-49f1-91fe-2d8040fb66d6", 6 | "metadata": {}, 7 | "source": [ 8 | "# 1-2: Python Data Types - Numbers\n", 9 | "\n", 10 | "Before we can dive further into how to use Python, we have to grasp some basics about the language's data types. We've already seen one of them—strings—which we'll return to shortly. But for now, let's focus on numbers.\n", 11 | "\n", 12 | "## Numbers in Python\n", 13 | "\n", 14 | "Technically, numbers in Python come in two flavors: `int` and `float`. Check this out:" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "id": "f6e4fd01-3df8-4b98-8491-c900e5bf05f9", 21 | "metadata": {}, 22 | "outputs": [ 23 | { 24 | "data": { 25 | "text/plain": [ 26 | "int" 27 | ] 28 | }, 29 | "execution_count": 1, 30 | "metadata": {}, 31 | "output_type": "execute_result" 32 | } 33 | ], 34 | "source": [ 35 | "# The type() function can tell us what data type a thing is\n", 36 | "type(3) " 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "id": "5a77ef8e-ba95-4c07-b28d-70ad7c88a41b", 43 | "metadata": {}, 44 | "outputs": [ 45 | { 46 | "data": { 47 | "text/plain": [ 48 | "float" 49 | ] 50 | }, 51 | "execution_count": 2, 52 | "metadata": {}, 53 | "output_type": "execute_result" 54 | } 55 | ], 56 | "source": [ 57 | "type(3.0)" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "id": "cba75f7e-f116-4299-927b-0962db9c0a94", 63 | "metadata": {}, 64 | "source": [ 65 | "As you might have guessed, `int`s are integers, or whole numbers. `float`s are numbers with decimals. But remember that computers have limits to their precision, to irrational numbers get a little...weird. Watch what happens when we divide 10 by 3." 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 3, 71 | "id": "7b2be3f4-0630-4050-b3a0-c4739cff5248", 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "data": { 76 | "text/plain": [ 77 | "3.3333333333333335" 78 | ] 79 | }, 80 | "execution_count": 3, 81 | "metadata": {}, 82 | "output_type": "execute_result" 83 | } 84 | ], 85 | "source": [ 86 | "# / is the division operator\n", 87 | "irrational: float = 10 / 3\n", 88 | "irrational" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "id": "67717184-8326-4c87-b06d-2d7f17773a91", 94 | "metadata": {}, 95 | "source": [ 96 | "Whaaaat? Where'd that `5` come from?!\n", 97 | "\n", 98 | "Essentially when the `float` (short for floating point number) truncates and in so doing, the tail sort of rolls into that last digit. So be aware of your significant figures!\n", 99 | "\n", 100 | "You can round floats to whatever precision you like." 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 4, 106 | "id": "4dec7b82-fa4d-401f-a114-52e4b3371416", 107 | "metadata": {}, 108 | "outputs": [ 109 | { 110 | "data": { 111 | "text/plain": [ 112 | "3.333" 113 | ] 114 | }, 115 | "execution_count": 4, 116 | "metadata": {}, 117 | "output_type": "execute_result" 118 | } 119 | ], 120 | "source": [ 121 | "# Round the result of the division to 3 decimal places\n", 122 | "rounded: float = round(10/3, 3)\n", 123 | "rounded" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "id": "63c8beb1-7124-43b2-b14e-a14ebba496de", 129 | "metadata": {}, 130 | "source": [ 131 | "Some other operations you might need:" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 5, 137 | "id": "8c121162-e4c5-4e93-bc05-4ea64333427e", 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "data": { 142 | "text/plain": [ 143 | "10" 144 | ] 145 | }, 146 | "execution_count": 5, 147 | "metadata": {}, 148 | "output_type": "execute_result" 149 | } 150 | ], 151 | "source": [ 152 | "# Addition\n", 153 | "added: int = 5 + 5\n", 154 | "added" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 6, 160 | "id": "7ea0d840-0e93-42b5-810e-52b99c39b2fd", 161 | "metadata": {}, 162 | "outputs": [ 163 | { 164 | "data": { 165 | "text/plain": [ 166 | "-20" 167 | ] 168 | }, 169 | "execution_count": 6, 170 | "metadata": {}, 171 | "output_type": "execute_result" 172 | } 173 | ], 174 | "source": [ 175 | "# Subtraction\n", 176 | "10 - 30" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 7, 182 | "id": "4af3b839-ce11-42a9-9b59-cb3ef3544058", 183 | "metadata": {}, 184 | "outputs": [ 185 | { 186 | "data": { 187 | "text/plain": [ 188 | "50" 189 | ] 190 | }, 191 | "execution_count": 7, 192 | "metadata": {}, 193 | "output_type": "execute_result" 194 | } 195 | ], 196 | "source": [ 197 | "# Multiplication\n", 198 | "5 * 10" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 8, 204 | "id": "b99bec1a-e330-4999-9d66-2b27365a3948", 205 | "metadata": {}, 206 | "outputs": [ 207 | { 208 | "data": { 209 | "text/plain": [ 210 | "1000" 211 | ] 212 | }, 213 | "execution_count": 8, 214 | "metadata": {}, 215 | "output_type": "execute_result" 216 | } 217 | ], 218 | "source": [ 219 | "# Exponentiation\n", 220 | "10 ** 3" 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": 9, 226 | "id": "b8af371b-f081-4a2e-afab-77ad2f2ed762", 227 | "metadata": {}, 228 | "outputs": [ 229 | { 230 | "data": { 231 | "text/plain": [ 232 | "7.0" 233 | ] 234 | }, 235 | "execution_count": 9, 236 | "metadata": {}, 237 | "output_type": "execute_result" 238 | } 239 | ], 240 | "source": [ 241 | "# Exponentiation can also do roots\n", 242 | "49 ** .5" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "id": "fe84f516-4991-47de-b042-4c0c8500241a", 248 | "metadata": {}, 249 | "source": [ 250 | "And finally, the last one may need a little more explanation. The **modulo** (`%`) operator returns the remainder of integer division. So `10 % 2` would be `0`, since that's a clean divide. But `10 % 7` would yield `3`.\n", 251 | "\n", 252 | "This is a common way to check for evenness/oddness. Moding a number by `2` will yield `0` for even numbers, and 1 for odd." 253 | ] 254 | }, 255 | { 256 | "cell_type": "code", 257 | "execution_count": 10, 258 | "id": "743d3f1b-de2c-46f2-8ca2-cba8407f1c6b", 259 | "metadata": {}, 260 | "outputs": [ 261 | { 262 | "data": { 263 | "text/plain": [ 264 | "0" 265 | ] 266 | }, 267 | "execution_count": 10, 268 | "metadata": {}, 269 | "output_type": "execute_result" 270 | } 271 | ], 272 | "source": [ 273 | "10 % 2" 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": 11, 279 | "id": "33b1fd81-daa1-432e-b50c-92e38a530b55", 280 | "metadata": {}, 281 | "outputs": [ 282 | { 283 | "data": { 284 | "text/plain": [ 285 | "1" 286 | ] 287 | }, 288 | "execution_count": 11, 289 | "metadata": {}, 290 | "output_type": "execute_result" 291 | } 292 | ], 293 | "source": [ 294 | "11 % 2" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "id": "9a2f0396-82a6-4c27-9e79-452955e3ef69", 300 | "metadata": {}, 301 | "source": [ 302 | "These kinds of checks are often best evaluated as `True` or `False`. Luckily, that's an entire data type in Python (and literally every other programming language). We'll cover those next. But first, let's confirm you grok `float`s and `int`s." 303 | ] 304 | }, 305 | { 306 | "cell_type": "markdown", 307 | "id": "269625a6-6adf-4f1a-899a-3a714bb9a187", 308 | "metadata": {}, 309 | "source": [ 310 | "## Self-Assessment Objectives\n", 311 | "\n", 312 | "1. Create any `float` and pass it to `testme_1()`\n", 313 | "\n", 314 | "2. Provide `testme_2()` any 2 `int`s that when `mod`ed produce `5`." 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": null, 320 | "id": "dc472b6f-c407-4074-b02d-e1524645dae8", 321 | "metadata": {}, 322 | "outputs": [], 323 | "source": [ 324 | "# Leave this line!\n", 325 | "from testme import *\n", 326 | "\n", 327 | "# Create any `float` and pass it to `testme_1()`\n", 328 | "testme_1(_)\n", 329 | "\n", 330 | "# Provide `testme_2()` any 2 `int`s that when `mod`ed produce `5`\n", 331 | "testme_2(_,_)" 332 | ] 333 | } 334 | ], 335 | "metadata": { 336 | "kernelspec": { 337 | "display_name": "Python 3 (ipykernel)", 338 | "language": "python", 339 | "name": "python3" 340 | }, 341 | "language_info": { 342 | "codemirror_mode": { 343 | "name": "ipython", 344 | "version": 3 345 | }, 346 | "file_extension": ".py", 347 | "mimetype": "text/x-python", 348 | "name": "python", 349 | "nbconvert_exporter": "python", 350 | "pygments_lexer": "ipython3", 351 | "version": "3.8.10" 352 | } 353 | }, 354 | "nbformat": 4, 355 | "nbformat_minor": 5 356 | } 357 | -------------------------------------------------------------------------------- /1-2_numbers/testme.py: -------------------------------------------------------------------------------- 1 | def testme_1(data): 2 | print("[+] Testing for floatiness...") 3 | if type(data) == float: 4 | print(f"[+] {data} is a float!") 5 | else: 6 | print(f"[!] {data} is not a float!") 7 | 8 | def testme_2(x, y): 9 | print("[+] Testing for modiness...") 10 | if x % y == 5: 11 | print(f"[+] Success! {x} % {y} is 5!") 12 | else: 13 | print(f"[!] Hmm, {x} % {y} does not yield a remainder of 5...") -------------------------------------------------------------------------------- /1-3_booleans/1-3_booleans.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "a846f100-5e09-4f44-b77f-a687026c9769", 6 | "metadata": {}, 7 | "source": [ 8 | "# 1-3: Booleans\n", 9 | "\n", 10 | "This may be the simplest data type to explain, but also the most versatile.\n", 11 | "\n", 12 | "**Booleans** (`bool`)have 2 possible values: `True` or `False`. Arguably, booleans are the core of computing logic.\n", 13 | "\n", 14 | "Although bools are easy to grasp, deriving these 2 values gets a little more complicated. Booleans result from **comparisons**, and they come in many forms.\n", 15 | "\n", 16 | "Let's check out some comparisons" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 1, 22 | "id": "4609e63f-a235-4be2-9fae-e1cf83357271", 23 | "metadata": {}, 24 | "outputs": [ 25 | { 26 | "data": { 27 | "text/plain": [ 28 | "True" 29 | ] 30 | }, 31 | "execution_count": 1, 32 | "metadata": {}, 33 | "output_type": "execute_result" 34 | } 35 | ], 36 | "source": [ 37 | "# Equivalence\n", 38 | "\n", 39 | "2 == 2" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 4, 45 | "id": "93697f6e-25b8-462d-b5ea-5f33633b19ee", 46 | "metadata": {}, 47 | "outputs": [ 48 | { 49 | "data": { 50 | "text/plain": [ 51 | "False" 52 | ] 53 | }, 54 | "execution_count": 4, 55 | "metadata": {}, 56 | "output_type": "execute_result" 57 | } 58 | ], 59 | "source": [ 60 | "# Negated equivalence\n", 61 | "\n", 62 | "10 % 2 != 0" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 5, 68 | "id": "306543b7-de02-4cab-92e1-a51e97354597", 69 | "metadata": {}, 70 | "outputs": [ 71 | { 72 | "data": { 73 | "text/plain": [ 74 | "True" 75 | ] 76 | }, 77 | "execution_count": 5, 78 | "metadata": {}, 79 | "output_type": "execute_result" 80 | } 81 | ], 82 | "source": [ 83 | "# Inequality\n", 84 | "\n", 85 | "10 > 3" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "id": "d7c599d1-96c6-4c22-9204-f94a44d694fd", 91 | "metadata": {}, 92 | "source": [ 93 | "## Boolean Operators" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "id": "9c260c7b-aecf-46a2-866d-7f7f9136d353", 99 | "metadata": {}, 100 | "source": [ 101 | "In addition to the comparison operators, Python supports traditional boolean operators `and`, `or`, and `not`. These can be used to combine boolean expressions to create a new one. Much like in mathematical operations, parentheses can really matter!" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 8, 107 | "id": "b1da94a7-405a-4cd0-a0f6-2644fe42228a", 108 | "metadata": {}, 109 | "outputs": [ 110 | { 111 | "data": { 112 | "text/plain": [ 113 | "True" 114 | ] 115 | }, 116 | "execution_count": 8, 117 | "metadata": {}, 118 | "output_type": "execute_result" 119 | } 120 | ], 121 | "source": [ 122 | "True and True" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 9, 128 | "id": "38d175dc-84d9-4937-91ff-ba3b8b4b18f7", 129 | "metadata": {}, 130 | "outputs": [ 131 | { 132 | "data": { 133 | "text/plain": [ 134 | "True" 135 | ] 136 | }, 137 | "execution_count": 9, 138 | "metadata": {}, 139 | "output_type": "execute_result" 140 | } 141 | ], 142 | "source": [ 143 | "True and (False or True)" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 12, 149 | "id": "9121d06f-5c2b-4761-84df-1328b302b339", 150 | "metadata": {}, 151 | "outputs": [ 152 | { 153 | "data": { 154 | "text/plain": [ 155 | "False" 156 | ] 157 | }, 158 | "execution_count": 12, 159 | "metadata": {}, 160 | "output_type": "execute_result" 161 | } 162 | ], 163 | "source": [ 164 | "not True and (False or True)" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "id": "57a13f6d-56cf-49dc-b8ff-20da7624b679", 170 | "metadata": {}, 171 | "source": [ 172 | "## `None` \n", 173 | "\n", 174 | "`None` is its own type (`NoneType`) in Python, but I mention it here because it is \"falsy.\" That means it will evaluate as `False`. Many functions will return `None` if there was no result. This can be a gotcha, but it can also be used in combination with `or` for assigning fallback values.\n", 175 | "\n", 176 | "Check it out:\n" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 18, 182 | "id": "848ff547-1c94-435e-9c8a-7904fbcd66de", 183 | "metadata": {}, 184 | "outputs": [ 185 | { 186 | "data": { 187 | "text/plain": [ 188 | "10" 189 | ] 190 | }, 191 | "execution_count": 18, 192 | "metadata": {}, 193 | "output_type": "execute_result" 194 | } 195 | ], 196 | "source": [ 197 | "x: int = None # Python doesn't care that the type hint doesn't match the value!\n", 198 | "results = x or 10 # Or will favor the \"truthy\" item, which is 10 in this case\n", 199 | "results" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "id": "35be208b-0a49-418e-9367-bcb37e80d685", 205 | "metadata": {}, 206 | "source": [ 207 | "No test necessary for this one. Let's move on to strings!" 208 | ] 209 | } 210 | ], 211 | "metadata": { 212 | "kernelspec": { 213 | "display_name": "Python 3 (ipykernel)", 214 | "language": "python", 215 | "name": "python3" 216 | }, 217 | "language_info": { 218 | "codemirror_mode": { 219 | "name": "ipython", 220 | "version": 3 221 | }, 222 | "file_extension": ".py", 223 | "mimetype": "text/x-python", 224 | "name": "python", 225 | "nbconvert_exporter": "python", 226 | "pygments_lexer": "ipython3", 227 | "version": "3.8.10" 228 | } 229 | }, 230 | "nbformat": 4, 231 | "nbformat_minor": 5 232 | } 233 | -------------------------------------------------------------------------------- /1-4_strings/1-4_strings.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "c6f026be-90fd-4189-8c6b-53e3ed2eebac", 6 | "metadata": {}, 7 | "source": [ 8 | "# 1-4: Strings\n", 9 | "\n", 10 | "Now this data type we've already seen, but there's a lot more to text data in Python than meets the eye!\n", 11 | "\n", 12 | "Strings are always inside quotes—single or double are fine, but be consistent. I prefer double quotes.\n", 13 | "\n", 14 | "## Sequences\n", 15 | "\n", 16 | "Strings are also our first data type that's a **sequence**. Sequences in Python are collections of elements. As we'll see later, sequences can be used in **loops** for repeated operations. But also, sequences are **indexable**. That means we can access specific elements of the sequence by select its **index**, or \"address\" in the sequence. We use square brackets `[]` to select an index." 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 11, 22 | "id": "a4ce5ffe-d160-49a7-ab9e-8715135aff9a", 23 | "metadata": {}, 24 | "outputs": [ 25 | { 26 | "data": { 27 | "text/plain": [ 28 | "'w'" 29 | ] 30 | }, 31 | "execution_count": 11, 32 | "metadata": {}, 33 | "output_type": "execute_result" 34 | } 35 | ], 36 | "source": [ 37 | "# Assigning a string to a variable\n", 38 | "foo: str = \"Fhqwgads\"\n", 39 | "\n", 40 | "# Accessing a character in the string\n", 41 | "foo[3]" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 8, 47 | "id": "2da8990a-6e29-4f58-9157-1a1c0495fed5", 48 | "metadata": {}, 49 | "outputs": [ 50 | { 51 | "data": { 52 | "text/plain": [ 53 | "'F'" 54 | ] 55 | }, 56 | "execution_count": 8, 57 | "metadata": {}, 58 | "output_type": "execute_result" 59 | } 60 | ], 61 | "source": [ 62 | "# Indices start at 0!\n", 63 | "foo[0]" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 10, 69 | "id": "9b722550-543c-418e-a81b-96add1e57bf6", 70 | "metadata": {}, 71 | "outputs": [ 72 | { 73 | "data": { 74 | "text/plain": [ 75 | "'s'" 76 | ] 77 | }, 78 | "execution_count": 10, 79 | "metadata": {}, 80 | "output_type": "execute_result" 81 | } 82 | ], 83 | "source": [ 84 | "# Indices can go backwards!\n", 85 | "foo[-1]\n", 86 | "# This is really handy for getting the last element of a sequence" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "id": "30c8aa42-fe0e-44a9-b629-6ef61212211f", 92 | "metadata": {}, 93 | "source": [ 94 | "## Slicing\n", 95 | "\n", 96 | "But wait, there's more! Not only can we index strings, we can **slice** them with those brackets as well. Here's the structure of the slicing syntax:\n", 97 | "\n", 98 | "```python\n", 99 | "string[start_index : end_index : step_interval]\n", 100 | "```\n", 101 | "\n", 102 | "I know that's a little confusing, so let's try it piece by piece.\n", 103 | "\n", 104 | "To start, we can take a slice of a string by specifying the starting index of the slice and the end. The start is inclusive, and the end is exclusive. That meanins the slice will include _up to_, but not including, the end index." 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 5, 110 | "id": "37d7cf66-8d27-4e5c-8b4f-221205636df5", 111 | "metadata": {}, 112 | "outputs": [ 113 | { 114 | "data": { 115 | "text/plain": [ 116 | "'qwgad'" 117 | ] 118 | }, 119 | "execution_count": 5, 120 | "metadata": {}, 121 | "output_type": "execute_result" 122 | } 123 | ], 124 | "source": [ 125 | "# Slicing foo\n", 126 | "foo[2:7]" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "id": "593f1419-a324-4afb-ba8d-aa170e724e57", 132 | "metadata": {}, 133 | "source": [ 134 | "Notice that the slice started at the third letter of \"Fhqwgads,\" which is index `2` since we start at `0`. The slice is 5 characters long, ending on the seventh letter (index `6`).\n", 135 | "\n", 136 | "Now we can talk about the `step_interval`, which placed after the second colon in the brackets—when we need it.\n", 137 | "\n", 138 | "The step interval allows \"skip counting,\" like taking every third element. We can also use a negative interval for quick reversing.\n", 139 | "\n", 140 | "We can use the step interval without specifying the other values. That would look like empty colons : `[::4]` takes every 4th element from the entire string." 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 12, 146 | "id": "32a5e787-773f-46a9-905f-3fbed83a536a", 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "data": { 151 | "text/plain": [ 152 | "'acegikmoqsuwy'" 153 | ] 154 | }, 155 | "execution_count": 12, 156 | "metadata": {}, 157 | "output_type": "execute_result" 158 | } 159 | ], 160 | "source": [ 161 | "# New string!\n", 162 | "alphabet: str = \"abcdefghijklmnopqrstuvwxyz\"\n", 163 | "\n", 164 | "# Every other letter\n", 165 | "alphabet[::2]" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": 17, 171 | "id": "3f8e542c-aecc-440a-99dd-c20cda83477d", 172 | "metadata": {}, 173 | "outputs": [ 174 | { 175 | "data": { 176 | "text/plain": [ 177 | "'fhjlnprt'" 178 | ] 179 | }, 180 | "execution_count": 17, 181 | "metadata": {}, 182 | "output_type": "execute_result" 183 | } 184 | ], 185 | "source": [ 186 | "# 6th to 20th elements, taking every other\n", 187 | "alphabet[5:20:2]" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "id": "f56feafb-fac3-448b-ae2e-6004be9341fb", 193 | "metadata": {}, 194 | "source": [ 195 | "## Creating Strings from Other Strings\n", 196 | "\n", 197 | "We've already seen that we can use `+` to combine, or **concatenate** strings. It's important to note that we can also use the `+=` syntax to add onto an existing string stored in a variable." 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 20, 203 | "id": "d40b5718-4431-4cae-bdb7-791d8c51221e", 204 | "metadata": {}, 205 | "outputs": [ 206 | { 207 | "data": { 208 | "text/plain": [ 209 | "'Everybody to the limit'" 210 | ] 211 | }, 212 | "execution_count": 20, 213 | "metadata": {}, 214 | "output_type": "execute_result" 215 | } 216 | ], 217 | "source": [ 218 | "# Using += to add onto a string variable\n", 219 | "bar: str = \"Everybody to the\"\n", 220 | "bar += \" limit\"\n", 221 | "bar" 222 | ] 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "id": "d2a10618-b67f-4cd5-b2b5-3df8e3790c64", 227 | "metadata": {}, 228 | "source": [ 229 | "### Format Strings\n", 230 | "\n", 231 | "It'll often be the case that we need to embed data within strings. Format strings make this simple.\n", 232 | "\n", 233 | "Making format strings is simple too. There are multiple ways to do it, but th cleanest way is to prepend the string wtih an `f'`. Then we can insert values from our code in the string surrounded by `{}`." 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 23, 239 | "id": "0f63c4df-cb0c-4e30-9722-e9d8de71c29c", 240 | "metadata": {}, 241 | "outputs": [ 242 | { 243 | "data": { 244 | "text/plain": [ 245 | "'X is 5. Y is 7.3.'" 246 | ] 247 | }, 248 | "execution_count": 23, 249 | "metadata": {}, 250 | "output_type": "execute_result" 251 | } 252 | ], 253 | "source": [ 254 | "x: int = 5\n", 255 | "y: float = 7.3\n", 256 | "res: str = f\"X is {x}. Y is {y}.\"\n", 257 | "res" 258 | ] 259 | }, 260 | { 261 | "cell_type": "markdown", 262 | "id": "056f66cf-95aa-4f2c-bcb0-5fadff6d5467", 263 | "metadata": { 264 | "tags": [] 265 | }, 266 | "source": [ 267 | "## Check for Understanding\n", 268 | "\n", 269 | "Okay, let's test your stringiness!\n", 270 | "\n", 271 | "### Objectives\n", 272 | "\n", 273 | "1. Reverse the string `foo` and send it to `testme_1()`\n", 274 | "2. Take everything but the first and last characters from the string `bar` and send it to `testme_2()`\n", 275 | "3. Create a format string `baz` that reads `\"The values x and y are _ and _\"`, with the undercores being replaced by `x` and `y`, respectively." 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": null, 281 | "id": "1a17858b-a04f-40cd-be44-904c2045d4fd", 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [ 285 | "from testme import *\n", 286 | "\n", 287 | "# Reverse the string `foo` and send it to `testme_1()`\n", 288 | "foo: str = \"I should be backwards!\"\n", 289 | "testme_1(_)\n", 290 | "\n", 291 | "# Take everything but the first and last characters from the string `bar` and send it to `testme_2()`\n", 292 | "bar: str = \"whipporwhill\"\n", 293 | "testme_2(_)\n", 294 | "\n", 295 | "# Create a format string `baz` that reads `\"The values x and y are _ and _\"`, \n", 296 | "# with the undercores being replaced by `x` and `y`, respectively.\n", 297 | "x: int = 77\n", 298 | "y: int = 28\n", 299 | "baz: str = \"\"\n", 300 | "testme_3(baz)" 301 | ] 302 | } 303 | ], 304 | "metadata": { 305 | "kernelspec": { 306 | "display_name": "Python 3 (ipykernel)", 307 | "language": "python", 308 | "name": "python3" 309 | }, 310 | "language_info": { 311 | "codemirror_mode": { 312 | "name": "ipython", 313 | "version": 3 314 | }, 315 | "file_extension": ".py", 316 | "mimetype": "text/x-python", 317 | "name": "python", 318 | "nbconvert_exporter": "python", 319 | "pygments_lexer": "ipython3", 320 | "version": "3.8.10" 321 | } 322 | }, 323 | "nbformat": 4, 324 | "nbformat_minor": 5 325 | } 326 | -------------------------------------------------------------------------------- /1-4_strings/testme.py: -------------------------------------------------------------------------------- 1 | def testme_1(data): 2 | print("[+] Testing foo...") 3 | if data == "!sdrawkcab eb dluohs I": 4 | print("[+] Succesfully reversed!") 5 | else: 6 | print(f"[-] {data} doesn't seem to match what I expected.") 7 | 8 | 9 | def testme_2(data): 10 | print("[+] Testing bar...") 11 | if data == "hipporwhil": 12 | print("[+] Slice successful!") 13 | else: 14 | print(f"[-] {data} doesn't seem to match what I expected.") 15 | 16 | 17 | def testme_3(data): 18 | print("[+] Testing baz...") 19 | if data == "The values x and y are 77 and 28": 20 | print("[+] Formatting successful!") 21 | else: 22 | print(f"[-] {data} doesn't seem to match what I expected.") -------------------------------------------------------------------------------- /1-5_lists-tuples/testme.py: -------------------------------------------------------------------------------- 1 | crew = [ 2 | "Shepard", 3 | "Joker", 4 | "Garrus", 5 | "Liara", 6 | "Chakwas" 7 | ] 8 | 9 | def testme_1(data): 10 | print("[+] Testing initial crew...") 11 | if sorted(data) == sorted(crew): 12 | print("[+] Crew assembled! Ready for launch!") 13 | else: 14 | print("[-] Hmm, the crew doesn't look quite right.") 15 | 16 | def testme_2(data): 17 | print("[+] Testing expanded crew...") 18 | added = list(filter(lambda c: c not in crew, data)) 19 | if all([c in data for c in crew]) and len(added) > 0: 20 | added_str = ", ".join(added) 21 | print(f"[+] Nice! You added {added_str}") 22 | else: 23 | print("[-] Hmm, the expanded crew doesn't look quite right.") 24 | 25 | pass 26 | 27 | def testme_3(data): 28 | print("[+] Testing the 'revised' crew") 29 | removed = crew[:] 30 | removed.remove("Liara") 31 | if all([c in data for c in removed]) and "Liara" not in data: 32 | print("[+] Sorry to see you go, Liara.") 33 | else: 34 | print("[-] Uh-oh. Did you remember to remove Liara?") 35 | -------------------------------------------------------------------------------- /1-7_conditionals/1-7_conditionals.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "3c49a04c-0932-486d-b051-0adcaaba91d4", 6 | "metadata": {}, 7 | "source": [ 8 | "# 1-7: Conditionals\n", 9 | "\n", 10 | "Way back in 1-1, we covered the first of the **4 Fundamental Structures** of programming languages: variables. Then we went on a rather lengthy detour to discuss various data types. That was important base knowledge, but now we're ready to return to the 4 Structures with **Conditionals**.\n", 11 | "\n", 12 | "As we've seen, our programs and cells execute code instructions in order from top to bottom. But sometimes, we want to change it up depending on the situation. **Conditionals** allow us to test for particular circumstances and choose different code to execute depending on whether the test passes or fails. \n", 13 | "\n", 14 | "Conditionals are part of a class of structure known as **control flow**, and they're essential to writing code.\n", 15 | "\n", 16 | "The tests in a conditional are statements that can evaluate to `True` or `False` boolean values. Remember the comparisons we did back in 1-3? That's the shape of a conditional test.\n", 17 | "\n", 18 | "Let's write a basic conditional." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "id": "69d914e7-6c03-44ed-b227-9bb76f7959b1", 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdin", 29 | "output_type": "stream", 30 | "text": [ 31 | "What's your name? Bob\n" 32 | ] 33 | }, 34 | { 35 | "name": "stdout", 36 | "output_type": "stream", 37 | "text": [ 38 | "Hey Bob! Good to see ya!\n" 39 | ] 40 | } 41 | ], 42 | "source": [ 43 | "# A basic conditional\n", 44 | "name: str = input(\"What's your name?\")\n", 45 | "if name == \"Bob\":\n", 46 | " print(\"Hey Bob! Good to see ya!\")\n", 47 | "else:\n", 48 | " print(f\"Nice to meet you, {name}!\")" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "id": "a54a819b-bbee-4eb7-9f2d-112a47356b71", 54 | "metadata": {}, 55 | "source": [ 56 | "If you run that cell a couple times, using both the name `Bob` and any other name, you'll see that the result changes. The code in the `if` branch of the conditional will only execute _if_ the test is `True`. This test is a simple equivalence test, but there are other options.\n", 57 | "\n", 58 | "## Syntax\n", 59 | "\n", 60 | "Conditionals begin with an `if` statement which contains our test. After the test, a colon and any code that should run if the test passes is indented underneath.\n", 61 | "\n", 62 | "When we need something to happen when the test fails, we can use `else` to define those actions. Again the code for that case is indented beneath `else`.\n", 63 | "\n", 64 | "What about multiple possible conditions? We can handle many possibilities by using one or more `elif` (else if) tests in addition to our initial test." 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 4, 70 | "id": "1ff51309-40c4-4aae-90bf-d91f29a6d918", 71 | "metadata": {}, 72 | "outputs": [ 73 | { 74 | "name": "stdin", 75 | "output_type": "stream", 76 | "text": [ 77 | "Rock, paper, or scissors? rock\n" 78 | ] 79 | }, 80 | { 81 | "name": "stdout", 82 | "output_type": "stream", 83 | "text": [ 84 | "You chose 🪨!\n" 85 | ] 86 | } 87 | ], 88 | "source": [ 89 | "# A simple elif tree\n", 90 | "choice: str = input(\"Rock, paper, or scissors?\")\n", 91 | "\n", 92 | "# Lower case the choice to normalize it\n", 93 | "choice = choice.lower()\n", 94 | "\n", 95 | "if choice == \"rock\":\n", 96 | " print(\"You chose 🪨!\")\n", 97 | "elif choice == \"paper\":\n", 98 | " print(\"You chose 📄!\")\n", 99 | "elif choice == \"scissors\":\n", 100 | " print(\"You chose ✂!\")\n", 101 | "else:\n", 102 | " print(\"That's not a choice!\")\n" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "id": "9db30034-050f-46f8-8689-0822dfdf697a", 108 | "metadata": {}, 109 | "source": [ 110 | "I love using Rock, Paper, Scissors as an example for this because it's so clear: there are 4 choices. R, P, S, or you tried to cheat. So that translates into an `if`, 2 `elif`s, and an `else` to cap it off. Also note that we kind of \"fail down\" the conditional tree. But we want to make sure we cover all possibilities before we get to the end of our conditional.\n", 111 | "\n", 112 | "But I think we can be a little more realistic than just RPS. Let's make a real conditional that serves our purpose." 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "id": "7a56a457-e968-409b-986b-b9743f918f90", 118 | "metadata": {}, 119 | "source": [ 120 | "## Conditional Lab: Password Policy\n", 121 | "\n", 122 | "If you're here, you're probably invested in defensive cybersecurity. It's in the course title! For better or worse, some part of our lives as defenders concerns, yes, Compliance.\n", 123 | "\n", 124 | "And Compliance loves a password policy, oh yes they do. We all know the words; let's sing them together!\n", 125 | "\n", 126 | "🎵 Your password must be... 🎵\n", 127 | "\n", 128 | "🎵 Longer than eight characters 🎵\n", 129 | "\n", 130 | "🎵 But not just alphabetical 🎵\n", 131 | "\n", 132 | "🎵 Be sure to use a capital 🎵\n", 133 | "\n", 134 | "🎵 And digits would be radical 🎵\n", 135 | "\n", 136 | "🎵 And for that extra dash of strength 🎵\n", 137 | "\n", 138 | "🎵 Use symbols to extend the length 🎵\n", 139 | "\n", 140 | "🎵 If you follow these few simple rules 🎵\n", 141 | "\n", 142 | "🎵 Your password won't be guessed by fools 🎵\n", 143 | "\n", 144 | "🎵 And that's the password song!🎵\n", 145 | "\n" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "id": "b4734989-c70a-4c0e-8b38-a993b1a7e043", 151 | "metadata": {}, 152 | "source": [ 153 | "And now, let's build a conditional tree that tests it!" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 28, 159 | "id": "34a841fe-9a39-4a50-8cca-722a01752317", 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "name": "stdin", 164 | "output_type": "stream", 165 | "text": [ 166 | "What's the password? Test12345!\n" 167 | ] 168 | } 169 | ], 170 | "source": [ 171 | "# Get the password to test\n", 172 | "password: str = input(\"What's the password?\")" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "id": "11066219-8940-46c8-8f38-8ab65e27aae0", 178 | "metadata": {}, 179 | "source": [ 180 | "### Length Check\n", 181 | "\n", 182 | "The first check is for length. Python has a built-in `len()` function that will return the length of sequences, including strings. We can use that in our first test." 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": null, 188 | "id": "5efc47f5-26ab-4c98-883f-9dd61bed623f", 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "# Test for length\n", 193 | "min_length = 8\n", 194 | "if len(password) < min_length:\n", 195 | " print(\"[-] Length check failed!\")" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "id": "7e7b7b0d-eddd-4900-9383-7e7aca28fd35", 201 | "metadata": {}, 202 | "source": [ 203 | "### Mixed-Case\n", 204 | "\n", 205 | "The second check is for mixed case (both upper and lower-case letters). For this, we can use the strings built-in `lower()` and `upper()` methods, which will lower-case or upper-case a given string, respectively. Our check can be whether the lower-cased version of the string matches the original `or` the upper case version matches. Either way, it's a fail." 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 30, 211 | "id": "e0f5c266-b7ca-4db9-9c43-c0dc0a7e1a04", 212 | "metadata": {}, 213 | "outputs": [], 214 | "source": [ 215 | "# Use or to combine 2 checks\n", 216 | "if password.lower() == password or password.upper() == password:\n", 217 | " print(\"[-] Mixed case check failed\")" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "id": "6587fd98-63d9-4f44-8057-a41fd2d811f6", 223 | "metadata": {}, 224 | "source": [ 225 | "### Digits\n", 226 | "\n", 227 | "We need to check for the presence of digits. Now, while Python strings have `isalpha()` and `isalnum()` methods, we can't just use those on our whole password and call it a day. For one thing, these tests will fail if we have special characters in there like we're supposed to. Rather, we need to check to make sure that at _at least one_ character is a digit.\n", 228 | "\n", 229 | "There's an efficient way to do this, but it requires a trick we haven't learned yet, so forgive the brief skip-ahead.\n", 230 | "\n", 231 | "Python has a built-in `any()` function that returns `True` if any element of the sequence you give it evaluates to `True`. Watch:\n" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": 31, 237 | "id": "8b3bcc48-fa1a-43d2-9079-eb2750e02621", 238 | "metadata": {}, 239 | "outputs": [ 240 | { 241 | "data": { 242 | "text/plain": [ 243 | "True" 244 | ] 245 | }, 246 | "execution_count": 31, 247 | "metadata": {}, 248 | "output_type": "execute_result" 249 | } 250 | ], 251 | "source": [ 252 | "# Any with booleans\n", 253 | "any([False, False, False, True])" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 32, 259 | "id": "b849a7ce-98a7-4059-a4a2-9f916807e474", 260 | "metadata": {}, 261 | "outputs": [ 262 | { 263 | "data": { 264 | "text/plain": [ 265 | "False" 266 | ] 267 | }, 268 | "execution_count": 32, 269 | "metadata": {}, 270 | "output_type": "execute_result" 271 | } 272 | ], 273 | "source": [ 274 | "# Any with falsy values\n", 275 | "any([0, 0, 0])" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": 33, 281 | "id": "43d6561a-c59f-471d-85a8-de641ed1e2a4", 282 | "metadata": {}, 283 | "outputs": [ 284 | { 285 | "data": { 286 | "text/plain": [ 287 | "True" 288 | ] 289 | }, 290 | "execution_count": 33, 291 | "metadata": {}, 292 | "output_type": "execute_result" 293 | } 294 | ], 295 | "source": [ 296 | "# Any with falsy and truthy values\n", 297 | "any([0, 0, 1])" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "id": "b2ce94e7-d3c0-4013-993b-7e0bb3ace51b", 303 | "metadata": {}, 304 | "source": [ 305 | "So what we need is a list of `True`/`False` values generated from the `password` string. We haven't yet covered repeated actions, so forgive the brief leap, but we'll use a **list comprehension** to apply `isdecimal()` to each character of `password`, and use `any()` on that list to determine if there are digits present.\n", 306 | "\n", 307 | "Since we're testing for their absence, we'll negate the `any()` with `not`." 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 34, 313 | "id": "41ed8c75-b061-4fbc-a03f-1c4f595836ec", 314 | "metadata": {}, 315 | "outputs": [], 316 | "source": [ 317 | "if not any([c.isdecimal() for c in password]):\n", 318 | " print(\"[-] Digit test failed\")" 319 | ] 320 | }, 321 | { 322 | "cell_type": "markdown", 323 | "id": "ba76af47-187e-4e38-a09d-37fa37e4e773", 324 | "metadata": {}, 325 | "source": [ 326 | "### Symbols\n", 327 | "\n", 328 | "To seek symbols (naively), there's a much easier method we can use. `isalnum()` returns `True` if every character in the string is alphanumeric. This should fail if symbols are present. It's not perfect, but it'll do for now." 329 | ] 330 | }, 331 | { 332 | "cell_type": "code", 333 | "execution_count": 36, 334 | "id": "dacd9696-ade3-415f-9782-d194c8189727", 335 | "metadata": {}, 336 | "outputs": [], 337 | "source": [ 338 | "if password.isalnum():\n", 339 | " print(\"[-] Symbol test failed\")" 340 | ] 341 | }, 342 | { 343 | "cell_type": "markdown", 344 | "id": "3b72fff1-a2ff-4182-8cfa-b78f02df9aa5", 345 | "metadata": {}, 346 | "source": [ 347 | "## Check for Understanding: Put it all together\n", 348 | "\n", 349 | "### Objectives\n", 350 | "\n", 351 | "You have on objective: Combine these checks into a single conditional! Don't forget we begin with `if`, have 0 or more `elifs`, and end with `else`." 352 | ] 353 | }, 354 | { 355 | "cell_type": "code", 356 | "execution_count": 37, 357 | "id": "31c96e2c-cdd6-4fa0-8570-0df96035c7f1", 358 | "metadata": {}, 359 | "outputs": [ 360 | { 361 | "name": "stdin", 362 | "output_type": "stream", 363 | "text": [ 364 | "What's the password? test123\n" 365 | ] 366 | } 367 | ], 368 | "source": [ 369 | "# Get the password from the user\n", 370 | "password = input(\"What's the password?\")\n", 371 | "\n", 372 | "# Your conditions go here." 373 | ] 374 | } 375 | ], 376 | "metadata": { 377 | "kernelspec": { 378 | "display_name": "Python 3 (ipykernel)", 379 | "language": "python", 380 | "name": "python3" 381 | }, 382 | "language_info": { 383 | "codemirror_mode": { 384 | "name": "ipython", 385 | "version": 3 386 | }, 387 | "file_extension": ".py", 388 | "mimetype": "text/x-python", 389 | "name": "python", 390 | "nbconvert_exporter": "python", 391 | "pygments_lexer": "ipython3", 392 | "version": "3.8.10" 393 | } 394 | }, 395 | "nbformat": 4, 396 | "nbformat_minor": 5 397 | } 398 | -------------------------------------------------------------------------------- /1-8_loops/1-8_loops.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "dc83e89a-2fde-4eb1-ac34-2f5a2e35f4df", 6 | "metadata": {}, 7 | "source": [ 8 | "# 1-8: Loops\n", 9 | "\n", 10 | "We've encountered **flow control** already in the form of conditionals. In a conditional, our program's flow branches depending on our test(s). But very often, we need to repeat tasks in our program. That's what **loops** are for.\n", 11 | "\n", 12 | "Loops are ways to tell a program to repeat a set of instructions. We can repeat until a condition is met (or no longer met), or for a predetermined number of iterations.\n" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "id": "1df1a513-fe15-4b24-aefe-d7f529856727", 18 | "metadata": {}, 19 | "source": [ 20 | "## `for` Loops\n", 21 | "\n", 22 | "When we know how many times we need to repeat our operations, a `for` loop is best. We can use `for` loops with either a predefined sequence (`list`, `tuple`, `str`), or we can specify a number of times to repeat with a `range()`.\n", 23 | "\n", 24 | "Let's review the syntax for both." 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "id": "dd3ce83d-ce8f-499b-913d-96207810c6db", 30 | "metadata": {}, 31 | "source": [ 32 | "### Over a Sequence" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 1, 38 | "id": "f85b9862-098a-4543-abf7-5867b6944fc5", 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "milk\n", 46 | "eggs\n", 47 | "oranges\n", 48 | "rice\n" 49 | ] 50 | } 51 | ], 52 | "source": [ 53 | "# Looping over a sequence\n", 54 | "groceries: list = [\"milk\", \"eggs\", \"oranges\", \"rice\"]\n", 55 | "\n", 56 | "# Build the loop\n", 57 | "for g in groceries:\n", 58 | " print(g)" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "id": "2c6385cd-0fdf-4235-a6df-a312cc16a0a7", 64 | "metadata": {}, 65 | "source": [ 66 | "So the loop begins with the `for` keyword—no surprise there. Then we create a temporary variable, known as an **iterator** to keep track of the current element of the sequence. Each time we repeat the loop, the value of the iterator will update to the next value in the sequence." 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "id": "c04333bf-e048-4518-97ea-9b529f78d161", 72 | "metadata": {}, 73 | "source": [ 74 | "### Over a `range`" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 3, 80 | "id": "814ac5a5-b1a5-4a2f-9f00-4c6437fcb106", 81 | "metadata": {}, 82 | "outputs": [ 83 | { 84 | "name": "stdout", 85 | "output_type": "stream", 86 | "text": [ 87 | "0\n", 88 | "1\n", 89 | "2\n", 90 | "3\n", 91 | "4\n", 92 | "5\n", 93 | "6\n", 94 | "7\n", 95 | "8\n", 96 | "9\n" 97 | ] 98 | } 99 | ], 100 | "source": [ 101 | "# A simple range, starting at 0\n", 102 | "for i in range(10):\n", 103 | " print(i)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 4, 109 | "id": "f75ce5e4-429f-44d4-9a0a-f18a15e66f46", 110 | "metadata": {}, 111 | "outputs": [ 112 | { 113 | "name": "stdout", 114 | "output_type": "stream", 115 | "text": [ 116 | "20\n", 117 | "21\n", 118 | "22\n", 119 | "23\n", 120 | "24\n", 121 | "25\n", 122 | "26\n", 123 | "27\n", 124 | "28\n", 125 | "29\n", 126 | "30\n" 127 | ] 128 | } 129 | ], 130 | "source": [ 131 | "# A range that doesn't start at 0\n", 132 | "for i in range(20, 31):\n", 133 | " print(i)" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "id": "6b047cd6-e3c6-4995-9fed-5a3c31c80e38", 139 | "metadata": {}, 140 | "source": [ 141 | "`range()` produces a `range` object that produces integers either up to, but not including, a single argument, or from a start and up to an end when given 2 arguments.\n", 142 | "\n", 143 | "Ranges are super handy for when we need a raw index number for one reason or another." 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "id": "fdaa5a45-59bf-4ea6-a801-a2b22ec6ed5e", 149 | "metadata": {}, 150 | "source": [ 151 | "### Building Lists/Strings\n", 152 | "\n", 153 | "It's a common pattern to use loops to create new lists or sequences from existing ones. The basic approach looks like this:\n", 154 | "\n", 155 | "1. Create an empty list.\n", 156 | "2. Loop over an existing list.\n", 157 | "3. Append a modified/calculated value to the new list based on the original element.\n", 158 | "\n", 159 | "Like so:" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": 20, 165 | "id": "128b0467-63ce-4246-90c6-d23d7cd741dd", 166 | "metadata": {}, 167 | "outputs": [ 168 | { 169 | "data": { 170 | "text/plain": [ 171 | "['Milk!', 'Eggs!', 'Oranges!', 'Rice!']" 172 | ] 173 | }, 174 | "execution_count": 20, 175 | "metadata": {}, 176 | "output_type": "execute_result" 177 | } 178 | ], 179 | "source": [ 180 | "# A list of capitalized/exclaimed groceries\n", 181 | "exclaimed: list = []\n", 182 | "for g in groceries:\n", 183 | " exclaimed.append(g.capitalize() + \"!\")\n", 184 | "exclaimed" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "id": "7e65699c-333b-4f0f-9dad-1a8c12bfd7c6", 190 | "metadata": {}, 191 | "source": [ 192 | "## `while` Loops\n", 193 | "\n", 194 | "If we don't necessarily know how many times we need to repeat an operation, but we know when we want to stop, a `while` loop is appropriate. \n", 195 | "\n", 196 | "`while`, like `if`, requires a test that must evaluate to `True` for the loop to continue. That means we have to be extra careful when writing that test to prevent **infinite loops**. " 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "id": "e254e1b3-d68f-4d0b-8b2c-01ff926013f6", 202 | "metadata": {}, 203 | "source": [ 204 | "### External Iterators\n", 205 | "\n", 206 | "It's very common to use external variables to keep track of what's up with a `while` loop. Either as a counter, or a \"switch\" type variable that we turn from `False` to `True` or vice-versa once we have what we need.\n", 207 | "\n", 208 | "But don't forget, we have to manually update this variable, as the loop won't do it for us!" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": 8, 214 | "id": "881cd4d1-77fd-4e8c-9161-1c926877f859", 215 | "metadata": {}, 216 | "outputs": [ 217 | { 218 | "name": "stdout", 219 | "output_type": "stream", 220 | "text": [ 221 | "10\n", 222 | "9\n", 223 | "8\n", 224 | "7\n", 225 | "6\n", 226 | "5\n", 227 | "4\n", 228 | "3\n", 229 | "2\n", 230 | "1\n", 231 | "0\n", 232 | "Liftoff!\n" 233 | ] 234 | } 235 | ], 236 | "source": [ 237 | "# While loop with external counter\n", 238 | "count: int = 10\n", 239 | "while count >= 0:\n", 240 | " print(count)\n", 241 | " # Manually update the count\n", 242 | " count -= 1\n", 243 | "print(\"Liftoff!\")" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 12, 249 | "id": "3816c47f-a560-4de0-9e08-e778b938f325", 250 | "metadata": {}, 251 | "outputs": [ 252 | { 253 | "name": "stdout", 254 | "output_type": "stream", 255 | "text": [ 256 | "iridium\n", 257 | "osmium\n", 258 | "tantalum\n", 259 | "Found it!\n" 260 | ] 261 | } 262 | ], 263 | "source": [ 264 | "# While loop with a switch\n", 265 | "elements: list = [\"iridium\", \"osmium\", \"tantalum\", \"manganese\"]\n", 266 | "\n", 267 | "found: bool = False\n", 268 | "count: int = 0\n", 269 | "while not found:\n", 270 | " el: str = elements[count]\n", 271 | " print(el)\n", 272 | " count += 1\n", 273 | " if el == \"tantalum\":\n", 274 | " found = True\n", 275 | " print(\"Found it!\")\n", 276 | " " 277 | ] 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "id": "6a3bdfe2-6e83-45f0-8824-bb0a6e24bfb8", 282 | "metadata": {}, 283 | "source": [ 284 | "## Loop Tricks\n", 285 | "\n", 286 | "There are lots of more advanced techniques available for us in Python to make loops a little more efficient. Let's discuss 2 of them: `in` and **list comprehensions**." 287 | ] 288 | }, 289 | { 290 | "cell_type": "markdown", 291 | "id": "df4b6b95-0e84-4552-86c4-113a389ad77a", 292 | "metadata": {}, 293 | "source": [ 294 | "### `in`\n", 295 | "\n", 296 | "We've already seen `in`: `for i in range(30)`, for example. But `in` has some other powers in Python. It can be used to test for membership in sequences." 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": 13, 302 | "id": "e61c0e77-5960-4053-ae90-42df542756e4", 303 | "metadata": {}, 304 | "outputs": [ 305 | { 306 | "data": { 307 | "text/plain": [ 308 | "True" 309 | ] 310 | }, 311 | "execution_count": 13, 312 | "metadata": {}, 313 | "output_type": "execute_result" 314 | } 315 | ], 316 | "source": [ 317 | "# Using in to test for membership\n", 318 | "\"F\" in \"Fhgwgads\"" 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": 16, 324 | "id": "fc4cac26-5eef-4799-bf2b-93c992b72cab", 325 | "metadata": {}, 326 | "outputs": [ 327 | { 328 | "data": { 329 | "text/plain": [ 330 | "True" 331 | ] 332 | }, 333 | "execution_count": 16, 334 | "metadata": {}, 335 | "output_type": "execute_result" 336 | } 337 | ], 338 | "source": [ 339 | "# With lists\n", 340 | "\"a\" in [\"a\", \"b\", \"c\"]" 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": 18, 346 | "id": "321ae44b-f6b8-443d-907e-7c935f038bf8", 347 | "metadata": {}, 348 | "outputs": [ 349 | { 350 | "data": { 351 | "text/plain": [ 352 | "True" 353 | ] 354 | }, 355 | "execution_count": 18, 356 | "metadata": {}, 357 | "output_type": "execute_result" 358 | } 359 | ], 360 | "source": [ 361 | "# Even with dicts (tests keys)\n", 362 | "\"bob\" in {\"bob\": \"test123\", \"alice\": \"password123\"}" 363 | ] 364 | }, 365 | { 366 | "cell_type": "markdown", 367 | "id": "d3bd335b-3e15-492a-8d0f-267660e3051b", 368 | "metadata": {}, 369 | "source": [ 370 | "### List Comprehensions\n", 371 | "\n", 372 | "We've demonstrated the \"classic\" pattern for generated lists from pre-existing sequences, but there's a faster way. **List comprehensions** are \"syntactical sugar\" that put that entire construction into a single expression. Let's revisit those exclaimed groceries:" 373 | ] 374 | }, 375 | { 376 | "cell_type": "code", 377 | "execution_count": 21, 378 | "id": "72989b29-26b6-4397-9e62-42435613d9eb", 379 | "metadata": {}, 380 | "outputs": [ 381 | { 382 | "data": { 383 | "text/plain": [ 384 | "['Milk!', 'Eggs!', 'Oranges!', 'Rice!']" 385 | ] 386 | }, 387 | "execution_count": 21, 388 | "metadata": {}, 389 | "output_type": "execute_result" 390 | } 391 | ], 392 | "source": [ 393 | "# What took 3 lines now takes 1!\n", 394 | "exclaimed: list = [ g.capitalize() + \"!\" for g in groceries ]\n", 395 | "exclaimed" 396 | ] 397 | }, 398 | { 399 | "cell_type": "markdown", 400 | "id": "a63cff66-a7ad-430c-94fc-b4d1aa7b8854", 401 | "metadata": {}, 402 | "source": [ 403 | "List comprehensions are common in Jupyter notebooks given their ease of use. Use them instead of `for` loops whenever possible." 404 | ] 405 | }, 406 | { 407 | "cell_type": "markdown", 408 | "id": "5e9e784b-b529-46e0-9a50-13553da96a2d", 409 | "metadata": {}, 410 | "source": [ 411 | "## Check for Understanding\n", 412 | "\n", 413 | "For this check, we're going to perform a real-world operation. We have in our position a credential dump of potential usernames and passwords. These have been provided for you as the list `cred_dump`, which contains tuples of shape `(username, password)`. Not all of the users may exist, and who knows which passwords are legit?\n", 414 | "\n", 415 | "You also have a function called `authenticate()` that takes a username and a password. It returns a tuple of shape (`bool`, `str`). The `bool` is whether the authentication was successful, and and the `str` will be any error message, if it exists.\n", 416 | "\n", 417 | "For example, a successful auth will return `(True, \"\")` and a bad password will return `(False, 'Password is incorrect')`.\n", 418 | "\n", 419 | "### Objectives\n", 420 | "\n", 421 | "1. Make two lists: `valid_users` and `authenticated_users`. We need to discover which usernames are real _and_ which username/password combos are real.\n", 422 | "2. Loop through `cred_dump`. If the username exists but the auth fails, add the user to the `valid_users` list. If the username exists and the auth succeeds, add the username to both lists.\n", 423 | "3. Send `valid_users` to `testme_1()`.\n", 424 | "4. Send `authenticated_users` to `testme_2()`.\n", 425 | "\n", 426 | "**Note: This test will use different usernames/passwords every time the kernel is restarted.**\n" 427 | ] 428 | }, 429 | { 430 | "cell_type": "code", 431 | "execution_count": null, 432 | "id": "e5001494-2ec2-4e11-8348-7deea3fc1304", 433 | "metadata": {}, 434 | "outputs": [], 435 | "source": [ 436 | "from testme import cred_dump, authenticate, testme_1, testme_2\n", 437 | "\n" 438 | ] 439 | }, 440 | { 441 | "cell_type": "markdown", 442 | "id": "bfbe5230-c1f1-4de4-921c-91c2d6a9b03b", 443 | "metadata": {}, 444 | "source": [ 445 | "That does it for loops! On to (my favorite topic) functions!" 446 | ] 447 | } 448 | ], 449 | "metadata": { 450 | "kernelspec": { 451 | "display_name": "Python 3 (ipykernel)", 452 | "language": "python", 453 | "name": "python3" 454 | }, 455 | "language_info": { 456 | "codemirror_mode": { 457 | "name": "ipython", 458 | "version": 3 459 | }, 460 | "file_extension": ".py", 461 | "mimetype": "text/x-python", 462 | "name": "python", 463 | "nbconvert_exporter": "python", 464 | "pygments_lexer": "ipython3", 465 | "version": "3.10.6" 466 | } 467 | }, 468 | "nbformat": 4, 469 | "nbformat_minor": 5 470 | } 471 | -------------------------------------------------------------------------------- /1-8_loops/testme.py: -------------------------------------------------------------------------------- 1 | from random import sample, shuffle 2 | 3 | # Load Files 4 | with open("names.txt") as f: 5 | names = [l.strip() for l in f.readlines()] 6 | 7 | with open("rockyou-50.txt") as f: 8 | pws = [l.strip() for l in f.readlines()] 9 | 10 | # Constants 11 | NUM_DUMPS = 500 12 | NUM_ACCTS = 200 13 | PCT_CORRECT = 0.1 14 | 15 | 16 | # Build accounts 17 | acct_users = sample(names, NUM_ACCTS) 18 | acct_pws = sample(pws, NUM_ACCTS) 19 | accounts = {acct_users[i]:acct_pws[i] for i in range(NUM_ACCTS)} 20 | 21 | # Build cred dump 22 | account_samples = sample(acct_users, round(NUM_ACCTS * PCT_CORRECT)) 23 | dump_users = sample(names, NUM_DUMPS) + account_samples 24 | dump_pws = sample(pws, NUM_DUMPS) + [accounts[p] for p in account_samples] 25 | cred_dump = list(zip(dump_users, dump_pws)) 26 | shuffle(cred_dump) 27 | 28 | def authenticate(username, password) -> (bool, str): 29 | if username in accounts: 30 | if password == accounts[username]: 31 | return (True, "") 32 | return (False, "Password is incorrect") 33 | return (False, "Username does not exist") 34 | 35 | 36 | def testme_1(valid_users): 37 | real_users = list(filter(lambda u: u in accounts, dump_users)) 38 | try: 39 | if sorted(valid_users) == sorted(real_users): 40 | print("[+] Valid users matched!") 41 | else: 42 | print("[-] Valid users do not match!") 43 | except Error as e: 44 | print(e) 45 | 46 | def testme_2(test_authed): 47 | true_authed = [a[0] for a in list(filter(lambda d: authenticate(d[0], d[1])[0], cred_dump))] 48 | try: 49 | if sorted(true_authed) == sorted(test_authed): 50 | print("[+] Authenticated users matched!") 51 | else: 52 | print("[-] Authenticated users do not match!") 53 | except Error as e: 54 | print(e) 55 | -------------------------------------------------------------------------------- /1-9_functions/1-9_functions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "b5ce9de6-21ee-4804-b06b-4bd1b4fd228d", 6 | "metadata": {}, 7 | "source": [ 8 | "# 1-9: Functions\n", 9 | "\n", 10 | "Not gonna lie, functions are my favorite of the 4 Fundamental Structures of Programming. I just think they're neat!\n", 11 | "\n", 12 | "We've already taken advantage of functions. Anything with parens after it, like `print()`, is a function.\n", 13 | "\n", 14 | "Functions are reusable blocks of code that take inputs, known as **arguments** or **parameters** and **return** an output. \n", 15 | "\n", 16 | "What gets really cool is when we start putting functions together to transform data from one shape to another, to another, to anot—\n", 17 | "\n", 18 | "But I digress.\n", 19 | "\n", 20 | "In Jupyter Notebooks, functions play an important role of saving us a lot of repeated work. By defining a common operation once in a function, we save ourselves from having to write the same code in cell after cell." 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "id": "bfa0b82d-dcac-4576-95fe-f5d738089618", 26 | "metadata": {}, 27 | "source": [ 28 | "## Syntax\n", 29 | "\n", 30 | "The keyword that begins a function is `def`, short for \"define.\" Then comes the function name, followed by parentheses. Inside the parens are comma-separated names of arguments. If the function takes no arguments, the parens stay empty. The whole thing is ended with a colon, just like a loop or conditional, and the **implementation** (code inside the function) is indented underneat the `def` line.\n", 31 | "\n", 32 | "We **call** the function by writing the function name, with parens, and placing any values inside that match what we want for our arguments.\n", 33 | "\n", 34 | "Let's check out a simple function." 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 1, 40 | "id": "38a27899-6a10-4025-afff-07725b483487", 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "# Basic function without args\n", 45 | "def greet():\n", 46 | " print(\"Hello, you!\")" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "id": "2fa43333-735a-46b9-8ee0-e1d9d64fabbc", 52 | "metadata": {}, 53 | "source": [ 54 | "Notice that nothing happened when we ran that cell. That's because the function has been _defined_, but not _called_. Let's do that now." 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 2, 60 | "id": "1018fe5f-edae-4345-b423-ef9d25cacde3", 61 | "metadata": {}, 62 | "outputs": [ 63 | { 64 | "name": "stdout", 65 | "output_type": "stream", 66 | "text": [ 67 | "Hello, you!\n" 68 | ] 69 | } 70 | ], 71 | "source": [ 72 | "# Call greet()\n", 73 | "greet()" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "id": "710c34c5-2543-4c60-8914-2cfba92eefaf", 79 | "metadata": {}, 80 | "source": [ 81 | "Not very interesting, but you get the idea.\n", 82 | "\n", 83 | "Now let's add some arguments to our `greet()` function to make it slightly more useful." 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 4, 89 | "id": "6c2a3e8e-8731-4246-837b-20f02c43ccb0", 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "# greet(): now with args! \n", 94 | "def greet(name):\n", 95 | " print(f\"Hello, {name}!\")" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 5, 101 | "id": "e79a20fe-e482-4210-a331-7d426d5d991c", 102 | "metadata": {}, 103 | "outputs": [ 104 | { 105 | "name": "stdout", 106 | "output_type": "stream", 107 | "text": [ 108 | "Hello, Taggart!\n" 109 | ] 110 | } 111 | ], 112 | "source": [ 113 | "# Calling greet() with a name\n", 114 | "greet(\"Taggart\")" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "id": "1e35944e-d176-4235-84fb-afefd9c240c3", 120 | "metadata": {}, 121 | "source": [ 122 | "## Returns\n", 123 | "\n", 124 | "So far, our functions have had _side effects_, like printing something out, but they don't **return** anything. This is the true calling (get it?) of a function. Functions can and should return a value that can be used in a variable, a comparison, or even sent to other functions.\n", 125 | "\n", 126 | "Let's refactor `greet()` to follow this pattern, and use the `return` keyword." 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 8, 132 | "id": "0954d2b2-505b-4677-8658-03a3996b5e05", 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "data": { 137 | "text/plain": [ 138 | "['Hello, Alice!', 'Hello, Bob!', 'Hello, Cthulhu!', 'Hello, Dagon!']" 139 | ] 140 | }, 141 | "execution_count": 8, 142 | "metadata": {}, 143 | "output_type": "execute_result" 144 | } 145 | ], 146 | "source": [ 147 | "# greet() but it returns \n", 148 | "def greet(name):\n", 149 | " return f\"Hello, {name}!\"\n", 150 | "\n", 151 | "# Make a list of greetings\n", 152 | "\n", 153 | "names: list = [\"Alice\",\"Bob\",\"Cthulhu\",\"Dagon\"]\n", 154 | "\n", 155 | "# Use greet() for each name to make a list of greetings\n", 156 | "greetings: list = [ greet(n) for n in names ]\n", 157 | "greetings" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "id": "390284ec-5775-4b68-a9fb-f4150047c3f6", 163 | "metadata": {}, 164 | "source": [ 165 | "It's a little contrived, but the idea is that by returning the value, we can use the result of the function in any way we like, not just any activities within the function itself." 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "id": "a08d52aa-23f1-4aa1-b029-8d94bf95207e", 171 | "metadata": {}, 172 | "source": [ 173 | "## Type Hinting\n", 174 | "\n", 175 | "You might have noticed that we didn't use any type hinting for the functions we've written so far. Remember that the type hints are entirely optional! However, we can use type hints with functions. Arguably, that's where they matter the most. Type hints live in the arguments of a function, as well as after the argument parentheses, with another symbol: `->`, to indicate the return type.\n", 176 | "\n", 177 | "Let's minorly refactor our `greet()` function to use type hints." 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": 9, 183 | "id": "a3ff4a66-cc98-4e00-9110-3a105031137b", 184 | "metadata": {}, 185 | "outputs": [], 186 | "source": [ 187 | "# Adding type hints\n", 188 | "def greet(name: str) -> str:\n", 189 | " return f\"Hello, {name}!\"" 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "id": "a9b0d96a-ab2d-4027-b0f6-65ed09e180d6", 195 | "metadata": {}, 196 | "source": [ 197 | "This may not make much of a difference at first, but being able to tell at a glance what data types a function takes and returns is incredibly valuable. Those pieces of information—data in and data out—are known as a function's **signature**. " 198 | ] 199 | }, 200 | { 201 | "cell_type": "markdown", 202 | "id": "1a7cb985-8fe9-4070-8a24-48d38694e6d0", 203 | "metadata": {}, 204 | "source": [ 205 | "## Default Arguments\n", 206 | "\n", 207 | "In addition to providing arguments to functions, sometimes we may want to provide defaults in case no arguments are provided. We can do that too!" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": 11, 213 | "id": "4d1d0bb0-cd09-4419-b116-76a0b01884e8", 214 | "metadata": {}, 215 | "outputs": [ 216 | { 217 | "data": { 218 | "text/plain": [ 219 | "'Hello, You!'" 220 | ] 221 | }, 222 | "execution_count": 11, 223 | "metadata": {}, 224 | "output_type": "execute_result" 225 | } 226 | ], 227 | "source": [ 228 | "# Syntax for default function args\n", 229 | "def greet(name: str = \"You\") -> str:\n", 230 | " return f\"Hello, {name}!\"\n", 231 | "\n", 232 | "# Call greet without args\n", 233 | "greet()" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "id": "c62fe661-7529-4fc1-bd4e-4165919ffa7c", 239 | "metadata": {}, 240 | "source": [ 241 | "## Function Composition and Chaining\n", 242 | "\n", 243 | "Once you've mastered returning values from a single function, the next step is learning how to **compose** functions. This is when calls to one function are passed as arguments to another. It's probably easier to see in context.\n", 244 | "\n", 245 | "Let's write a small function to transform our names before sending them to `greet()`. Since we're all hackers here, we'll do a quick function to transform our names into L337. To do this, we're going to take advantage of a built-in `str` method: `replace()`.\n", 246 | "\n", 247 | "`replace()` takes two arguments: the pattern to replace, and what to replace it with. It returns a new string that has those replacements. Like so:" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": 28, 253 | "id": "8ef5b75d-8721-43ab-8c4c-d80b7627a113", 254 | "metadata": {}, 255 | "outputs": [ 256 | { 257 | "data": { 258 | "text/plain": [ 259 | "'Pyth0n'" 260 | ] 261 | }, 262 | "execution_count": 28, 263 | "metadata": {}, 264 | "output_type": "execute_result" 265 | } 266 | ], 267 | "source": [ 268 | "\"Python\".replace(\"o\",\"0\")" 269 | ] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "id": "31569a12-347d-4198-b733-e06c197e3012", 274 | "metadata": {}, 275 | "source": [ 276 | "That's cool, but if we want to do multiple replacements, it seems as though we'd need to assign the new string to a variable and then repeatedly call `replace()` on that variable." 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 34, 282 | "id": "907f3791-029b-42e4-8655-63f09fdfcd7d", 283 | "metadata": {}, 284 | "outputs": [ 285 | { 286 | "data": { 287 | "text/plain": [ 288 | "'T3s7 s7r1ng'" 289 | ] 290 | }, 291 | "execution_count": 34, 292 | "metadata": {}, 293 | "output_type": "execute_result" 294 | } 295 | ], 296 | "source": [ 297 | "l33t3d = \"Test string\"\n", 298 | "l33t3d = l33t3d.replace(\"e\",\"3\")\n", 299 | "l33t3d = l33t3d.replace(\"l\",\"|\")\n", 300 | "l33t3d = l33t3d.replace(\"t\",\"7\")\n", 301 | "l33t3d = l33t3d.replace(\"i\",\"1\")\n", 302 | "l33t3d" 303 | ] 304 | }, 305 | { 306 | "cell_type": "markdown", 307 | "id": "4ce17bcb-2e84-4c68-989e-a0d6cc5196e6", 308 | "metadata": {}, 309 | "source": [ 310 | "And that works, but it's kind of unwieldy. This is part of why `return` is so important.\n", 311 | "\n", 312 | "Because `replace()` returns a value, we can continue to operate on that value after the parentheses just as though it were the original.\n", 313 | "\n", 314 | "What that means is that we can **chain** function calls together, one after another." 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": 36, 320 | "id": "2ec329d9-fa61-4e4e-9053-c1b5a85f0430", 321 | "metadata": {}, 322 | "outputs": [ 323 | { 324 | "data": { 325 | "text/plain": [ 326 | "'T3s7 s7r1ng'" 327 | ] 328 | }, 329 | "execution_count": 36, 330 | "metadata": {}, 331 | "output_type": "execute_result" 332 | } 333 | ], 334 | "source": [ 335 | "# Chaining functions together\n", 336 | "\"Test string\".replace(\"e\",\"3\").replace(\"l\",\"|\").replace(\"t\",\"7\").replace(\"i\",\"1\")" 337 | ] 338 | }, 339 | { 340 | "cell_type": "markdown", 341 | "id": "a14e272a-a89d-4004-9698-b3ef6a86d899", 342 | "metadata": {}, 343 | "source": [ 344 | "Finally, there's a nice syntactical flourish we can use to split these calls into separate lines, using backslashes. Let's put this all together to create our function." 345 | ] 346 | }, 347 | { 348 | "cell_type": "code", 349 | "execution_count": 44, 350 | "id": "ae1a482e-df6b-48d0-b7e2-b59da4c967f4", 351 | "metadata": {}, 352 | "outputs": [], 353 | "source": [ 354 | "# Functionify our conversion\n", 355 | "# Note the nice clean backslashes rather than one long mess of code\n", 356 | "def l33t(name: str) -> str:\n", 357 | " return name \\\n", 358 | " .replace(\"e\",\"3\") \\\n", 359 | " .replace(\"a\",\"4\") \\\n", 360 | " .replace(\"l\",\"|\") \\\n", 361 | " .replace(\"t\",\"7\") \\\n", 362 | " .replace(\"i\",\"1\") \\\n", 363 | " .replace(\"o\",\"0\")" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": 41, 369 | "id": "e4dc4efa-b27e-469f-a70f-8322189beb05", 370 | "metadata": {}, 371 | "outputs": [ 372 | { 373 | "data": { 374 | "text/plain": [ 375 | "'T3s7 s7r1ng'" 376 | ] 377 | }, 378 | "execution_count": 41, 379 | "metadata": {}, 380 | "output_type": "execute_result" 381 | } 382 | ], 383 | "source": [ 384 | "l33t(\"Test string\")" 385 | ] 386 | }, 387 | { 388 | "cell_type": "markdown", 389 | "id": "f9f89a2f-0476-416a-9ace-6afbed17c982", 390 | "metadata": {}, 391 | "source": [ 392 | "Now that we have a working function, we can greet folks with l337. To do this we'll remake our list with a list comprehension and 2 functions nested inside each other, showing how composition can make functions even more powerful." 393 | ] 394 | }, 395 | { 396 | "cell_type": "code", 397 | "execution_count": 47, 398 | "id": "0af4a2ff-2516-425a-9fa2-dcf15754548c", 399 | "metadata": {}, 400 | "outputs": [ 401 | { 402 | "data": { 403 | "text/plain": [ 404 | "['Hello, A|1c3!', 'Hello, B0b!', 'Hello, C7hu|hu!', 'Hello, D4g0n!']" 405 | ] 406 | }, 407 | "execution_count": 47, 408 | "metadata": {}, 409 | "output_type": "execute_result" 410 | } 411 | ], 412 | "source": [ 413 | "# Compose l337() with greet()\n", 414 | "l33tings: list = [ greet(l33t(n)) for n in names ]\n", 415 | "l33tings" 416 | ] 417 | }, 418 | { 419 | "cell_type": "markdown", 420 | "id": "b5345138-bf28-49eb-9c65-44119368f3a7", 421 | "metadata": {}, 422 | "source": [ 423 | "## Check For Understanding: \n", 424 | "\n", 425 | "Time to write your own function. Remember the password policy from 1-7? Let's functionalize it.\n", 426 | "\n", 427 | "## Objectives\n", 428 | "\n", 429 | "1. Write a function called `valid_password()` that takes a single `str` argument and validates it against our password policy. If the password is valid, the function returns `True`. If it fails, it returns `False`.\n", 430 | "2. Send the policy function (without parens) to `testme()`. Yes, you can pass functions as arguments!\n" 431 | ] 432 | }, 433 | { 434 | "cell_type": "code", 435 | "execution_count": null, 436 | "id": "7c269cd0-7e78-4d05-ac9b-68c41a9b7a19", 437 | "metadata": {}, 438 | "outputs": [], 439 | "source": [ 440 | "# Don't delete this line!\n", 441 | "from testme import *\n", 442 | "\n", 443 | "# Write your password validator function\n", 444 | "def ____():\n", 445 | " pass\n", 446 | "\n", 447 | "\n", 448 | "testme(_)\n" 449 | ] 450 | } 451 | ], 452 | "metadata": { 453 | "kernelspec": { 454 | "display_name": "Python 3 (ipykernel)", 455 | "language": "python", 456 | "name": "python3" 457 | }, 458 | "language_info": { 459 | "codemirror_mode": { 460 | "name": "ipython", 461 | "version": 3 462 | }, 463 | "file_extension": ".py", 464 | "mimetype": "text/x-python", 465 | "name": "python", 466 | "nbconvert_exporter": "python", 467 | "pygments_lexer": "ipython3", 468 | "version": "3.8.10" 469 | } 470 | }, 471 | "nbformat": 4, 472 | "nbformat_minor": 5 473 | } 474 | -------------------------------------------------------------------------------- /1-9_functions/testme.py: -------------------------------------------------------------------------------- 1 | """ 2 | 🎵 Your password must be... 🎵 3 | 4 | 🎵 Longer than eight characters 🎵 5 | 6 | 🎵 But not just alphabetical 🎵 7 | 8 | 🎵 Be sure to use a capital 🎵 9 | 10 | 🎵 And digits would be radical 🎵 11 | 12 | 🎵 And for that extra dash of strength 🎵 13 | 14 | 🎵 Use symbols to extend the length 🎵 15 | 16 | 🎵 If you follow these few simple rules 🎵 17 | 18 | 🎵 Your password won't be guessed by fools 🎵 19 | 20 | 🎵 And that's the password song!🎵 21 | """ 22 | 23 | pw_tests = { 24 | "should pass": "AbCdefgHijkL1234!", 25 | "length": "abc123", 26 | "mixed case": "abc12345ksla", 27 | "digits": "AbCdefgHijkL", 28 | "symbols": "AbCdefgHijkL134", 29 | 30 | } 31 | 32 | def testme(pw_func): 33 | for t in pw_tests: 34 | pw = pw_tests[t] 35 | if t == "should pass": 36 | print("[+] Testing known good password") 37 | if pw_func(pw): 38 | print("[+] Good password validates") 39 | else: 40 | print("[-] Good password fails validation") 41 | else: 42 | print(f"[+] Testing validation for {t}") 43 | if pw_func(pw): 44 | print(f"[-] Password validated without {t}") 45 | else: 46 | print(f"[+] Password failed validation without {t}") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 The Taggart Institute 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-for-defenders 2 | Python for Defenders Course Resources 3 | 4 | ## How to Use this Repo 5 | 6 | This repository is intended for use in conjunction with the course [Python For Defenders, Pt. 1](https://learn.taggart-tech.com/p/python-for-defenders-pt1). 7 | 8 | To use the repo, make sure that [Poetry](https://python-poetry.org) has been installed. Then: 9 | 10 | ```shell 11 | git clone https://github.com/The-Taggart-Institute/python-for-defenders 12 | cd python-for-defenders 13 | poetry install 14 | poetry init 15 | jupyter lab 16 | ``` 17 | 18 | This will launch a Jupyter Lab environment on your machine. Follow the Notebooks in conjunction with the video lectures. 19 | 20 | ## Changelog 21 | 22 | * **9/21/22:** Added Usage; fixed test for 1-2. 23 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "python-for-defenders" 3 | version = "0.1.0" 4 | description = "Code for the course on The Taggart Institute" 5 | authors = ["Michael Taggart "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | jupyterlab = "^3.4.4" 10 | ipywidgets = "^7.7.1" 11 | plotly = "^5.9.0" 12 | pandas = "^1.4.3" 13 | jupyter-dash = "^0.4.2" 14 | jupyter = "^1.0.0" 15 | JLDracula = "^0.1.0" 16 | requests = "^2.28.1" 17 | 18 | [tool.poetry.dev-dependencies] 19 | 20 | [build-system] 21 | requires = ["poetry-core>=1.0.0"] 22 | build-backend = "poetry.core.masonry.api" 23 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "[+] Installing dependencies" 4 | 5 | sudo apt update 6 | sudo apt install -y python3-pip python3-venv 7 | 8 | echo "[+] Installing Poetry" 9 | 10 | curl -sSL https://install.python-poetry.org | python3 - 11 | 12 | echo "[+] Installing Poetry Dependencies" 13 | poetry install 14 | 15 | echo "[+] Done! Run 'poetry shell' to activate the environment" 16 | --------------------------------------------------------------------------------