- Published on
Python Learning Journey
Table of Contents
- Built In Constants
- Maximum and Minimum Integer Values
- Keywords
- Naming conventions:
- Packages and Modules
- Libraries and built-in modules
- ORD
- Sorting a dictionary
- Method 1: Get sorted keys and then build a new dictionary
- Method 2: Sort items and convert back to dictionary
- Counter
- Deque
- Defaultdict
- Defaultdict in Python
- List Depth (Nesting Level)
- defaultdict(list)
- defaultdict(set)
- Citations:
Built In Constants
Maximum and Minimum Integer Values
1.sys.maxint - The maximum integer value that can be stored in a Python integer variable. 2.sys.minint - The minimum integer value that can be stored in a Python integer variable.
Keywords
- with: The
with
keyword in Python is used for resource management and exception handling, particularly when working with unmanaged resources like file streams.1
The key points about the with
statement in Python are:
Resource Management: The
with
statement ensures that a resource (e.g., a file, a lock, a database connection) is properly acquired and released, even in the presence of exceptions. This helps avoid resource leaks and ensures that the resource is always properly cleaned up.Exception Handling: The
with
statement simplifies the use oftry-finally
blocks, which are commonly used to ensure that a resource is properly released, regardless of whether an exception is raised or not.Syntax: The
with
statement is used as follows:
with <context_expression> as <target>:
<suite>
The <context_expression>
is an expression that returns a context manager object, which must have __enter__()
and __exit__()
methods. The <target>
is an optional name that is bound to the object returned by the __enter__()
method.
Context Managers: Objects that support the
with
statement are called "context managers". They provide the__enter__()
and__exit__()
methods, which are called when thewith
block is entered and exited, respectively.Examples: The most common use of the
with
statement is with file handling, where it ensures that the file is properly closed, even if an exception is raised:
with open('file.txt', 'r') as f:
data = f.read()
The with
statement can also be used with other context managers, such as locks, database connections, and custom context managers. In summary, the with
keyword in Python is a powerful tool for resource management and exception handling, making code more concise, readable, and less prone to resource leaks.
RunnablePassThrough(): The RunnablePassthrough() component is part of the LangChain library and is used to handle the input to the RAG (Retrieval-Augmented Generation) chain. Here's a breakdown of what RunnablePassthrough() does and the implications of not using it:
Purpose of RunnablePassthrough():
The RunnablePassthrough() component is a utility class in LangChain that allows you to pass the input question directly to the next step in the chain, without any additional processing. It is often used in the context of a RAG chain, where the input question needs to be passed to the retriever and the language model without any modifications. What happens if you don't use RunnablePassthrough(): If you don't use RunnablePassthrough() in the provided code, the input question would not be passed directly to the next step in the chain. Instead, the input question would need to be handled by a separate component, such as a custom prompt template or a custom input processing function.
Naming conventions:
Python has the following method naming conventions:
Single Underscore Before a Name (_name): This is a convention to indicate that the name is intended for internal use and should be treated as "private". It's a way to signal to other programmers that the name is an implementation detail and should not be accessed directly from outside the class. However, this is just a convention, and Python does not enforce true privacy. The name can still be accessed from outside the class if needed.
Double Underscore Before a Name (name): This is not a convention, but a specific language feature called "name mangling". Python automatically renames these methods to _ClassName_name to avoid naming conflicts with subclasses. This is used to create a form of name obfuscation and to make it harder for subclasses to accidentally override these methods. These "name-mangled" methods are still accessible, but the mangled name should be used to access them.
Double Underscore Before and After a Name (name): These are special "magic" or "dunder" (double underscore) methods in Python. They are part of the Python language and have specific meanings and behaviors. Examples include init, str, len, etc. These methods are called by the Python interpreter in specific contexts. You can also define your own "magic" methods, but it's generally recommended to avoid this unless you have a specific reason, as it can lead to confusion.
In summary, the underscores before method names in Python are used for different purposes: Single underscore (_name) is a convention for "private" methods. Double underscore (**name) is a language feature for name mangling to avoid naming conflicts. Double underscore before and after (**name__) are special "magic" or "dunder" methods.
Here are examples for the different uses of underscores in Python method names:
Single Underscore (_name
):
class MyClass:
def __init__(self):
self._internal_variable = 42
def _internal_method(self):
print("This is an internal method.")
obj = MyClass()
obj._internal_method() # Access is possible, but discouraged
print(obj._internal_variable) # Access is possible, but discouraged
In this example, the _internal_method
and _internal_variable
are marked with a single underscore to indicate that they are intended for internal use and should be treated as "private" by other parts of the code.
Double Underscore (__name
):
class ParentClass:
def __private_method(self):
print("This is a private method in the parent class.")
class ChildClass(ParentClass):
def __private_method(self):
print("This is a private method in the child class.")
parent = ParentClass()
parent._ParentClass__private_method() # Access the parent's private method
child = ChildClass()
child._ChildClass__private_method() # Access the child's private method
In this example, the __private_method
methods are "name-mangled" by Python to avoid naming conflicts between the parent and child classes. The mangled names can be accessed using the _ClassName__method_name
syntax.
Double Underscore Before and After (__name__
):
class MyClass:
def __init__(self):
self.name = "MyClass"
def __str__(self):
return f"Instance of {self.name}"
def __len__(self):
return 42
obj = MyClass()
print(obj) # Output: Instance of MyClass
print(len(obj)) # Output: 42
In this example, the __init__
, __str__
, and __len__
methods are special "magic" or "dunder" methods in Python. These methods are called by the Python interpreter in specific contexts, such as when creating an instance of the class, printing the object, or using the len()
function on the object.
Packages and Modules
Based on the search results provided, here are the key points on when to use from package import module
vs import module
in Python:
Importing Modules vs Packages:
Using
from package import module
:Using
import module
:Aliasing Modules:
In general, you should use from package import module
when you only need to access specific modules or functions from a package, and import module
when you need to access multiple modules or functions from a package. Aliasing modules can be useful in both cases to make the code more readable and maintainable.
Libraries and built-in modules
- bisect.bisect_left, bisect.insort_left
- enumerate()
- any()
- lambda
- heapify
ORD
The ord()
function in Python is a built-in function that returns the Unicode code point of a given character. It takes a single character (a string of length 1) as an argument and returns its corresponding integer Unicode value.
Key points about ord()
:
Purpose: It converts a character to its numerical representation based on the Unicode standard.
Input: It requires a single character string. Providing a string with more than one character will result in a
TypeError
.Output: It returns an integer representing the Unicode code point of the input character.
Inverse of
chr()
: Theord()
function is the inverse of thechr()
function, which converts an integer Unicode code point back to its corresponding character.Use Cases: It is particularly useful in scenarios involving character encoding/decoding, text processing, cryptography, and custom sorting based on character values.
Example:
print(ord('A')) # Output: 65
print(ord('a')) # Output: 97
print(ord('€')) # Output: 8364
Sorting a dictionary
my_dict = {'apple': 3, 'orange': 1, 'banana': 2}
Method 1: Get sorted keys and then build a new dictionary
sorted_keys = sorted(my_dict.keys())
sorted_dict_by_keys = {key: my_dict[key] for key in sorted_keys}
print(sorted_dict_by_keys)
Method 2: Sort items and convert back to dictionary
This directly creates a new dictionary with items sorted by key:
sorted_dict_by_keys_items = dict(sorted(my_dict.items()))
print(sorted_dict_by_keys_items)
Counter
The Counter
in Python is a specialized dictionary subclass found within the collections
module. It is designed for efficiently counting hashable objects within an iterable.
Key Characteristics and Usage:
Counting Occurrences: It takes an iterable (like a list, string, or tuple) and creates a dictionary-like object where keys are the unique elements from the iterable and values are their respective counts.
Subclass of
dict
:Counter
inherits fromdict
, meaning it behaves largely like a standard dictionary. You can access counts using keys, iterate through items, keys, or values, and use methods likeupdate()
.Handling Missing Keys: Unlike a regular dictionary, accessing a key that is not present in a
Counter
object will return a count of0
instead of raising aKeyError
.most_common()
Method: This method returns a list of then
most common elements and their counts, ordered from most frequent to least frequent.elements()
Method: This method returns an iterator that yields each element as many times as its count.Arithmetic Operations:
Counter
objects support basic arithmetic operations like addition, subtraction, intersection, and union, allowing for set-like operations on counts.
Example:
from collections import Counter
# Counting elements in a list
my_list = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
counts = Counter(my_list)
print(counts) # Output: Counter({'apple': 3, 'banana': 2, 'orange': 1})
# Accessing a count
print(counts['apple']) # Output: 3
# Handling a missing key
print(counts['grape']) # Output: 0
# Finding the most common elements
most_common_items = counts.most_common(2)
print(most_common_items) # Output: [('apple', 3), ('banana', 2)]
Deque
collections.deque
(pronounced "deck") in Python is a list-like container that provides efficient appending and popping of elements from both ends of the sequence. It is a double-ended queue, which means it can function as both a stack (Last-In, First-Out) and a queue (First-In, First-Out).
Key Features and Advantages:
Efficient Operations at Both Ends: Unlike standard Python lists, which have O(n) time complexity for inserting or removing elements from the beginning,
deque
offers O(1) time complexity forappend()
,appendleft()
,pop()
, andpopleft()
operations. This makes it ideal for scenarios requiring frequent additions or removals from either end.Memory Efficiency:
deque
is designed to be memory-efficient, especially when dealing with large sequences and frequent modifications.Thread Safety: Append and pop operations on a
deque
are thread-safe, making it suitable for concurrent programming.Fixed-Size Deque (maxlen): You can create a
deque
with amaxlen
parameter, which automatically discards elements from the opposite end when the maximum length is reached. This is useful for implementing fixed-size buffers or sliding window algorithms.
Common Operations:
Creation:
from collections import deque
d = deque([1, 2, 3])
Adding elements:
d.append(4) # Adds to the right: deque([1, 2, 3, 4])
d.appendleft(0) # Adds to the left: deque([0, 1, 2, 3, 4])
Removing elements:
right_element = d.pop() # Removes from the right: 4
left_element = d.popleft() # Removes from the left: 0
- Other Operations:
extend()
,extendleft()
,rotate()
,clear()
,count()
,remove()
.
Use Cases: Implementing queues and stacks, sliding window problems in algorithms, managing recent history or logs with a fixed size, and producer-consumer patterns in concurrent programming.
Defaultdict
In Python, a defaultdict is a specialized dictionary that returns a default value for missing keys, making data handling easier and more robust. Python does not have built-in concepts of "default depth" for lists or sets, but depth usually refers to nesting levels in lists. Here is a breakdown with examples and explanations for each concept.
Defaultdict in Python
A defaultdict lets you specify a type or function that produces default values for keys that don't exist. For example:
from collections import defaultdict
d = defaultdict(list)
d['fruits'].append('apple')
d['vegetables'].append('carrot')
print(d['fruits']) # ['apple']
print(d['juices']) # []
Here, d['juices']
returns an empty list instead of causing an error, because we initialized the dict with list
as its default factory.
List Depth (Nesting Level)
"Default depth" of a list is not a standard term, but depth usually means how many levels of nested lists exist in a structure. For example:
my_list = [1, [2, [3, [4]]]]
# Function to get max depth
def list_depth(lst):
if isinstance(lst, list):
if len(lst) == 0:
return 1
return 1 + max(list_depth(item) for item in lst)
else:
return 0
print(list_depth(my_list)) # 4
In Python, defaultdict(list) creates a dictionary that gives an empty list as the default value for any new key, while defaultdict(set) provides an empty set as the default for missing keys. Both make it easy to append or add items to new keys without manual checks or initialization.
defaultdict(list)
- When accessing a non-existent key, an empty list
[]
is automatically created. - It's ideal for grouping or collecting items by key.
Example:
from collections import defaultdict
d = defaultdict(list)
d['fruits'].append('apple')
d['fruits'].append('banana')
d['vegetables'].append('carrot')
print(d)
# Output: defaultdict(<class 'list'>, {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']})
If d['unknown']
is accessed, it simply returns []
instead of raising a KeyError.
defaultdict(set)
- When accessing a non-existent key, an empty set
set()
is automatically created. - Useful for collecting unique items by key.
Example:
from collections import defaultdict
d = defaultdict(set)
d['fruits'].add('apple')
d['fruits'].add('banana')
d['fruits'].add('apple') # Won't be duplicated
d['vegetables'].add('carrot')
print(d)
# Output: defaultdict(<class 'set'>, {'fruits': {'apple', 'banana'}, 'vegetables': {'carrot']})
Citations:
Footnotes
https://stackoverflow.com/questions/1369526/what-is-the-python-keyword-with-used-for ↩
https://note.nkmk.me/en/python-import-usage/ ↩ ↩2 ↩3 ↩4 ↩5 ↩6
https://www.geeksforgeeks.org/import-module-python/ ↩ ↩2 ↩3 ↩4 ↩5 ↩6
https://www.digitalocean.com/community/tutorials/how-to-import-modules-in-python-3 ↩ ↩2