In the recursion question, both functions are doing different things. The recursion is doing a lot of recalculations, while the iterative function calculate each number once. If you wanna say recursion hurts performance, it's better to compare two functions that are well written.
I'm not arguing that removing while iterating causes an index skip. But the reason "indently" is getting printed is because it's the current value stored in the variable "channel" during that iteration. Than the index skip happens which results in bool. It would have been more appropriate to print the list itself on each iteration not just the indexed value.
Exactly. The variables "i" and "channel" have values stored in them for each iteration of the loop. The print statement on line 7 is referencing the values stored in them on line 3, it's not referencing the list. If line 7 was "print(i, channels[i])", then it would have printed "1 ArjanCodes" on the 2nd print instead.
The problem with removing items from the list while iterating is not that 'Indently' is printed but that 'ArjanCodes' is not (it is not processed). In Java, they chosen to support the fail-fast pattern and an exception would be raise in the next iteration.
@@IonicMC except Exception does not actually prevent Keyboard Interruption. If you were to use BaseException, then yes in that case I believe it would prevent Ctrl C in the terminal
Perhaps for simple try blocks, where the exception cases are clear, I agree with the video. As soon as you have a call to a library, you now need to either trust the docs or inspect the library code and also make assumptions about the exact version of the library being used. E.g. sockets, where the exception hierarchy has changed and some exceptions were deprecated.
It's not bad if all you're using is normal exceptions. The real issue is when you start hitting the thousands of lines of code. A simple ValueError doesn't tell you much unless you know what raised it. When you start working with custom exceptions. You'll see why except all is poor practice.
i think the recursion part is wrong. First, you mix the dynamic programming part(nothing to do with python) with the recursion overhead. Avoiding recursion is great if you really need that, but honestly most people using python will never do anything where it really matters. If yes, a single (at)cache will solve the problem with maintaining the readability. If the recursion overhead would matter, python would already be a bad answer, tbh. The second example in recursion is an antipattern, a simple loop would suffice with the possibility to setup a retry number.
So the challenge is to produce an example of code that violates as many of these rules as possible whilst being readable and performant. Bonus points for using descriptive names rather that type annotations.
"Bonus points for using descriptive names rather that type annotations" Okay, so this is a valid point, but it needs to be explained, because it will not be obvious to a lot of people. My take on this idea is that, for non-strongly typed languages, the code is intended to be type-flexible. Type annotations tend to defeat this objective. For example, `def neg(x: int): return -x` might be supposed to return the negative of a number. But if I try to use `neg(1.2)` it fails because I've given it a float, and the type annotation demanded an integer. If I had written `def neg(x): return -x` my float example would have worked. And so would any other numeric type, probably, including future numeric types that I have yet to invent. Also, the `def` is a bit more concise. Maybe I should write `def neg(num): return -num` to hint that the parameter is expected to be numeric. Having said all of this, I use TypeScript professionally instead of JavaScript and I find the TypeScript code to be immensely more maintainable in a commercial setting. So the ideal of dynamically typed programming I just illustrated may need to be taken with a pinch of salt. Possibly it is a case where the pros are outweighed by the cons.
@@therealdebater Is your example not more about poorly typing, rather than not having to type at all. Your code would have worked with typing had you used float as the type instead. Whilst also providing linting, that highlights if your input is invalid.
@@SirZyPA Yes, in a way. I could have used a broader type, or just Any. I think my example was poor. Imagine writing a function to sort an array. Its input might be an array of Any. Or perhaps an array of Comparable. Not sure. Anyway, I think it can be seen that careful choice of type (annotations) is better than no types at all, but it is always a little wordier. I find, in practice, the stronger typing is worth it most of the time.
5:25 another thing about try/except blocks is minimizing the code in the try block, so the 1st print statement should be before: try: user_float = float(user_input) except ValueError as err: ... except TypeError as err: ... else: print ("etc...") and note, the print statement is removed also, only the cast-to-float lives in try because only it is in question
the example of a good recursion usage is actually bad. This is not a case fir recursion, rather you can use while True, and break do loop on success. the recursion approach is bad because you fill the call stack, the stack can overflow
Or you could use filter(), which is arguably even cleaner. This creates an iterable, so if you don't need to store the new list, only iterate over it, even saves the allocation of a new list in memory.
This is a crappy way to do it, like, you're linearly searching the list TWICE since remove does it, too. It's also usually not a problem if you're iterating backwards. Plenty other patterns when you need to edit a list in-place like this, it's only really a problem with iterators and simple for loops (and not only in Python).
The biggest issue I find with error handling is not the exception block, but handling the exception in the exception block and not passing the exception to the calling function. In an API this many times results in it returning a 200 response with no content, and the error being logged internally. So something interesting would be to cover how to do error handling in big multi-layer applications. On example I have is in one program someone made a DB call which could return a not found exception, but then handled the exception to log out the error message and return an empty array. All good, user get's an empty response if not found, but if the DB connection fails (DB not found exception) it was also returning 200 with empty response instead of a 500 internal error.
Personally, the main reason I avoid using recursive algorithms in Python is the lack of tail call optimization, which unfortunately is very unlikely to ever be implemented. Some algorithms, particularly those operating on tree structures, are well-suited for recursion. However, Python is unfortunately not the best language for implementing them.
13:22 I think (I'm not sure) that a better term would be 'early exit' rather than 'guard clause' (which tends to be used in connection with multi-tasking and synchronisation). Early exit is often a neat way to avoid spaghetti if-then-else structures, but it can also be a liability. In a large block of code, an early exit can be easy to miss, and it can make it hard to spot certain kinds of bug. So use it with care; try to avoid it getting buried in a mass of code.
In what situation would an early exit be bad? Youre failing early, you aren't short circuiting a success. Unless you aren't setting some state, but then if its that type of code, you should be returning to a piece of code to make sure thats all set. In C that would be an acceptable use of goto. I don't know what the python way is though.
@@benholroyd5221 Obviously not OP but a problem I caught with it is that if you want to print all conditions that aren't met, you can't do it with the guard clause because you're exiting early. With the nested if...else statements, you can print all of the missing conditions and return false at the end, otherwise return true.
For the endless if-else checks, if you don’t need separate messages for every false boolean and just want to return a bool, just make a new variable (maybe call it returnBool) and set it to the first bool. Now, set it to itself and the second bool. Then, just keep doing that over and over until you reach the last bool. It should look like this: def exampleFunction(bool1: bool, bool2: bool, bool3: bool, bool4: bool) -> bool: returnBool: bool = bool1 returnBool = returnBool and bool2 returnBool = returnBool and bool3 returnBool = returnBool and bool4 return returnBool However, you could also use the all() function. It should look like this: def exampleFunction(bool1: bool, bool2: bool, bool3: bool, bool4: bool) -> bool: return all([bool1, bool2, bool3, bool4])
General exceptions are not always bad. For instance if your program is calling to a JVM instance (i.e. Databricks), Python can’t access the underlying exception types. So you have to catch it in a general except clause, check if str(e) contains the Java error you want to check snd raise again otherwise. So there are totally valid use cases
Regarding the Fibonacci calculation, the readability goes down but what is your opinion on using the exact formula for the nth term? an = [Phi^n - (phi)^n] / Sqrt[5]. Phi = (1 + Sqrt[5]) / 2 phi = (1 - Sqrt[5]) / 2
i did not know the golden ratio could be used to calculate Fibonacci 😮 this makes the function complexity O(1) instead of the O(n) function with for loop, great !
The issue is that this uses floating point numbers and so it doesn't actually work for large n (because of inaccuracy). The absolute best way to do fibonacci on computers is using matrix representation the idea is that you have an vector f = [F(n), F(n+1)] and a matrix M such that M @ f = [F(n+1), F(n+2)]. Then you can use matrix exponentiation to get: ((M ** n) @ [0, 1])[0] = F(n) You can do matrix multiplication rather quickly by repeatedly either squaring the matrix or squaring and multiplying it by M**1. This way you get O(log(n)) runtime and perfect precision. Also works for generalized fibonacci (like what if it's T(n) = T(n-1) + 52T(n-2) - 13T(n-3) instead? Well, you use the same method, just pick a corresponding matrix.) Also the version in the video can be fixed very easily: @functools.cached
For recursion: if recurrsion is still the simplest way to do something and you have enough memory you can cache the results to speed up the process with the decorators lru_cache or simply cache from functools.
Aside from the if..else section as there are use cases for the rest of the examples, what I learned from this is "Thing to not do unless you absolutely know what you're doing."
I did the first mistake. I noticed luckily that that the printed length did not match the number of printed elements from that list. After thunking myself, I asked ChatGPT and it found the bug
20:10 you should return the do_math() because if you would have something after the except block it will run twice because when its done with the new do_math function it will continue with the original and it will run everything that is outside the do math twice
@@HardlineCheatsImagine try being an 'if error' statement. Once the user inputs the correct code, since no error occurred, the reader will read out the else statement, where the 'return' function will be executed.
13:22 when i heard about that, it was a heaven!! Now I cant even think about nesting! 19:39 i would use loop here too, like, return 0 and return 1 in cases if it completed without or with error, and loop accordingly
If you reach a recursion depth of ~1000 you get a RecursionError. Ol’ mate would need to be REALLY drunk and patient to get that error. To get a stack overflow you would need to set sys.setrecursionlimit(n) to an extremely large number and not run out of heap memory before then.
25:10 it would be better to use a guard for the None value instead of an if/else with intentions - just this line instead of an if/else: ' target_list = target_list or []'
The first example shows why you should be working with functional languages. Teasing aside, functional languages can inform better practices. I assume Python has a filter method. That would be perfect for this code.
Pure functional languages are lame and gay, but elements from them in multiparadigm languages are welcome. That first example would be most easily solved with a generator or a list comprehension, calling filter map and reduce are a bit cumbersome and ugly.
These are not bad ideas that are specific to python, but just generally had programming practices, illustrated in Python. As others have said, there are also often exceptions to these rules. E.g. it’s ok to use indented ifs for two levels, if the contained blocks don’t get too long. Catch all exceptions can be useful at the top level to exit the application elegantly. Recursion is fine to get things running - don’t try to optimise too early. Etc. Etc. YMMV.
That list iteration one is python specific. Or at least python could handle it better. Same for the mutable defaults one. I have no idea what python is doing under the hood. But I would have expected it to use a stack frame, but obviously not.
to be fair, couldn't you use: except Exception as e: match e: case _ if(isinstance(e, errortype): ... to make it even more explicit when determining what to do on an error? it's a bit hacky, but you could even group classes of exceptions together like: case _ if any([isinstance(e, errortype_1), ..., isisntance(e, errortype_n)]): print(f'error: {e}') (after working with it for like 5 min in REPL i have come to the conclusion this is more of a matter of preference and i am being nit-picky, but it was a fun thought exercise before work at least lol)
Default mutable that persist the function call seems more like a python design bug than a "bad idea" on the user's side. What's the intended use of that? If you want persistent data, you create a class.
I'm trying to work out how they got here. Every language I'm aware of creates a stack frame for each function. and everything that isn't returned just gets destroyed.
I've gotten some good use out of it when debugging. Ex. 1: Imagine a function that gets called thousands of times, that does something wrong when given 2 or 3 specific inputs in a row. The mutable default allows you to record those inputs, and inspect them when you have a breakpoint in an `except`. Especially handy when those values are large objects you can't just print. Ex. 2: You have a single function on a server that may get called multiple times asynchronously. You can't just put a breakpoint in there, as it'll break on every call, instead on the first call, you have `foo=[]` as a default parameter, check if it's empty, if so, `foo.append(1)` and break. Sure there's multiple other ways to do the same thing, but none that can be done as easily afaik.
@@fcolecumberri If "at the beginning" you mean outside the function/method, then yeah, sometimes it works, sometimes it doesn't without a `global` keyword which I don't fully understand and don't intend to learn when there's an easier alternative.
There's nothing complicated about this, it's just a result of python syntax. When you use default arguments, Python does not help you create an additional anonymous function that will be called every time the function is executed. Instead, it executes the expression and saves the resulting object in the function's properties. Because Python does not have a const decorator, it cannot prevent you from accessing it (and the reference semantics make it difficult to easily make a type hint containing const).
I do have good uses for `exception as e`, like when I have a complex library that calls user defined functions. Those functions can raise some exceptions and I cannot predict what they will be, and I need my system to fail gracefully oh just log and proceed to the next task
really cool videos but as a beginner i find them pretty hard to understrand some times, can you maybe explain things a bit more so that us just starting out can understand?
True, you could possibly add a retry limit, or hope that the user doesn't get this wrong more than a thousand times (although that could be wishful thinking).
Every time I see list.remove(value), it gives me the ick. Are you sure the list has only one element with that value? Are you sure you want to remove only the first occurrence? Etc. When I see things like that, I either use sets or remove them based on the index.
I've used 'except exception as e' a fair amount where I want to log errors but I want the program to continue even if there is an issue. I'm not convinced by this video that this is a mistake.
Uhm, didn't like that much the explanation to "why 'Idently' was printed" in the modifying a list section. It printed it because the channel variable had that value since channel was attributed prior to removing teh value held by channel from the list. I think making a point to notice as soon as possible that Arjan doesn't appear while Idently does is the most helpful here. Don't get me wrong, it is not wrong and the overall point is well made. The idea was to give contructive criticism, here. If I failed, I'm sorry!
Are you kidding me? I use nested if-statements and recursion all the time, i don't care if it makes the code bad and unreadable, the only thing that matters is that it works.
Not at all surprised that your take on recursion got a lot of objection. Issues with it are not limited to Python - but nor is its value for certain algorithms. De-recursifying can make the code unreadable (we all had to go through that exercise in uni, didn't we?) for little gain. Horse for courses, as we say.
Except Exception as e is perfectly fine 99% of the time. It's just part of this absurd typing disorder you suffer from. You can type the error response for goodness sake. People, don't waste time in typing nonsense, you have a job to do. Get on with it.
In the recursion question, both functions are doing different things. The recursion is doing a lot of recalculations, while the iterative function calculate each number once. If you wanna say recursion hurts performance, it's better to compare two functions that are well written.
I'm not arguing that removing while iterating causes an index skip. But the reason "indently" is getting printed is because it's the current value stored in the variable "channel" during that iteration. Than the index skip happens which results in bool. It would have been more appropriate to print the list itself on each iteration not just the indexed value.
Exactly. The variables "i" and "channel" have values stored in them for each iteration of the loop. The print statement on line 7 is referencing the values stored in them on line 3, it's not referencing the list. If line 7 was "print(i, channels[i])", then it would have printed "1 ArjanCodes" on the 2nd print instead.
The problem with removing items from the list while iterating is not that 'Indently' is printed but that 'ArjanCodes' is not (it is not processed).
In Java, they chosen to support the fail-fast pattern and an exception would be raise in the next iteration.
@@jeromemainaud Yes, its not even as if a continue can save it.
I suppose the solution would be to build a new list.
You can cache the result of the recursion to make it faster
I am not convinced handling 'exception as e' is wrong. This is more like pushing one opinion as a standard or "good practice".
Especially in fields where you need the program to keep runnnig and already log all errors as they occur
No it is actually VERY bad as a ctrl+C (exit) is handled by exceptions so this would not be ideal
@@IonicMC except Exception does not actually prevent Keyboard Interruption. If you were to use BaseException, then yes in that case I believe it would prevent Ctrl C in the terminal
Perhaps for simple try blocks, where the exception cases are clear, I agree with the video. As soon as you have a call to a library, you now need to either trust the docs or inspect the library code and also make assumptions about the exact version of the library being used. E.g. sockets, where the exception hierarchy has changed and some exceptions were deprecated.
It's not bad if all you're using is normal exceptions.
The real issue is when you start hitting the thousands of lines of code. A simple ValueError doesn't tell you much unless you know what raised it.
When you start working with custom exceptions. You'll see why except all is poor practice.
i think the recursion part is wrong. First, you mix the dynamic programming part(nothing to do with python) with the recursion overhead. Avoiding recursion is great if you really need that, but honestly most people using python will never do anything where it really matters. If yes, a single (at)cache will solve the problem with maintaining the readability. If the recursion overhead would matter, python would already be a bad answer, tbh.
The second example in recursion is an antipattern, a simple loop would suffice with the possibility to setup a retry number.
So the challenge is to produce an example of code that violates as many of these rules as possible whilst being readable and performant.
Bonus points for using descriptive names rather that type annotations.
"Bonus points for using descriptive names rather that type annotations" Okay, so this is a valid point, but it needs to be explained, because it will not be obvious to a lot of people. My take on this idea is that, for non-strongly typed languages, the code is intended to be type-flexible. Type annotations tend to defeat this objective.
For example, `def neg(x: int): return -x` might be supposed to return the negative of a number. But if I try to use `neg(1.2)` it fails because I've given it a float, and the type annotation demanded an integer. If I had written `def neg(x): return -x` my float example would have worked. And so would any other numeric type, probably, including future numeric types that I have yet to invent. Also, the `def` is a bit more concise. Maybe I should write `def neg(num): return -num` to hint that the parameter is expected to be numeric.
Having said all of this, I use TypeScript professionally instead of JavaScript and I find the TypeScript code to be immensely more maintainable in a commercial setting. So the ideal of dynamically typed programming I just illustrated may need to be taken with a pinch of salt. Possibly it is a case where the pros are outweighed by the cons.
@@therealdebater Is your example not more about poorly typing, rather than not having to type at all.
Your code would have worked with typing had you used float as the type instead. Whilst also providing linting, that highlights if your input is invalid.
@@SirZyPA Yes, in a way. I could have used a broader type, or just Any. I think my example was poor. Imagine writing a function to sort an array. Its input might be an array of Any. Or perhaps an array of Comparable. Not sure. Anyway, I think it can be seen that careful choice of type (annotations) is better than no types at all, but it is always a little wordier. I find, in practice, the stronger typing is worth it most of the time.
5:25 another thing about try/except blocks is minimizing the code in the try block, so the 1st print statement should be before:
try:
user_float = float(user_input)
except ValueError as err:
...
except TypeError as err:
...
else:
print ("etc...")
and note, the print statement is removed also, only the cast-to-float lives in try because only it is in question
"It's easy to debug if you know how error is produced."
KeyError: 0
That only tells that the key 0 doesn't exists in a dictionary, a very clear exception
the example of a good recursion usage is actually bad. This is not a case fir recursion, rather you can use while True, and break do loop on success. the recursion approach is bad because you fill the call stack, the stack can overflow
For list modification, I loop through my list and append the ones I want to keep to a new list. No deletion at all. Much easier to debug
Not very memory efficiënt, but certainly easier
Or you could use filter(), which is arguably even cleaner. This creates an iterable, so if you don't need to store the new list, only iterate over it, even saves the allocation of a new list in memory.
See my comment elsewhere: use a list comprehension or filter() with a lambda. This is THE use case of filter().
This is a crappy way to do it, like, you're linearly searching the list TWICE since remove does it, too. It's also usually not a problem if you're iterating backwards. Plenty other patterns when you need to edit a list in-place like this, it's only really a problem with iterators and simple for loops (and not only in Python).
Ironic that a channel named "Indently" is recommending against excessive indentation... haha😉
“We need to unindent” but I thought this was Indently!
The biggest issue I find with error handling is not the exception block, but handling the exception in the exception block and not passing the exception to the calling function. In an API this many times results in it returning a 200 response with no content, and the error being logged internally. So something interesting would be to cover how to do error handling in big multi-layer applications.
On example I have is in one program someone made a DB call which could return a not found exception, but then handled the exception to log out the error message and return an empty array. All good, user get's an empty response if not found, but if the DB connection fails (DB not found exception) it was also returning 200 with empty response instead of a 500 internal error.
Using guard clause will make you understand your logic better
Personally, the main reason I avoid using recursive algorithms in Python is the lack of tail call optimization, which unfortunately is very unlikely to ever be implemented. Some algorithms, particularly those operating on tree structures, are well-suited for recursion. However, Python is unfortunately not the best language for implementing them.
I am usually working with TypeScript. This is eyeopening that knowledge about one language don’t help to avoid gotchas in a other.
A good rule of thumb with guard clauses I use is " If statements that return can never have an else statement."
13:22 I think (I'm not sure) that a better term would be 'early exit' rather than 'guard clause' (which tends to be used in connection with multi-tasking and synchronisation). Early exit is often a neat way to avoid spaghetti if-then-else structures, but it can also be a liability. In a large block of code, an early exit can be easy to miss, and it can make it hard to spot certain kinds of bug. So use it with care; try to avoid it getting buried in a mass of code.
In what situation would an early exit be bad?
Youre failing early, you aren't short circuiting a success.
Unless you aren't setting some state, but then if its that type of code, you should be returning to a piece of code to make sure thats all set.
In C that would be an acceptable use of goto. I don't know what the python way is though.
@@benholroyd5221 Obviously not OP but a problem I caught with it is that if you want to print all conditions that aren't met, you can't do it with the guard clause because you're exiting early. With the nested if...else statements, you can print all of the missing conditions and return false at the end, otherwise return true.
@@Siggy or just setup a flag and return it at the end after checking all conditions
For the endless if-else checks, if you don’t need separate messages for every false boolean and just want to return a bool, just make a new variable (maybe call it returnBool) and set it to the first bool. Now, set it to itself and the second bool. Then, just keep doing that over and over until you reach the last bool. It should look like this:
def exampleFunction(bool1: bool, bool2: bool, bool3: bool, bool4: bool) -> bool:
returnBool: bool = bool1
returnBool = returnBool and bool2
returnBool = returnBool and bool3
returnBool = returnBool and bool4
return returnBool
However, you could also use the all() function. It should look like this:
def exampleFunction(bool1: bool, bool2: bool, bool3: bool, bool4: bool) -> bool:
return all([bool1, bool2, bool3, bool4])
Always wondered why does the intellisense warn me about setting a default value to a mutable object... Thanks for your explanation
General exceptions are not always bad. For instance if your program is calling to a JVM instance (i.e. Databricks), Python can’t access the underlying exception types. So you have to catch it in a general except clause, check if str(e) contains the Java error you want to check snd raise again otherwise. So there are totally valid use cases
Thank you. I really appreciate your python teaching!
Regarding the Fibonacci calculation, the readability goes down but what is your opinion on using the exact formula for the nth term?
an = [Phi^n - (phi)^n] / Sqrt[5].
Phi = (1 + Sqrt[5]) / 2
phi = (1 - Sqrt[5]) / 2
i did not know the golden ratio could be used to calculate Fibonacci 😮 this makes the function complexity O(1) instead of the O(n) function with for loop, great !
The issue is that this uses floating point numbers and so it doesn't actually work for large n (because of inaccuracy).
The absolute best way to do fibonacci on computers is using matrix representation the idea is that you have an vector f = [F(n), F(n+1)] and a matrix M such that M @ f = [F(n+1), F(n+2)].
Then you can use matrix exponentiation to get: ((M ** n) @ [0, 1])[0] = F(n)
You can do matrix multiplication rather quickly by repeatedly either squaring the matrix or squaring and multiplying it by M**1.
This way you get O(log(n)) runtime and perfect precision.
Also works for generalized fibonacci (like what if it's T(n) = T(n-1) + 52T(n-2) - 13T(n-3) instead? Well, you use the same method, just pick a corresponding matrix.)
Also the version in the video can be fixed very easily: @functools.cached
For recursion: if recurrsion is still the simplest way to do something and you have enough memory you can cache the results to speed up the process with the decorators lru_cache or simply cache from functools.
Aside from the if..else section as there are use cases for the rest of the examples, what I learned from this is "Thing to not do unless you absolutely know what you're doing."
such a cool concept of a video thank you very much
I did the first mistake. I noticed luckily that that the printed length did not match the number of printed elements from that list. After thunking myself, I asked ChatGPT and it found the bug
20:10 you should return the do_math() because if you would have something after the except block it will run twice because when its done with the new do_math function it will continue with the original and it will run everything that is outside the do math twice
that's what try/except/else is for. do_math() should be in an else block.
@@DrDeuteron wdym
@@HardlineCheatsImagine try being an 'if error' statement. Once the user inputs the correct code, since no error occurred, the reader will read out the else statement, where the 'return' function will be executed.
@@megareunion4407 basically you just need to return it even in the except it will work
You can always reverse a list to safely modify its contents in a for loop.
13:22 when i heard about that, it was a heaven!! Now I cant even think about nesting!
19:39 i would use loop here too, like, return 0 and return 1 in cases if it completed without or with error, and loop accordingly
isnt the good recursion example potentially bad cuz if mate is too drunk and keeps hitting those exceptions wed hit a stack overflow eventually
If you reach a recursion depth of ~1000 you get a RecursionError. Ol’ mate would need to be REALLY drunk and patient to get that error. To get a stack overflow you would need to set sys.setrecursionlimit(n) to an extremely large number and not run out of heap memory before then.
Damn, that "has money, time and cat but no girlfriend" felt kinda personal 🤣
19:58 i don't think that would be my GOTO strategy...
also isn't typing deprecated?
Who deprecated the guard clause?
12:10 instead of just deleting and rewriting, most editors can flip the if/else, resulting in the better code you show.
25:10 it would be better to use a guard for the None value instead of an if/else with intentions - just this line instead of an if/else: ' target_list = target_list or []'
03:00 is it not this the way to go: channel_filtered = [channel for channel in channels if channel != 'Idently']?
20:12 That last "bad idea" can sometimes also be used as Python's version of static variables.
Very informative
The first example shows why you should be working with functional languages.
Teasing aside, functional languages can inform better practices. I assume Python has a filter method. That would be perfect for this code.
Pure functional languages are lame and gay, but elements from them in multiparadigm languages are welcome. That first example would be most easily solved with a generator or a list comprehension, calling filter map and reduce are a bit cumbersome and ugly.
These are not bad ideas that are specific to python, but just generally had programming practices, illustrated in Python. As others have said, there are also often exceptions to these rules. E.g. it’s ok to use indented ifs for two levels, if the contained blocks don’t get too long. Catch all exceptions can be useful at the top level to exit the application elegantly. Recursion is fine to get things running - don’t try to optimise too early. Etc. Etc. YMMV.
That list iteration one is python specific. Or at least python could handle it better.
Same for the mutable defaults one. I have no idea what python is doing under the hood. But I would have expected it to use a stack frame, but obviously not.
to be fair, couldn't you use:
except Exception as e:
match e:
case _ if(isinstance(e, errortype):
...
to make it even more explicit when determining what to do on an error?
it's a bit hacky, but you could even group classes of exceptions together like:
case _ if any([isinstance(e, errortype_1), ..., isisntance(e, errortype_n)]):
print(f'error: {e}')
(after working with it for like 5 min in REPL i have come to the conclusion this is more of a matter of preference and i am being nit-picky, but it was a fun thought exercise before work at least lol)
Default mutable that persist the function call seems more like a python design bug than a "bad idea" on the user's side. What's the intended use of that? If you want persistent data, you create a class.
I'm trying to work out how they got here.
Every language I'm aware of creates a stack frame for each function. and everything that isn't returned just gets destroyed.
I've gotten some good use out of it when debugging.
Ex. 1: Imagine a function that gets called thousands of times, that does something wrong when given 2 or 3 specific inputs in a row. The mutable default allows you to record those inputs, and inspect them when you have a breakpoint in an `except`. Especially handy when those values are large objects you can't just print.
Ex. 2: You have a single function on a server that may get called multiple times asynchronously. You can't just put a breakpoint in there, as it'll break on every call, instead on the first call, you have `foo=[]` as a default parameter, check if it's empty, if so, `foo.append(1)` and break.
Sure there's multiple other ways to do the same thing, but none that can be done as easily afaik.
@@ultru3525 I can't get a reason why a singleton variable instanciated at the beginning can't do the same exactly as fast, but ok.
@@fcolecumberri If "at the beginning" you mean outside the function/method, then yeah, sometimes it works, sometimes it doesn't without a `global` keyword which I don't fully understand and don't intend to learn when there's an easier alternative.
There's nothing complicated about this, it's just a result of python syntax. When you use default arguments, Python does not help you create an additional anonymous function that will be called every time the function is executed. Instead, it executes the expression and saves the resulting object in the function's properties. Because Python does not have a const decorator, it cannot prevent you from accessing it (and the reference semantics make it difficult to easily make a type hint containing const).
I do have good uses for `exception as e`, like when I have a complex library that calls user defined functions. Those functions can raise some exceptions and I cannot predict what they will be, and I need my system to fail gracefully oh just log and proceed to the next task
If not gurd cases are a very elegent solution.
Thank you 😊
No cat... No girlfriend... No time... No money...
what IDE is that?
really cool videos but as a beginner i find them pretty hard to understrand some times, can you maybe explain things a bit more so that us just starting out can understand?
18:18 RecursionError if user wrong too many times
True, you could possibly add a retry limit, or hope that the user doesn't get this wrong more than a thousand times (although that could be wishful thinking).
Every time I see list.remove(value), it gives me the ick. Are you sure the list has only one element with that value? Are you sure you want to remove only the first occurrence? Etc.
When I see things like that, I either use sets or remove them based on the index.
19:39 that still isn't ideal, as we can reach the recursion limit and the program will fail
many people do not know how to use async/await properly, and it leads to make asynchronize code to become synchronized again.
The first one is still showing Indently because the variable channel is already set.
I've used 'except exception as e' a fair amount where I want to log errors but I want the program to continue even if there is an issue. I'm not convinced by this video that this is a mistake.
Uhm, didn't like that much the explanation to "why 'Idently' was printed" in the modifying a list section. It printed it because the channel variable had that value since channel was attributed prior to removing teh value held by channel from the list. I think making a point to notice as soon as possible that Arjan doesn't appear while Idently does is the most helpful here.
Don't get me wrong, it is not wrong and the overall point is well made. The idea was to give contructive criticism, here. If I failed, I'm sorry!
Handling “exception as e” is fine as long my as you re raise the error after
Are you kidding me? I use nested if-statements and recursion all the time, i don't care if it makes the code bad and unreadable, the only thing that matters is that it works.
A working product is better than no product in many cases. Happy you have it figured it out :)
Your recursive example is O(2^n).
Not at all surprised that your take on recursion got a lot of objection. Issues with it are not limited to Python - but nor is its value for certain algorithms. De-recursifying can make the code unreadable (we all had to go through that exercise in uni, didn't we?) for little gain. Horse for courses, as we say.
I appreciate when people object and present their own ideas and opinions, it allows us all to learn new ways of seeing things.
i have not cat, girlfriend or money ☹️…at least i have my jigsaw puzzle collection
Except Exception as e is perfectly fine 99% of the time. It's just part of this absurd typing disorder you suffer from. You can type the error response for goodness sake.
People, don't waste time in typing nonsense, you have a job to do. Get on with it.
But those are not "ideas", they are bad coding practices and should be avoided in any programming language
hi