That last section was very hard to follow with the switching between windows and the high number of things being printed. Comments next to the lines with the results would have helped a lot, or maybe using a jupyter notebook could have worked as well.
It could have been better shown if he showed that dataclass objects don't have class attributes. It's definitely confusing when you don't have the attributes defined on different scope/indentation levels.
Why not use f-strings to show the evaluated expression as well as the result to save flipping between screens? E.g. >>> print(f"{(1 == True) = }") (1 == True) = True
In C, false is zero and everything else is true. And that's exactly the way while loop conditions work in Python as well. You can put a number and when it's not 0, it's true and the loop continues.
My other favorite easter egg in Python - in any REPL, type "from this import that" - it prints the zen of Python poem. But if you go look in the module, the string is encrypted with a substitution cipher! Side note, if you use ctrl+alt+up/down, you can do multiline cursors in VS Code (similar to ctrl + click). So if you have a bunch of statements that line up in a columnar way you can edit them all at the same time. PyCharm has similar functionality but you have to press ctrl 2x in quick succession and hold it down on the second press, then use the arrow keys, which I like a lot less.
Hi Arjan, again a very nice video, so thanks for that! With respect to the function attributes: yes at first it may seem strange that such an attribute definition on a function is possible. On the other hand, if you look at the output of dir(functionName) you can see automatically defined attributes, all of them being dunder-attributes (starting and ending with double underscores). Example is that a function documented with a docstring has that documentation stored in the attribute __doc__. Knowing that dunder-attributes are usually defined by Python itself, it makes sense that non-dunder attributes are available to the programmer. I agree with you that it is not common use to do. As mentioned by another reader, using decorators on functions is a more widely used practice to obtain functionality that could otherwise be reached using function attributes.
What version of python do I need to use, or what should I import, for your function signature in Ex#2 to work? def append_to_list[T]... 3.11.4 doesn't like the "[T]"
9 has nothing to do with Python, it's just how OS works. The output is line-buffered by default, so OS won't pass it on to the terminal before it contains (which you purposefully suppressed) or the program exits. OS provides a way to force buffer flush (which is what `flush=True` does) or to switch to other buffer strategy (eg. by buffer size).
Function name starts with double underscore inside a class can cause big trouble for child class. It kind of makes it an exclusive method for the super class.
Great stuff! I did not hear about NotImpemented (and, obviously, antigravity 😂) before this video. I only think that while most of the other features are caused by the peculiarities of the language implementation, item 6 is not truly weird, as it is what anyone might expect from string immutability and variable reassignment
Not sure if this classifies as weirdness, if anything it is perhaps rather unexpected but in a good way: In python almost anything is fair game. Asigning attributes that were not defined on the class? Sure! Swap out methods using moneky-patching!? Go ahead! Hooking into the import system to allow loading modules directly from pip? Why not. But if you try to return a negative number from `__len__` the interpreter will scream at you =D
It would be beneficial if you used the Jupyter notebook for this video. Rather than switching between outputs you could show them in line between code.
Since `some_attribute` is an integer, doing `a.some_attribute is b.some_attribute` will always return True if the values are the same, even if they're not references to each other.
At 15:00 the for/else loop is discussed. Raymond Hettinger gave a talk "Transforming Code into Beautiful, Idiomatic Python" at 18:00 he suggested that the 'else' should have been called 'nobreak'. He also mentioned that Python has an inbuild capability to find (so long as it's an interable). Something like: item = next((x for x in iterable if condition(x)), None) if item: # Found the item, do something
About "weirdness" of class and instance variable: it's confusing only when you don't know the Python language. It's called MRO and well described in the official documentation.
Function attributes are cursed but can also be pretty awesome. Being able to count invocations, automatically log calls, etc. You can also usually accomplish the same with decorators though
I agree. I think there was the possibility to implement memoization (caching) maybe before any decorator (like lru_cache) existed in the std library for it. And you can't find any better place the cache dictionary than into the function itself as an attribute.
Essentially, data classes use class-level descriptor to store default values for instance attributes, class attributes are only defined by leaving variable untyped, and this can be seen with a simple example I’m happy to share if comments would allow. 25:30
And to add to this discussion, two things I found really weird about Python. 1. Circular imports are not allowed but can be bypassed by importing only the modules. 2. Also about imports. Python makes it really hard when you have a file in a folder in which you’d like import another file from another folder which is of the same level in your repo tree structure. Usually it’s no big deal but when you wanna put all your tests in a folder and import functions and classes from other folders it’s a nightmare. To be honest I’m still quite confused what’s the best practice for this. For the moment I’m using pip install -e . And add a setup file. But I’m not sure this is the best way….
I find passing mutable parameters into a function and then modifying them inside the function and having that change appear outside the function to be something that trips me up occasionally. On the otherhand else clauses on for loops and while loops make total sense!
Arjan, do you think you could tell viewers at the beginning of a video which version of Python you were using? It's frustrating to type in your examples only to have them crash because of version issues.
I will normally use the latest version of Python in my videos. I do put the Python version in the pyproject file for each example in the Git repository (see link in the description). So if you work from that, you shouldn’t have that problem anymore.
The delayed print is not Python-specific. That buffering behaviour is standard for all programming languages I know of. Because for most situations, the overhead of printing is huge, and the program only wants to incur this overhead when it is useful to the user. I.e. when a sentence is completed.
The class attribute behavior is great. An instance look up starts at "self", and then checks type(self). It makes class attributes the place to set instance defaults. When I see: self.foo = foo and "foo" is NOT a dunder init argument, I lose it. Example: a car class: class Car: crashes = 0 def crash(self): self.crashes += 1 then: >>>car = Car() >>>car.crashes 0 is the class variable. But after: >>>car.crash() >>>car.crashes 1 is the instance attribute. Python is a language where, as with timer.counter, and the main() at 21:40 , you should never modify instance attributes outside of instance methods, Major code smell.
I recently discovered behavior that surprised me. If you create a nested Tuple like ('a', ('b')) Python strips the single element tuple which leaves ('a', 'b') which broke my iteration code so I had to replace it with a list, which is NOT removed.
A tuple is not defined by parentheses in Python, it's just defined by commas, so `a = (1, 2)` is the same as `a = 1, 2`. To get a tuple with just one element, you have to write `a = 1,` or `a = (1,)'.
Before I followed this channel I never actually used dataclasses so it wasn’t such a big deal to me. And to make sure I don’t get confused I always use all caps to define class attributes. But then I found this channel and I soon realized how convenient data classes. Now I’m starting to get confused….😅
I have an objection to part 13! While everything you said was true, there's a bug in your demonstration for why it's true that goes back to an earlier part. Since small integers are always cached, `a.some_attribute is b.some_attribute` only checks their value (as long as they're both small integers). Should've assigned a string to demonstrate they're exactly the same object.
This is nice, speaking as someone who does far more JavaScript than Python, it's nice to see Python taking some of the abuse that's normally thrown at JavaScript. ;)
With that for-else (or even while-else) part with break I normally just put a comment with the else like this: "else: # no break". I believe it was also mentioned at one of the PyCon videos by someone that it would have been better if the called the else there nobreak. Adding this comment normally makes it much clearer what is happening. I also think of this as an actual if-else statement. So if you get to the if {condition} part and the condition is False you go to else otherwise you go inside the if block. Now let's say inside the if block you return / break your else part would never run. With the for-else statement you can think of the "for item in items" part as the if condition, but the condition is checked with each iteration. If it is false here (like the iterable/iterator is empty or the loop is done) it goes to else instead, because that condition suddenly turned into a false statement.
I wonder if these are quirks or just built-in in Python. It will be weird if functions in Python are reference type. I think C# or Java also have NotImplementedError equivalent. Actually, in most languages, true and false are integer or 1 and 0 they are not subclasses. Python putting "else" everywhere is probably a meme. What's next comprehension with else ? For the last one I think functions having attributes explains this quirks.
The fact that ___iadd___ method of lists just extend the list instead of creating a new list is a very weird design choice imo. Like why would u do that?
Check IEEE-754 rounding rules, it's not specific to Python. It's the same reason why 0.1 + 0.2 = 0.30000000000000004 in Python like in any other programming language that follows IEEE-754.
Examples with instance/class attributes using integer and is operator is not verify compelling imo the fact that a.some_attribute is X.some_attribute return True does not prove your point as 2 might be store in instance __dict__ and in class __dict__ but as integers are cached it will be True anyway
Recently on medium i found this, which I couldn't even think of. from somewhere import get_dog from elsewhere import create_default_dog dog = get_dog() # 5% chance of returning None instead of a Dog object (dog or create_default_dog()).bark()
for me the following python behavior is strange (however i realize the reason); x = 1 y = 2 def one_plus_two(): return x + y print(one_plus_two()) # output is 3
If I were you, I would learn and use Jupiter notebooks for my videos or found a clever way to video edit the console output in the IDE window. Right now, your presentation skill is below average...
Are you trying to manipulate him into using your favourite tool?😅 It definitely comes across that way. I don't even use vscode and I don't have trouble following along when he switches screens.
These actually make sense when you know why it is the way it is. And Python has good reasons for pretty much all of these 'quirks'. But if you look at Javascript then there are TONNS of quirks that are not justified.
I hope you guys are not superstitious 😁.
It actually is Tuesday, the 13th for us in Spain (idk why)
That last section was very hard to follow with the switching between windows and the high number of things being printed. Comments next to the lines with the results would have helped a lot, or maybe using a jupyter notebook could have worked as well.
It could have been better shown if he showed that dataclass objects don't have class attributes. It's definitely confusing when you don't have the attributes defined on different scope/indentation levels.
I never paid attention to the print flush behavior before, it makes sense now that I think about it. Thanks for pointing that out!
Why not use f-strings to show the evaluated expression as well as the result to save flipping between screens?
E.g.
>>> print(f"{(1 == True) = }")
(1 == True) = True
wow didn't know that was possible! thxx
Not just for that example. But for every single run-on print()s
Beginners may not know the f-magic
In C, false is zero and everything else is true. And that's exactly the way while loop conditions work in Python as well. You can put a number and when it's not 0, it's true and the loop continues.
My other favorite easter egg in Python - in any REPL, type "from this import that" - it prints the zen of Python poem. But if you go look in the module, the string is encrypted with a substitution cipher!
Side note, if you use ctrl+alt+up/down, you can do multiline cursors in VS Code (similar to ctrl + click). So if you have a bunch of statements that line up in a columnar way you can edit them all at the same time. PyCharm has similar functionality but you have to press ctrl 2x in quick succession and hold it down on the second press, then use the arrow keys, which I like a lot less.
Many thanks for your very instructive videos.
Wanted to ask you what font you are using in the video.
8:47 data[0] = data[0] + [3] does NOT give the same result as data[0] += [3]
Yeah, which makes this case even weirder.
He was trying to say that, by contrasting "in-place addition" with "assignment." But yeah, it would have been better to demonstrate.
Hi Arjan, again a very nice video, so thanks for that!
With respect to the function attributes: yes at first it may seem strange that such an attribute definition on a function is possible. On the other hand, if you look at the output of dir(functionName) you can see automatically defined attributes, all of them being dunder-attributes (starting and ending with double underscores).
Example is that a function documented with a docstring has that documentation stored in the attribute __doc__.
Knowing that dunder-attributes are usually defined by Python itself, it makes sense that non-dunder attributes are available to the programmer. I agree with you that it is not common use to do.
As mentioned by another reader, using decorators on functions is a more widely used practice to obtain functionality that could otherwise be reached using function attributes.
What version of python do I need to use, or what should I import, for your function signature in Ex#2 to work?
def append_to_list[T]... 3.11.4 doesn't like the "[T]"
9 has nothing to do with Python, it's just how OS works. The output is line-buffered by default, so OS won't pass it on to the terminal before it contains
(which you purposefully suppressed) or the program exits.
OS provides a way to force buffer flush (which is what `flush=True` does) or to switch to other buffer strategy (eg. by buffer size).
Have you considered using the multi cursor to avoid wasting video time just changing repetitive things line by line
Love your videos!
Function name starts with double underscore inside a class can cause big trouble for child class. It kind of makes it an exclusive method for the super class.
Great stuff! I did not hear about NotImpemented (and, obviously, antigravity 😂) before this video. I only think that while most of the other features are caused by the peculiarities of the language implementation, item 6 is not truly weird, as it is what anyone might expect from string immutability and variable reassignment
Not sure if this classifies as weirdness, if anything it is perhaps rather unexpected but in a good way: In python almost anything is fair game. Asigning attributes that were not defined on the class? Sure! Swap out methods using moneky-patching!? Go ahead! Hooking into the import system to allow loading modules directly from pip? Why not. But if you try to return a negative number from `__len__` the interpreter will scream at you =D
It would be beneficial if you used the Jupyter notebook for this video.
Rather than switching between outputs you could show them in line between code.
Since `some_attribute` is an integer, doing `a.some_attribute is b.some_attribute` will always return True if the values are the same, even if they're not references to each other.
At 15:00 the for/else loop is discussed. Raymond Hettinger gave a talk "Transforming Code into Beautiful, Idiomatic Python" at 18:00 he suggested that the 'else' should have been called 'nobreak'. He also mentioned that Python has an inbuild capability to find (so long as it's an interable). Something like:
item = next((x for x in iterable if condition(x)), None)
if item:
# Found the item, do something
I like 'for, else'. i just always comment the 'else' with # nobreak
About "weirdness" of class and instance variable: it's confusing only when you don't know the Python language. It's called MRO and well described in the official documentation.
Function attributes are cursed but can also be pretty awesome. Being able to count invocations, automatically log calls, etc. You can also usually accomplish the same with decorators though
It's used in the stdlib, too. A function cached with a functools caching decorator get the .cache_clear() method.
I agree. I think there was the possibility to implement memoization (caching) maybe before any decorator (like lru_cache) existed in the std library for it. And you can't find any better place the cache dictionary than into the function itself as an attribute.
I would also add those:
1) type(type) is type
2) issubclass(type, object) and isinstance(object, type)
3) "a"[0][0][0][0][0] == "a"
Essentially, data classes use class-level descriptor to store default values for instance attributes, class attributes are only defined by leaving variable untyped, and this can be seen with a simple example I’m happy to share if comments would allow. 25:30
And to add to this discussion, two things I found really weird about Python. 1. Circular imports are not allowed but can be bypassed by importing only the modules. 2. Also about imports. Python makes it really hard when you have a file in a folder in which you’d like import another file from another folder which is of the same level in your repo tree structure. Usually it’s no big deal but when you wanna put all your tests in a folder and import functions and classes from other folders it’s a nightmare. To be honest I’m still quite confused what’s the best practice for this. For the moment I’m using pip install -e . And add a setup file. But I’m not sure this is the best way….
I find passing mutable parameters into a function and then modifying them inside the function and having that change appear outside the function to be something that trips me up occasionally.
On the otherhand else clauses on for loops and while loops make total sense!
Arjan, do you think you could tell viewers at the beginning of a video which version of Python you were using? It's frustrating to type in your examples only to have them crash because of version issues.
I will normally use the latest version of Python in my videos. I do put the Python version in the pyproject file for each example in the Git repository (see link in the description). So if you work from that, you shouldn’t have that problem anymore.
3.12 shows up in the outputs in some of the examples
The delayed print is not Python-specific. That buffering behaviour is standard for all programming languages I know of. Because for most situations, the overhead of printing is huge, and the program only wants to incur this overhead when it is useful to the user. I.e. when a sentence is completed.
The class attribute behavior is great. An instance look up starts at "self", and then checks type(self). It makes class attributes the place to set instance defaults. When I see:
self.foo = foo and "foo" is NOT a dunder init argument, I lose it.
Example: a car class:
class Car:
crashes = 0
def crash(self):
self.crashes += 1
then:
>>>car = Car()
>>>car.crashes
0
is the class variable. But after:
>>>car.crash()
>>>car.crashes
1
is the instance attribute.
Python is a language where, as with timer.counter, and the main() at 21:40 , you should never modify instance attributes outside of instance methods, Major code smell.
I recently discovered behavior that surprised me. If you create a nested Tuple like ('a', ('b')) Python strips the single element tuple which leaves ('a', 'b') which broke my iteration code so I had to replace it with a list, which is NOT removed.
A tuple is not defined by parentheses in Python, it's just defined by commas, so `a = (1, 2)` is the same as `a = 1, 2`. To get a tuple with just one element, you have to write `a = 1,` or `a = (1,)'.
Before I followed this channel I never actually used dataclasses so it wasn’t such a big deal to me. And to make sure I don’t get confused I always use all caps to define class attributes. But then I found this channel and I soon realized how convenient data classes. Now I’m starting to get confused….😅
Any idea why we can't mess around with Python built-in function attributes like in point number 12?
The one with immutable default values of functions once cost me a few good hours to figure out.
I have an objection to part 13!
While everything you said was true, there's a bug in your demonstration for why it's true that goes back to an earlier part. Since small integers are always cached, `a.some_attribute is b.some_attribute` only checks their value (as long as they're both small integers). Should've assigned a string to demonstrate they're exactly the same object.
Even with strings, the compiler will optimize them to be the same object. You'd have to use something else
You’re right, thanks for pointing that out! There’s still the confusing mix-up between class vs instance variables though.
This is nice, speaking as someone who does far more JavaScript than Python, it's nice to see Python taking some of the abuse that's normally thrown at JavaScript. ;)
I have a good example to explain data classes, but my comments keep getting removed. How is it best to share this?
With that for-else (or even while-else) part with break I normally just put a comment with the else like this: "else: # no break". I believe it was also mentioned at one of the PyCon videos by someone that it would have been better if the called the else there nobreak. Adding this comment normally makes it much clearer what is happening.
I also think of this as an actual if-else statement. So if you get to the if {condition} part and the condition is False you go to else otherwise you go inside the if block. Now let's say inside the if block you return / break your else part would never run. With the for-else statement you can think of the "for item in items" part as the if condition, but the condition is checked with each iteration. If it is false here (like the iterable/iterator is empty or the loop is done) it goes to else instead, because that condition suddenly turned into a false statement.
Switching windows is confusing. Why not use side-by-side windows ?
Probably for viewers on mobile. Using a single window allows him to keep more content on screen with a larger font size.
I remember using a function attribute as a kind of static variable, but I cant' remember the context.
wait, isn't the
>>>a.some_attr is X.some_attr
rendered pointless by the weirdness No 1: integer caching?
Love this kind of videos
Glad you like them!
Are the intended advantages of integer caching measurable?
It prevents new int objects from being created and destroyed for most for loops over a typical value ranges.
True + True = 2 is some Dr Seuss stuff and now I want a Dr Seuss style silly rhyming book about coding
I wonder if these are quirks or just built-in in Python.
It will be weird if functions in Python are reference type.
I think C# or Java also have NotImplementedError equivalent.
Actually, in most languages, true and false are integer or 1 and 0 they are not subclasses.
Python putting "else" everywhere is probably a meme.
What's next comprehension with else ?
For the last one I think functions having attributes explains this quirks.
have you ever imported 'this'?
The second one has killed me before. I really wish it provided a new list in that scenario
Integer Caching: I don't think you can call it 'caching' when it's simply pointing both variables to the same object.
The fact that ___iadd___ method of lists just extend the list instead of creating a new list is a very weird design choice imo. Like why would u do that?
The most frustating thing i found were the errors
my last weird finding is the round(). that function do rounding, but in some surprising way.
Check IEEE-754 rounding rules, it's not specific to Python. It's the same reason why 0.1 + 0.2 = 0.30000000000000004 in Python like in any other programming language that follows IEEE-754.
@@lucasmultigaming4243 huh, I didn't realize the IEEE745 is about rounding too. thank you to pointing that out.
Nice to learn For-Else 👍
So ... sometimes Python can bite you in the asp? 🤣
(Very good video, by the way, and be assured, people, that all programming languages have quirks.)
Loved the BOUNS
Examples with instance/class attributes using integer and is operator is not verify compelling imo the fact that a.some_attribute is X.some_attribute return True does not prove your point as 2 might be store in instance __dict__ and in class __dict__ but as integers are cached it will be True anyway
Why in 8 you would ever use the "else:"? If you remove it you would get exactly the same functionality, no?
It would not because the `print("Does not exist")` statement would be called even if an element was found.
Scope issue when initializing new variable in try except clause.
Recently on medium i found this, which I couldn't even think of.
from somewhere import get_dog
from elsewhere import create_default_dog
dog = get_dog() # 5% chance of returning None instead of a Dog object
(dog or create_default_dog()).bark()
Just watch any of James Powell talks on YT and than you know python its weird. 😂 BR from 🇩🇰
6 - (True).denominator
Is Not Second
dark magic
for me the following python behavior is strange (however i realize the reason);
x = 1
y = 2
def one_plus_two():
return x + y
print(one_plus_two()) # output is 3
If I were you, I would learn and use Jupiter notebooks for my videos or found a clever way to video edit the console output in the IDE window. Right now, your presentation skill is below average...
Are you trying to manipulate him into using your favourite tool?😅 It definitely comes across that way.
I don't even use vscode and I don't have trouble following along when he switches screens.
8:46
adding two lists with + sign doesn't mutate the original lists. are you sure the syntactic sugar expands to that syntax?
>>> a1 += [3] # Uses __iadd__, modifies a1 in-place
>>> b1 = b1 + [3] # Uses __add__, creates new list, assigns it to b1
None of them are weird if you understand and accept working with Python as it is....
These actually make sense when you know why it is the way it is. And Python has good reasons for pretty much all of these 'quirks'.
But if you look at Javascript then there are TONNS of quirks that are not justified.
You could say that about any language. Weird is subjective. Like he says, it’s weird to a newbie maybe.