diff --git a/01_type_hints/OUTLINE.md b/01_type_hints/OUTLINE.md deleted file mode 100644 index 6153d13cff1ab22f942090a5f4cee269f4074dc4..0000000000000000000000000000000000000000 --- a/01_type_hints/OUTLINE.md +++ /dev/null @@ -1,89 +0,0 @@ -## Type systems - -Many programming languages have types built into their syntax. There are different axes of typing systems. Let's talk about a few of them. - -Static/Dynamic Typing is about when type information is acquired (at compile time or at runtime) - -Strong/Weak Typing is about how strictly types are distinguished (e.g. whether the language tries to do an implicit conversion from strings to numbers), but there's no clear definition of what these terms mean. - -Java is statically typed: - -```java -String my_var = "hello"; -my_var = 5; // error! -``` - -Python on the other hand is dynamically typed: - -```python -my_var = "Hello" # it's a string -my_var = 5 # now it's an integer; perfectly OK -``` - -Python is also quite strict and requires explicit type conversion in many cases: - -```python -my_var = 5 + "hello" # would raise an error since it does not want to cast one type to the other implicitly. -``` - -As opposed to for example JavaScript, which is more loose (weakly typed): - -```javascript -let myVar = 5 + "hello"; -``` - -Other ways of categorizing type systems: - -- Manifest vs inferred: whether you need to specify the type of each variable explicitly, or whether it can be inferred (C or Java has manifest typing, Python has inferred typing). -- Nominal vs structural: in a nominal typing system, the way that types are compared is by looking at the type names. Structural typing looks at the structure of objects and if they match, the types match. - -## Duck typing - -A special case is duck typing. Duck typing is like structural typing, but it's not the same. Structural typing is a static typing system that determines whether two types are equivalent by checking that their structures match. Duck typing is dynamic and only looks at the compatibility for the part that's actually accessed during runtime. - -The term "duck typing" comes from the saying “If it walks like a duck, and it quacks like a duck, then it must be a duck.” --> If the part you're accessing during runtime looks like what you expect, the types match. - -(show duck typing example) - -## Specifying type annotations in Python - -- In Python, you have the option to explicitly write type annotations, or: type hints, but you don't have to. -- Show basic examples. - -```python -my_var: str = "hi" -def func(arg: int): - pass -``` - -- Functions can also have return types -- Note that in recent version of Python, we use list instead of typing.List which has been deprecated since Python 3.9 due to PEP 585. -- A class is also a type! - -## Special case: protocols - -- In Python, you can use Protocol class to specify a sort of interface that uses the duck typing mechanism. -- Show SMTP example before and add protocol. - -## How are types helpful - -The Python interpreter doesn't check these types at interpret time, but raises an error at runtime if something's wrong. Types are not needed to write a correct Python program. They are very helpful though in establishing what a function expects or what kind of thing a variable contains. Tools such as Pylance are also helpful here. - -```python -def compute_stats(users, plans, products): - # some complicated code here that computes stats -``` - -What is `users` here? A list of User objects? A list of strings? A set? A number? We don't know. Only way to find out is look at the actual implementation. Worse: with missing types you could end up in a situation where the function is used with completely different argument types. You change the function and then everything breaks. - -Type hints are a great way to improve the readability of your code, which is very helpful when you work on code for a longer time, or with a team of people. Types also help you can errors earlier on in the development process, which is going to save you a lot of time. I highly recommend you start using them. Once you do, I promise you don't want to go back. - -In VSCode, you have control over the strictness of the type checker. In your settings file, you can specify this as follows: - -```json -"settings": { - "python.analysis.typeCheckingMode": "strict", -} -``` - -(choose between off, basic and strict) diff --git a/02_adv_classes/OUTLINE.md b/02_adv_classes/OUTLINE.md deleted file mode 100644 index 486dd5bdd945b06fc33f19d05035829cf44a8a2b..0000000000000000000000000000000000000000 --- a/02_adv_classes/OUTLINE.md +++ /dev/null @@ -1,64 +0,0 @@ -# What is a dataclass and how is it different from a regular class? - -- Mainly aimed at more data-oriented classes (i.e. Points and Vectors vs Button or PaymentService) -- Adds convenient mechanisms such as easier comparison, string representation, and easier definition and initialization -- Show example: PersonNoDataClass vs Person - -# Usage of dataclasses - -Several reasons for me to use dataclasses are: - -1. It's much shorter to define a class with a couple of attributes than with a regular class -2. I find it clearer to see what data is contained within a class if it's a dataclass -3. Already having a reasonable implementation for printing and initializing an object is very useful - -One thing I don't like about dataclasses is that they 'abuse' the concept of a class variable to represent instance variables. It's confusing, especially to beginners. And if you forget the @dataclass decorator, you end up with a bunch of class variables and you may not even realize it. - -# Different things you can do with a dataclass - -- default values for fields - - add an active field to Person set to True -- default factory for lists/dicts - - initialize a list of email addresses -- using a function for a default factory - - add an id field with a function to generate the id -- exclude attributes from the initializer - - add an init=False to the id field so we can't set it with the initializer -- Post init - - Show example of adding a search string after initialization that uses the other instance variables -- private attributes of a class - - Make the search string "private", and remove it from the initializer -- exclude certain attributes from the representation - - Exclude the search string using the option repr=False - -## kw_only (added in 3.10) - -If true (the default value is False), then all fields will be marked as keyword-only. The **init**() parameter generated from a keyword-only field must be specified with a keyword when **init**() is called. - -# Properties - -- Properties are a mechanism in Python to easily get a computed value from a class (using a getter), or to store a value (using a setter). -- This can be helpful to encapsulate the data inside the class. -- You can also use properties to compute values that are not stored directly as a value but that are still valuable. - -- Start by adding getter and setter method to VideoClip. - -- Switch to using property decorator. - -# Customizing classes - -## str and repr - -The difference between str() and repr() is: The str() function returns a user-friendly description of an object. The repr() method returns a developer-friendly string representation of an object. If you're using dataclasses, the repr dunder method is generated automatically for you. If you want to learn more about dataclasses, click the link in the top to watch a video that shows you everything you need to know. - -In f-strings, the **str** dunder method is used by default to construct a string representation of an object, but if you use the !r extension it calls the **repr** dunder method instead. - -## slots (for more performance) - -Normally, objects store their attributes in a dictionary (held by the dunder dict variable). You can change this to use the slots dunder variable instead, which has faster access (show example that illustrates this - I measured about 20-25% improvement). Slots also use less memory. - -Most important caveat with **slots** is multiple inheritance. When more than 1 superclass has the dunder slots defined, the code doesn't run (see the PersonEmployee class in slots). - -So, that's yet another reason to avoid multiple inheritance. I'm pretty opinionated about multiple inheritance (and mixins). I talk about that in my course at length. I won't go into too much detail in this video, but my general advice is to not use multiple inheritance or mixins at all. - -I get why the default is that slots are not used. Setting slots as default would break a lot of existing Python code. However, I think it would be nice if a 'strict' mode was added to Python that limits a couple of things: slots by default instead of dict, multiple inheritance is not allowed, some of the dunder methods can't be used anymore like **init_subclass** or **set_attr**, perhaps stricter type enforcement or exception handling as well. The interpreter can then make more assumptions about what the script does and thus improve performance and reliability. With JavaScript something similar was done by introducing strict mode that turns some of the silent errors into actually throwing errors, and imposes some limitations on global use, this in functions, and variable names. diff --git a/03_functools/OUTLINE.md b/03_functools/OUTLINE.md deleted file mode 100644 index cb97847926ba2afba774501ab0ee579d0f8f1d64..0000000000000000000000000000000000000000 --- a/03_functools/OUTLINE.md +++ /dev/null @@ -1,33 +0,0 @@ -# Functools - -## Introduction - -- In Python, everything is an object. Even a literal int value! And if an object has the **call** dunder method, it's a callable (a function!). -- Show very basic example of what a function is. - -## Higher-order functions - -- Passing functions as parameters, or return a function as a value (higher-order functions) -- Lambda functions are anonymous functions -- Closures - -## Partial function application - -- Partial function application means that you create a new function from an existing function, with some of the arguments already applied. -- Show partial example. - -## Cached property - -- With a cached property, you can define a property and have it be computed only once. -- Whenever you call the property afterward, it's going to use the cached value instead of doing the computation. -- Show example from the documentation. - -## Single dispatch - -- Single dispatch allows you to do something similar to what function overloading does in a language like C. You define a generic function but then register varieties of that function that accept different types. -- Show single dispatch example. Cover registering a function with the decorator, using union types, or using the functional version instead of the decorator (for existing functions or for lambda functions) - -## Final thoughts - -- Functools library is a very interesting collection of tools. -- I think people are not using these enough, while more functional code can often be shorter and simpler than object-oriented code. diff --git a/04_asyncio/OUTLINE.md b/04_asyncio/OUTLINE.md deleted file mode 100644 index e195a3da23fcdad038ca6ea9f219eb444aea8334..0000000000000000000000000000000000000000 --- a/04_asyncio/OUTLINE.md +++ /dev/null @@ -1,44 +0,0 @@ -## Intro - -- Especially if you're starting to use Python more extensively for work or project: you'll certainly encounter interacting with APIs. -- This means handling API requests efficiently otherwise program is going to be very slow. -- In Python, there's the asyncio module to help implement this, by relying on concurrency. - -## Concurrent vs Parallel computing & asyncio recap - -You may have heard the terms concurrency and parallel computing before, but what's the difference? True parallel computing means that an application runs multiple tasks at the same time, where each task runs on a separate processing unit. - -Concurrency means that an application is making progress on more than one task at the same time, but may switch between these tasks instead of actually running them in parallel. If an application works on tasks A and B, it doesn't have to finish A before starting B. It can do a little bit of A, then switch to doing a little bit of B, and back again A, and so on. - -This answer on StackOverflow nicely illustrates the difference: "Concurrency is two lines of customers ordering from a single cashier (lines take turns ordering); Parallelism is two lines of customers ordering from two cashiers (each line gets its own cashier)" (see https://stackoverflow.com/questions/1050222/what-is-the-difference-between-concurrency-and-parallelism). If you translate this back to computers, each cashier is a processing unit, a CPU core, each customer is a task that the processor needs to take care of. - -Modern computers use a combination of parallelism and concurrency. Your CPU might have 2, 4, 8, or more cores that can perform tasks in parallel. You OS will run 10s to 100s of different tasks concurrently. A subset of these tasks are actually running in parallel while the OS seemlessly switches between the tasks. - -Parallelism in Python has a caveat, which is the Global Interpreter Lock. Any time you run Python code, it needs to acquire an interpreter lock. There are reasons for this that I won't go into in this video, but it effectively means that Python code doesn't run in parallel. There are ways around this, for example by relying on multiple processes instead of multiple threads, or by switching to an interpreter that doesn't have the lock. As opposed to parallelism, concurrency on the other hand, works really well in Python, especially since version 3.10. - -Why is concurrency a smart way to do computing? Well, it so happens that many tasks involve waiting. Our applications are waiting for files to be read or written to, they're constantly communicating with other services over the Internet, or, they're waiting for you to interact with a GUI and press a button. - -It considerably speeds up things if your computer can do something else while waiting for that network response or for you to click the button. In other words, concurrency is a crucial mechanism for making our computers work efficiently in this age of connectivity. - -The asyncio package in Python gives you the tools to control how concurrency is handled within your application. - -There are two important keywords associated with concurrent code in Python: async and await. - -If you write async in front of a method or function, you indicate that it's allowed to run this method or function concurrently. Await gives you control over the order that things are being executed in. If you write await in front of a concurrent statement, this means that the portion written below that statement can only be executed after the concurrent statement has completed. - -Being able to do this is important, when the next part of your code relies on the result of the previous part. This is often the case: you need to wait until you get the data back from the database. You need the data from a network request in order to continue, and so on. - -## Recap of async - -- Show async and await syntax (using the asyncio_recap example without explaining http_get but simply using it) -- Show gather to run multiple http requests concurrently (asyncio_gather for an example and time difference) - -## Turning non-async code into async code - -- First show the wrong way of trying to do it (i.e. create a task, then do the request, then await the task to complete) - -- Show the to_thread method from asyncio. This creates a separate thread that will run concurrently with other threads (or the OS may decide to run threads in parallel on separate CPUs). - -- Show the simple req_http module that allows for async requests. - -- The to_thread function from asyncio creates a thread to run a blocking function in, and then integrates it with the event loop. diff --git a/05_iterators/OUTLINE.md b/05_iterators/OUTLINE.md deleted file mode 100644 index 0aad3a40015005e41bace24dcd9f0bd828b3e016..0000000000000000000000000000000000000000 --- a/05_iterators/OUTLINE.md +++ /dev/null @@ -1,34 +0,0 @@ -## What is an Iterator? - -- An iterator is an object that can be iterated upon, meaning that you can traverse through all the values. -- What this means in Python is that an iterator is an object which implements the iterator protocol, which consist of the methods **iter**() and **next**(). -- Iterators are everywhere in Python. They are elegantly implemented within for loops, comprehensions, generators (which I'll talk about later on in this course) etc. but are hidden in plain sight. - -## Iterator vs Iterable - -- You might have heard both terms being used → they’re not the same thing! -- An iterable is an object that you can get an iterator from using the iter() method. -- An iterator is an iterable that also has a next dunder method to get the next element. -- Lists, tuples, dictionaries, sets, even strings are all iterables. By calling _iter_, you can get an iterator from them. By the way, I find how iterators and iterables are organized a bit strange. It means that each iterator can return another iterator, and so on… I’m not sure I understand the function of this, perhaps this offers a mechanism to ‘restart’ an iterator (REVIEWERS: do you have any idea?) -- Show iterator examples (`iterator_basics.py`), using `next`, using a for-loop and show the equivalent while-loop. -- The built-in function iter() can be called with two arguments where the first argument must be a callable object (function) and second is the sentinel. The iterator calls this function until the returned value is equal to the sentinel. -- Iterables provide a nice way of abstracting the data structure from the code that traverses it. See for example `iterator_abstraction.py`. You can replace the `line_items` list by a tuple or a set (provided you add a hash dunder method), and the `print_totals` function works because it only expects an iterable. - -## Build your own iterators - -- Building an iterator from scratch is easy in Python. We just have to implement the **iter**() and the **next**() methods. -- The **iter**() method returns the iterator object itself. If required, some initialization can be performed. -- The **next**() method must return the next item in the sequence. On reaching the end, and in subsequent calls, it must raise StopIteration. -- Iterators can be finite or infinite -- Show `custom_iterator.py` - -## Itertools - -- Itertools is a module that provides functions that work on iterators and produce more complex iterators. Together this forms an iterator algebra. -- For example, let’s say you have a list of prices and quantities and you want to create another list containing the subtotals. You could do this naively with a for-loop and compute it yourself, or, you could use the `starmap` function, provide it with a multiplication function and a an iterable of tuples as the second parameter. -- Itertools has quite few useful functions like that and you can combine them in various ways to create complex iterator behavior. It’s also pretty fast and memory-efficient. -- Show examples (see `itertools_examples.py`) - -## Final thoughts - -- There's another way to build iterators, and that's by using so-called generator functions. diff --git a/06_generators/OUTLINE.md b/06_generators/OUTLINE.md deleted file mode 100644 index 9c6bcd83a744110098f8104f130f73c661b76673..0000000000000000000000000000000000000000 --- a/06_generators/OUTLINE.md +++ /dev/null @@ -1,54 +0,0 @@ -## Introduction - -- Generator functions in Python are a special kind of function that return a lazy iterator. They were introduced in PEP255. -- What does a "lazy iterator" mean? Well, a lazy iterator behaves like a normal iterator (so you can loop over it like for example a list). However, a lazy iterator doesn't store its contents in memory, it creates the content only when you ask for it. -- Python generators are a simple way of creating iterators. Generators automatically create a class with iter and next methods, it keeps track of the iterator's internal state and it raises a StopIteration error when there are no more values (in case of a finite iterator). - -## Creating Generators in Python - -- To create a generator function in Python is really easy. It's the same as defining a normal function, but it doesn't have a return statement but a yield statement. -- Both yield and return some kind of value (and that value can be None). -- The difference is that a return statement terminates the function, a yield statement pauses the function saving the state, including any local variables. When you call it the next time, it continues form there. -- The generator object can be iterated only once. If you want to perform the iteration again, you need to call the generator function again to get a new generator object. -- Show basic example of a generator function. Both using next and with a for loop -- Show generator type hints. - -## Generators and the send method - -- The send() method on a generator allows us to resume the execution of that generator, and “send” a value into the generator function. This value then becomes the result of the current yield expression. You can use this to pass data into the generator after it has created the iterator object. You can't achieve this by adding parameters to the generator function, because those parameters are supplied before you create the iterator and you can't change their values afterward. -- The send() method also allows us to use the generator like an iterator, where we can “send” None and get back the next value from the iterator. -- Are there many use cases for this? In my opinion, no, you will probably never need this. However, this is called "next-level" Python, and this feature is definitely "next-level". -- I would generally advise against combining returns and yields as it will be harder to understand in what cases the function actually terminates. I personally always opt for the simplest solution if possible. - -## Generators and loops - -- The examples I gave above are not really that useful in practice. You will normally use a generator function with a loop. -- Show basic generator loop example. - -## Generator expressions - -- Much like lambda functions, which are anonymous functions, generator expressions create anonymous generator functions. -- The way you write a generator expression is very similar to a list comprehension, but you use parentheses instead of square brackets. -- The difference is that a list comprehension creates the entire list, whereas a generator expression produces one item at a time. -- Because of that, generator expressions are more memory efficient than list comprehensions. -- Show generator expressions example. - -## Async generators - -- You can also use generators in combination with asynchronous code (see asyncio example). -- This integrates really nicely, simply use a yield expression in an async function. -- Use the AsyncGenerator type hint. Note: asynchronous generators don't allow for returning a value like normal generators. So, the type hint has only two type parameters. -- There is an asend method that sends a yield value asynchronously to a generator object. - -## Final thoughts - -Why are generators useful? - -- They're an easy way to create iterators. -- They're memory efficient and avoid needless computations. -- Great to represent streams (like infinite data streams over a network). - Caveats: -- Generators might lead to code being harder to understand (especially if you combine returns with yields or do complicated pipelines of generators. -- Because not all the code is immediately executed, you might encounter errors later and unexpectedly. - -Overall, generators are something you won't use everyday, but they are a nice tool to use from time to time. diff --git a/06_generators/__pycache__/req_http.cpython-311.pyc b/06_generators/__pycache__/req_http.cpython-311.pyc deleted file mode 100644 index 386e207cca71bc32b08a457c7e9e86501fcbf390..0000000000000000000000000000000000000000 Binary files a/06_generators/__pycache__/req_http.cpython-311.pyc and /dev/null differ diff --git a/07_context_managers/OUTLINE.md b/07_context_managers/OUTLINE.md deleted file mode 100644 index df56ca33f16c6bf84ec69d026d6576bcf663cef1..0000000000000000000000000000000000000000 --- a/07_context_managers/OUTLINE.md +++ /dev/null @@ -1,112 +0,0 @@ -## Introduction - -- The final Python feature I'd like to cover in this course is the context manager. -- Builds on other features such as the generator from the previous lessons. -- Also compatible with asynchronous code as I'll show you later on. -- I'll cover how to use context managers and how to build your own. - -## Explain the example without context manager (screencast) - -## What are context managers? - -- Context managers allow you to to control setup and teardown of any sort of resource in way that minimizes the chance of you forgetting to do the teardown, especially in the presence of exceptions. -- If you've ever opened a file in Python, you've probably used the with statement to manage the file. This is a context manager, and it's a good way to manage resources. - -```python -with open('file.txt') as f: - f.read() -``` - -Let's look a bit closer at what's happening in a with statement: - -```python -with EXPRESSION as VARIABLE: - DO SOMETHING -``` - -This is what's happening behind the scenes: - -```python -manager = (EXPRESSION) -enter = type(manager).__enter__ -exit = type(manager).__exit__ -value = enter(manager) -hit_except = False - -try: - VARIABLE = value - DO SOMETHING -except: - hit_except = True - if not exit(manager, *sys.exc_info()): - raise -finally: - if not hit_except: - exit(manager, None, None, None) -``` - -The SQLite connection can itself be used as a context manager, like so: - -```python -with sqlite3.connect('application.db') as conn: - cursor = connection.cursor() - cursor.execute("SELECT * FROM blogs") -``` - -- The advantage here is that the connection will automatically roll back commit or rollback transactions but the connection object does not automatically close so it should be closed manually afterwards. And then we'd still have to make sure that the connection is closed in all cases (even if an exception is raised). -- The nice thing about Python is that we can create our own context managers. Let's create one for the SQLite example, that automatically closes the connection for us. - -## Basic usage (screencast) - -- Create SQLite context manager (showing both the class and the decorator syntax) -- Mention generator - -## Context managers and async (talking head) - -Context managers also support async/await syntax. Here's a simple example of an asynchronous context manager taken from the Python documentation: - -```python -from contextlib import asynccontextmanager - -@asynccontextmanager -async def get_connection(): - conn = await acquire_db_connection() - try: - yield conn - finally: - await release_db_connection(conn) - -async def get_all_users(): - async with get_connection() as conn: - return conn.query('SELECT ...') -``` - -## Context managers + async example (using aiosqlite, screencast) - -## Nesting context managers - -If you're nesting with statements, like this: - -```python -with open_socket() as s: - with open_file() as f: - with open_database() as db: - do_stuff(s, f, db) # ugh -``` - -You can write this differently, because Python allows you to create multiple resources in a single with statement. And since Python 3.10, you can use the parenthesis syntax to split them over multiple lines, like so: - -```python -with ( - open_socket() as s, - open_file() as f, - open_database() as db -): - do_stuff(s, f, db) # ugh -``` - -## When to (not) use context managers - -- The main reason to use context managers is to help allocate and cleanup resources. This is helpful for example if you want to open a file, a database connection, or a network connection. If an error occurs, the context manager mechanism will automatically clean up the resource. -- It does introduce an extra code indentation level. If you don't need to cleanup any resources, you probably don't really need to use a context manager. -- From a software design perspective though, it's nice that you can group creating and destroying a resource in a single place. diff --git a/07_context_managers/exercise1.zip b/07_context_managers/exercise1.zip deleted file mode 100644 index cbe60b77a3c56a7a07a20c17ab735b8841b027a4..0000000000000000000000000000000000000000 Binary files a/07_context_managers/exercise1.zip and /dev/null differ diff --git a/07_context_managers/solution1.zip b/07_context_managers/solution1.zip deleted file mode 100644 index 2c5b1650547c54983d1c09b754a107e20202aee3..0000000000000000000000000000000000000000 Binary files a/07_context_managers/solution1.zip and /dev/null differ diff --git a/02_adv_classes/dataclasses_example.py b/adv_classes/dataclasses_example.py similarity index 100% rename from 02_adv_classes/dataclasses_example.py rename to adv_classes/dataclasses_example.py diff --git a/02_adv_classes/exercise1.py b/adv_classes/exercise/exercise.py similarity index 100% rename from 02_adv_classes/exercise1.py rename to adv_classes/exercise/exercise.py diff --git a/02_adv_classes/solution1.py b/adv_classes/exercise/solution.py similarity index 100% rename from 02_adv_classes/solution1.py rename to adv_classes/exercise/solution.py diff --git a/02_adv_classes/kw_only.py b/adv_classes/kw_only.py similarity index 100% rename from 02_adv_classes/kw_only.py rename to adv_classes/kw_only.py diff --git a/02_adv_classes/properties.py b/adv_classes/properties.py similarity index 100% rename from 02_adv_classes/properties.py rename to adv_classes/properties.py diff --git a/02_adv_classes/slots_dataclass.py b/adv_classes/slots_dataclass.py similarity index 100% rename from 02_adv_classes/slots_dataclass.py rename to adv_classes/slots_dataclass.py diff --git a/04_asyncio/asyncio_gather.py b/asyncio/asyncio_gather.py similarity index 100% rename from 04_asyncio/asyncio_gather.py rename to asyncio/asyncio_gather.py diff --git a/04_asyncio/asyncio_recap.py b/asyncio/asyncio_recap.py similarity index 100% rename from 04_asyncio/asyncio_recap.py rename to asyncio/asyncio_recap.py diff --git a/04_asyncio/exercise1.py b/asyncio/exercise/exercise.py similarity index 100% rename from 04_asyncio/exercise1.py rename to asyncio/exercise/exercise.py diff --git a/04_asyncio/solution1.py b/asyncio/exercise/solution.py similarity index 100% rename from 04_asyncio/solution1.py rename to asyncio/exercise/solution.py diff --git a/04_asyncio/req_http.py b/asyncio/req_http.py similarity index 100% rename from 04_asyncio/req_http.py rename to asyncio/req_http.py diff --git a/04_asyncio/sync_to_async_before.py b/asyncio/sync_to_async_before.py similarity index 100% rename from 04_asyncio/sync_to_async_before.py rename to asyncio/sync_to_async_before.py diff --git a/04_asyncio/sync_to_async_correct.py b/asyncio/sync_to_async_correct.py similarity index 100% rename from 04_asyncio/sync_to_async_correct.py rename to asyncio/sync_to_async_correct.py diff --git a/04_asyncio/sync_to_async_wrong.py b/asyncio/sync_to_async_wrong.py similarity index 100% rename from 04_asyncio/sync_to_async_wrong.py rename to asyncio/sync_to_async_wrong.py diff --git a/07_context_managers/application.db b/context_managers/application.db similarity index 100% rename from 07_context_managers/application.db rename to context_managers/application.db diff --git a/07_context_managers/create_db.py b/context_managers/create_db.py similarity index 100% rename from 07_context_managers/create_db.py rename to context_managers/create_db.py diff --git a/07_context_managers/exercise1.py b/context_managers/exercise/exercise.py similarity index 100% rename from 07_context_managers/exercise1.py rename to context_managers/exercise/exercise.py diff --git a/07_context_managers/solution1.py b/context_managers/exercise/solution.py similarity index 100% rename from 07_context_managers/solution1.py rename to context_managers/exercise/solution.py diff --git a/07_context_managers/requirements.txt b/context_managers/requirements.txt similarity index 100% rename from 07_context_managers/requirements.txt rename to context_managers/requirements.txt diff --git a/07_context_managers/sqlite.py b/context_managers/sqlite.py similarity index 100% rename from 07_context_managers/sqlite.py rename to context_managers/sqlite.py diff --git a/07_context_managers/sqlite_context.py b/context_managers/sqlite_context.py similarity index 100% rename from 07_context_managers/sqlite_context.py rename to context_managers/sqlite_context.py diff --git a/07_context_managers/sqlite_context_async.py b/context_managers/sqlite_context_async.py similarity index 100% rename from 07_context_managers/sqlite_context_async.py rename to context_managers/sqlite_context_async.py diff --git a/07_context_managers/sqlite_context_decorator.py b/context_managers/sqlite_context_decorator.py similarity index 100% rename from 07_context_managers/sqlite_context_decorator.py rename to context_managers/sqlite_context_decorator.py diff --git a/project.md b/final_project.md similarity index 100% rename from project.md rename to final_project.md diff --git a/03_functools/cached_property.py b/functools/cached_property.py similarity index 100% rename from 03_functools/cached_property.py rename to functools/cached_property.py diff --git a/03_functools/closure_partial.py b/functools/closure_partial.py similarity index 100% rename from 03_functools/closure_partial.py rename to functools/closure_partial.py diff --git a/03_functools/exercise1.py b/functools/exercise/exercise.py similarity index 100% rename from 03_functools/exercise1.py rename to functools/exercise/exercise.py diff --git a/03_functools/solution1.py b/functools/exercise/solution.py similarity index 100% rename from 03_functools/solution1.py rename to functools/exercise/solution.py diff --git a/03_functools/func_param_lambda.py b/functools/func_param_lambda.py similarity index 100% rename from 03_functools/func_param_lambda.py rename to functools/func_param_lambda.py diff --git a/03_functools/single_dispatch.py b/functools/single_dispatch.py similarity index 100% rename from 03_functools/single_dispatch.py rename to functools/single_dispatch.py diff --git a/06_generators/exercise1.py b/generators/exercise/exercise.py similarity index 100% rename from 06_generators/exercise1.py rename to generators/exercise/exercise.py diff --git a/06_generators/solution1.py b/generators/exercise/solution.py similarity index 100% rename from 06_generators/solution1.py rename to generators/exercise/solution.py diff --git a/06_generators/generator_asyncio.py b/generators/generator_asyncio.py similarity index 100% rename from 06_generators/generator_asyncio.py rename to generators/generator_asyncio.py diff --git a/06_generators/generator_basic.py b/generators/generator_basic.py similarity index 100% rename from 06_generators/generator_basic.py rename to generators/generator_basic.py diff --git a/06_generators/generator_expression.py b/generators/generator_expression.py similarity index 100% rename from 06_generators/generator_expression.py rename to generators/generator_expression.py diff --git a/06_generators/generator_loop.py b/generators/generator_loop.py similarity index 100% rename from 06_generators/generator_loop.py rename to generators/generator_loop.py diff --git a/06_generators/generators_combine.py b/generators/generators_combine.py similarity index 100% rename from 06_generators/generators_combine.py rename to generators/generators_combine.py diff --git a/06_generators/req_http.py b/generators/req_http.py similarity index 100% rename from 06_generators/req_http.py rename to generators/req_http.py diff --git a/intro.md b/intro.md deleted file mode 100644 index 56ce0abae1101f88cd05153bcb0d865d10acf102..0000000000000000000000000000000000000000 --- a/intro.md +++ /dev/null @@ -1,8 +0,0 @@ -# Introduction - -- Welcome to this course that teaches intermediate to advanced programming in Python. -- It's a curated course: contains what I think are the things that will have the most impact on the quality of your code. -- I'll cover topics like type hints, advanced features of classes, functional programming, asynchronous programming, iterators, generators and more. -- You can download all the code examples that I show in this course from the project and resources tab. -- And there's also a project you can dive into to practice with these concepts yourself. -- If you like this course, you might also like my YT channel: https://www.youtube.com/arjancodes. Learn more about software design. diff --git a/05_iterators/countries.txt b/iterators/countries.txt similarity index 100% rename from 05_iterators/countries.txt rename to iterators/countries.txt diff --git a/05_iterators/custom_iterator.py b/iterators/custom_iterator.py similarity index 100% rename from 05_iterators/custom_iterator.py rename to iterators/custom_iterator.py diff --git a/05_iterators/exercise1.py b/iterators/exercise/exercise1.py similarity index 100% rename from 05_iterators/exercise1.py rename to iterators/exercise/exercise1.py diff --git a/05_iterators/exercise2.py b/iterators/exercise/exercise2.py similarity index 100% rename from 05_iterators/exercise2.py rename to iterators/exercise/exercise2.py diff --git a/05_iterators/solution1.py b/iterators/exercise/solution1.py similarity index 100% rename from 05_iterators/solution1.py rename to iterators/exercise/solution1.py diff --git a/05_iterators/solution2.py b/iterators/exercise/solution2.py similarity index 100% rename from 05_iterators/solution2.py rename to iterators/exercise/solution2.py diff --git a/05_iterators/iterator_abstraction.py b/iterators/iterator_abstraction.py similarity index 100% rename from 05_iterators/iterator_abstraction.py rename to iterators/iterator_abstraction.py diff --git a/05_iterators/iterator_basics.py b/iterators/iterator_basics.py similarity index 100% rename from 05_iterators/iterator_basics.py rename to iterators/iterator_basics.py diff --git a/05_iterators/itertools_examples.py b/iterators/itertools_examples.py similarity index 100% rename from 05_iterators/itertools_examples.py rename to iterators/itertools_examples.py diff --git a/outro.md b/outro.md deleted file mode 100644 index cce7e287d5461b855bcf897d07f64d8b257b0ccf..0000000000000000000000000000000000000000 --- a/outro.md +++ /dev/null @@ -1,8 +0,0 @@ -# Outro - -- Thanks for following this course. -- Hope it taught you a few nice new tools you can use to to improve the Python code you write. -- Had a lot of fun recording this course! -- There's a ton more things to dive into. One in particular is learning how to better review code. If you enjoyed this course, I have a free Code Diagnosis workshop you can follow (point to website). -- If you have any questions or suggestions, please post a comment here. -- Thanks for watching and take care! diff --git a/01_type_hints/duck_typing.py b/type_hints/duck_typing.py similarity index 100% rename from 01_type_hints/duck_typing.py rename to type_hints/duck_typing.py diff --git a/01_type_hints/exercise1.py b/type_hints/exercise/exercise.py similarity index 100% rename from 01_type_hints/exercise1.py rename to type_hints/exercise/exercise.py diff --git a/01_type_hints/solution1.py b/type_hints/exercise/solution.py similarity index 100% rename from 01_type_hints/solution1.py rename to type_hints/exercise/solution.py diff --git a/01_type_hints/protocol_after.py b/type_hints/protocol_after.py similarity index 100% rename from 01_type_hints/protocol_after.py rename to type_hints/protocol_after.py diff --git a/01_type_hints/protocol_before.py b/type_hints/protocol_before.py similarity index 100% rename from 01_type_hints/protocol_before.py rename to type_hints/protocol_before.py diff --git a/01_type_hints/solution1_adv.py b/type_hints/solution1_adv.py similarity index 100% rename from 01_type_hints/solution1_adv.py rename to type_hints/solution1_adv.py