mirror of
https://github.com/jbranchaud/til
synced 2026-07-06 17:20:33 +00:00
Compare commits
3 Commits
2cd465bb08
...
0cb5890fc0
| Author | SHA1 | Date | |
|---|---|---|---|
| 0cb5890fc0 | |||
| 7de0e70d78 | |||
| 36934aa56f |
@@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/).
|
|||||||
|
|
||||||
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
|
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
|
||||||
|
|
||||||
_1775 TILs and counting..._
|
_1778 TILs and counting..._
|
||||||
|
|
||||||
See some of the other learning resources I work on:
|
See some of the other learning resources I work on:
|
||||||
|
|
||||||
@@ -1048,6 +1048,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
|||||||
|
|
||||||
- [Access Instance Variables](python/access-instance-variables.md)
|
- [Access Instance Variables](python/access-instance-variables.md)
|
||||||
- [Access Most Recent Return Value In REPL](python/access-most-recent-return-value-in-repl.md)
|
- [Access Most Recent Return Value In REPL](python/access-most-recent-return-value-in-repl.md)
|
||||||
|
- [Access Variables Outside Loop Scope](python/access-variables-outside-loop-scope.md)
|
||||||
- [Avoid Modification With Frozen Dataclass](python/avoid-modification-with-frozen-dataclass.md)
|
- [Avoid Modification With Frozen Dataclass](python/avoid-modification-with-frozen-dataclass.md)
|
||||||
- [Break Debugger On First Line Of Program](python/break-debugger-on-first-line-of-program.md)
|
- [Break Debugger On First Line Of Program](python/break-debugger-on-first-line-of-program.md)
|
||||||
- [Check If Package Is Installed With Pip](python/check-if-package-is-installed-with-pip.md)
|
- [Check If Package Is Installed With Pip](python/check-if-package-is-installed-with-pip.md)
|
||||||
@@ -1063,6 +1064,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
|||||||
- [Keep A Tally With collections.Counter](python/keep-a-tally-with-collections-counter.md)
|
- [Keep A Tally With collections.Counter](python/keep-a-tally-with-collections-counter.md)
|
||||||
- [Load A File Into The Python REPL](python/load-a-file-into-the-python-repl.md)
|
- [Load A File Into The Python REPL](python/load-a-file-into-the-python-repl.md)
|
||||||
- [Look Inside Pytest tmp_path](python/look-inside-pytest-tmp-path.md)
|
- [Look Inside Pytest tmp_path](python/look-inside-pytest-tmp-path.md)
|
||||||
|
- [Make Dataclass Sortable By Specific Field](python/make-dataclass-sortable-by-specific-field.md)
|
||||||
- [Override The Boolean Context Of A Class](python/override-the-boolean-context-of-a-class.md)
|
- [Override The Boolean Context Of A Class](python/override-the-boolean-context-of-a-class.md)
|
||||||
- [Parse Relative Time To datetime Object](python/parse-relative-time-to-datetime-object.md)
|
- [Parse Relative Time To datetime Object](python/parse-relative-time-to-datetime-object.md)
|
||||||
- [Skip Specific Pytest Test Cases](python/skip-specific-pytest-test-cases.md)
|
- [Skip Specific Pytest Test Cases](python/skip-specific-pytest-test-cases.md)
|
||||||
@@ -1419,6 +1421,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
|||||||
- [Defaulting To Frozen String Literals](ruby/defaulting-to-frozen-string-literals.md)
|
- [Defaulting To Frozen String Literals](ruby/defaulting-to-frozen-string-literals.md)
|
||||||
- [Define A Custom RSpec Matcher](ruby/define-a-custom-rspec-matcher.md)
|
- [Define A Custom RSpec Matcher](ruby/define-a-custom-rspec-matcher.md)
|
||||||
- [Define A Method On A Struct](ruby/define-a-method-on-a-struct.md)
|
- [Define A Method On A Struct](ruby/define-a-method-on-a-struct.md)
|
||||||
|
- [Define A Set Of Class Methods](ruby/define-a-set-of-class-methods.md)
|
||||||
- [Define Multiline Strings With Heredocs](ruby/define-multiline-strings-with-heredocs.md)
|
- [Define Multiline Strings With Heredocs](ruby/define-multiline-strings-with-heredocs.md)
|
||||||
- [Destructure The First Item From An Array](ruby/destructure-the-first-item-from-an-array.md)
|
- [Destructure The First Item From An Array](ruby/destructure-the-first-item-from-an-array.md)
|
||||||
- [Destructuring Arrays In Blocks](ruby/destructuring-arrays-in-blocks.md)
|
- [Destructuring Arrays In Blocks](ruby/destructuring-arrays-in-blocks.md)
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Access Variables Outside Loop Scope
|
||||||
|
|
||||||
|
Here is a function that loops over a list to find the first occurrence of a
|
||||||
|
falsy value.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def find_false(self):
|
||||||
|
for item in self.items:
|
||||||
|
item_type = type(item)
|
||||||
|
print(f"Current item: {item} ({item_type})")
|
||||||
|
if not item:
|
||||||
|
break
|
||||||
|
|
||||||
|
print(f"First false item: {item} ({item_type})")
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice how at the end of the function, outside of the loop, I am able to access
|
||||||
|
both `item` (defined in the loop definition) and `item_type` (defined within the
|
||||||
|
loop's body).
|
||||||
|
|
||||||
|
Both of these variables are defined, by the loop, in _function scope_ and are
|
||||||
|
accessible anywhere in the function after they have been defined.
|
||||||
|
|
||||||
|
The title of this TIL is a bit of a misnomer because Python doesn't have the
|
||||||
|
concept of a _loop scope_. There are two levels of scope in Python --
|
||||||
|
module/global scope and function scope.
|
||||||
|
|
||||||
|
I spend most of my time writing Ruby which also has _block scope_, so Python's
|
||||||
|
simplified two-level scoping took me by surprise.
|
||||||
|
|
||||||
|
Though the code sample above is contrived, this function scope assignment can be
|
||||||
|
taken advantage of with loop definitions in scenarios where you want to know
|
||||||
|
what the last `item` defined was before the loop terminated.
|
||||||
|
|
||||||
|
```python
|
||||||
|
for submission in submissions:
|
||||||
|
if passes(submission, criteria):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError("No submissions that meet given criteria")
|
||||||
|
|
||||||
|
print(f"Submit first passing submission: {submission.id}")
|
||||||
|
submit(submission)
|
||||||
|
```
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Make Dataclass Sortable By Specific Field
|
||||||
|
|
||||||
|
One way to sort a list of some `dataclass` is to define the `key` parameter when
|
||||||
|
calling `sort` or `sorted` like I discussed in [Sort a List of Dataclass
|
||||||
|
Instances](sort-a-list-of-dataclass-instances.md):
|
||||||
|
|
||||||
|
```python
|
||||||
|
for date in sessions_grouped_by_day.keys():
|
||||||
|
sessions_grouped_by_day[date].sort(
|
||||||
|
key=lambda session: session.start_time.time()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
But then that lambda for `key` needs to be defined everywhere you sort.
|
||||||
|
|
||||||
|
If the dataclass has a single, specific field that acts as a natural proxy for
|
||||||
|
sort order, then you can define that in the `dataclass` implementation with the
|
||||||
|
`__lt__` method.
|
||||||
|
|
||||||
|
As long as a class defines the _less than_ dunder method, it will be sortable.
|
||||||
|
|
||||||
|
Here is what that looks like for this `Session` dataclass:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Session:
|
||||||
|
start_time: datetime
|
||||||
|
project_name: str
|
||||||
|
end_time: datetime | None = None
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if not isinstance(other, Session):
|
||||||
|
return NotImplemented
|
||||||
|
return self.start_time < other.start_time
|
||||||
|
|
||||||
|
# more methods below ...
|
||||||
|
```
|
||||||
|
|
||||||
|
This implementation of `__lt__` tells the sorting methods that _this_ (`self`)
|
||||||
|
instance of `Session` can be compared to some `other` instance of `Session` by
|
||||||
|
comparing their `start_time` values to see which is less than. The guard at the
|
||||||
|
beginning makes sure only instances of `Session` are being compared.
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Define A Set Of Class Methods
|
||||||
|
|
||||||
|
The most common way to define class methods is by defining them directly with
|
||||||
|
`self` (the class in the current context) on a method by method basis:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class User
|
||||||
|
def self.find_by(attrs)
|
||||||
|
# lookup logic ...
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have a group of class methods you want to define, you can stick them all
|
||||||
|
within a `class << self` block which does similarly defines each of them as
|
||||||
|
singleton methods of that class (`User` in this case):
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class User
|
||||||
|
class << self
|
||||||
|
def find_by_email(email)
|
||||||
|
# lookup logic ...
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_by_last_name(last_name)
|
||||||
|
# lookup logic ...
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This opens the singleton class of `User` for modification, adding these two new
|
||||||
|
methods.
|
||||||
|
|
||||||
|
We can see those defined alongside all other direct and inherited class methods:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
> User.methods
|
||||||
|
=>
|
||||||
|
[:find_by_email,
|
||||||
|
:find_by_last_name,
|
||||||
|
:yaml_tag,
|
||||||
|
:allocate,
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user