Python’s inherent trait
Compilation, interpretation
During compilation into "Python bytecode", the only compile-time errors that are caught are syntax
errors unlike Java (can catch type-safety related, etc)
a. Instead, at runtime, Python runs its compiled version line by line(this is interpretation), and if
one line has an error in it, the program won’t throw the error until it gets to that line.
b. Implies you won't catch certain silly errors in Python as easily as you would in Java
c. However, because of its REPL nature, you can test things on the fly.
Indentation
Indentation is by spaces not curly brackets like in Java
Type-related
Is dynamically typed. Not required to specify data types.
a. Implies type checking isn’t done at compile-time.
b. Can change types dynamically.
Supports mixing of heterogenous types
Accessibility & underscores
a. Everything is public in Python. No notion of accessibility.
b. Underscores are used to imitate reduction of accessibility
c. Couple of reasons why Python adopted this is here
Encoding, decoding
Encoding and decoding…
Computer only stores in bits. Let’s talk in a higher level, bytes
To store anything in a computer, you must map it to bytes then bits. The algorithm for mapping to
bytes is done by an encoding scheme that differs for each type.
Similarly to map the bits back to the bytes, we use a decoding scheme(just the opposite mapping of
encoding scheme)
PS: Strings stored in bytes are called byte strings. In python, they are printed as b“some string”.
BUT note that Python actually decodes it for you and prints it out nicely with the ‘b’ prepended to
inform us that we’re working with byte strings
Error handler
Used to handle errors related to encoding, decoding. Nothing to do with exception handling lol
Passing the following values to the errors parameter will do the following
(can use your own via codecs.register_error() ):
String stuff
Operators
‘+’ and ‘,’ can do string concatenation.
o But doesn’t implicitly invoke str()/__str__() method on non-strings implicitly so can’t
concatenate things like Number etc
“some string” * num prints “some string” repetitively
Triple quoted strings
Writing strings in triple “”” or ‘’’ creates multiline strings. No need that pesky “\n”
To prevent the “\n” from being inserted at a position, type ‘\’
Used to generate docstrings (ie JavaDocs for python)
Formatting string (f-strings)
format(value[, format_spec]) No one uses this bruuuh, f-strings ftw
a. The string on which this method is called can contain literal text or replacement fields
delimited by braces {}
b. Replacement field contains either the numeric index of a positional argument, or the name
of a keyword argument.
c. Returns a copy of the string where each replacement field is replaced with the string value
of the corresponding argument
d. There are a lot of type of format_spec, look up documentation.
f-strings
a. Syntax:
To output as string
a/s/r invokes ascii()/str()/repr() Typical format specifier stuff
Raw strings (r-strings)
You want to interpret everything inside literally (Eg: file paths, regex etc)
Prefix with r of R
print() stuff
print(*objects, sep=' ', end='\n', file=[Link], flush=False)
o Print elements of tuple, objects to the text stream file, separated by sep and followed by
end.
IO stuff
IO class hierarchy
Both RawIOBase and
BufferedIOBase read, write in bin
mode but latter is higher level?
[Link], [Link] are
subtypes of TextIOWrapper
Garbage collection
In Java, unreferenced Scanner objects are garbage Similarly, in Python unreferenced input(file/std in etc)
collected but the underlying stream is still open(ie objects are garbage collected but the input stream itself
finalize() is not called on the stream). is open
BUT once you close stream, you can’t open it in Java
but for Python, you can
flush()
Why flush()?
For streams that’re buffered, data is held in buffer region temporarily before received by
corresponding stream.
By flushing, we force data out of buffer and into stream.
Common example: We do [Link]() so that everything is written onto terminal quickly
instead of waiting for some time
Working with user input
input() function to accept user input from the console and returns it
Optionally takes string as an argument and displays it to the user before waiting for their input
Common methods to produce File object
Click here to know more about this method
Common methods on File Objects
read(n=-1) Binary mode:
Reads up to n bytes/Reads n bytes (depends on implementation class) else till EOF
character if n not specified or negative
Return n bytes read if specified else empty bytes object
Textual mode:
Reads up to n character else till EOF
Return n chars read as string if specified else empty string when EOF reached
read1(n=-1) Reads and return up to n bytes
readline(n=-1) Binary mode:
Read and return one line from the stream. If size is specified, at most size bytes
will be read.
The line terminator is always b'\n' for binary files; for text files, the newline
argument to open() can be used to select the line terminator(s) recognized.
Textual mode:
Read until newline or EOF and return a single str. If the stream is already at EOF,
an empty string is returned.
If size is specified, at most size characters will be read.
readlines(hint=-1) Read and return a list of lines from the stream. hint can be specified to control
number of lines read
Often a “for line in f” is used instead as it’s cleaner/ memory-efficient/ faster
Write(content) Write content to file object like stdout and returns number of chars/bytes written
Working with files
Opening/closing files
open() function is used to open a file.
Takes two arguments: file path and a mode to open file in.
The most common modes are:
Character Meaning
'r' open for reading (default)
'w' open for writing, truncating the file first
'x' open for exclusive creation, failing if the file already exists
'a' open for writing, appending to the end of file if it exists
'b' binary mode
't' text mode (default)
'+' open for updating (reading and writing)
Must close file with close() method or with with clause else other processes can’t use it
Reading files
read() : reads the entire file content and returns it as a string.
readline() : reads a single line from the file till it encounter a newline or EOF
o If EOF, returns an empty string
readlines() : Does bunch of readline() till reaches EOF of file and returns as list[str]
Remember to adjust file pointer with
seek(offset, reference=0 by default ie start of file. 1 is current position, 2 is
EOF)
Writing files
write(string)
writelines(iterable of strings)
o Note these strings will not have new lines so can use write("\n".join(someIterable))
Just use print la bro…
o Modify argument of file parameter to the file instead of [Link]
o Omit the newlines by print if needed since lines in files typically already have a newline at
the end
Venv
An environment to isolate project-specific Python related binaries and dependencies to avoid
dependency conflicts with other projects
Created with python -m venv /path/to/new/virtual/environment
[Link] and sys.exec_prefix point to the directories of the virtual environment
sys.base_prefix and sys.base_exec_prefix point to those of the base Python used to create the
environment
Sufficient to check [Link] != sys.base_prefix to check if running from virtual environment
Activating/Deactivating
Virtual environment by be activated using the activate script
OR by running the python intepretor located in the venv (ie in the bin or Scripts folder)
a. To achieve latter, scripts in venvs must follow this
Just type deactivate (script located in bin (Linux) or Libs (Windows) directory)
Functions
Positional/Keyword Args & Required/Optional
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
The / and * are optional
o So if you omit both, you have a parameter that can be both positional and named
Note: Positional != Required nor Keyword!=Optional
o All arguments are required unless you provide the following syntax
o Eg: def f(a=sthsth, …). Note the parameter a is positional since it’s before / .
Variable arguments with *, **
Refer here
Decorators
Decorators use wrapper functions to do wrapper function stuff i.e. modify behavior w/o modifying
wrapped function
How to achieve?
They take in the wrapped_func => define a wrapper_func to do pre/post hooks before/after calling
wrapped_func
This wrapper_func is returned by decorator to actually be applied on whatever wrapped_func
decorator is decorating
def decorator_name(wrapped_func, ...):
def wrapper(...):
//some logic
wrapped_func()
//some logic
return wrapper
Python’s syntax sugar
(less clunky)
@decorator_name
def wrapped_func():
...
If wrapped_func needs to accept argument, ensure parameter list in
wrapper_func is be consistent
Same for variable argument list
Eg:
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper_do_twice
@do_twice
def greet(name):
print(f"Hello {name}")
>>> greet("World")
Hello World
Chaining decorators
Wrapped function first is passed into bottom-most decorator.
Decorators with arguments (Might be wrong)
By default according to the above syntax, decorators only take in the wrapped_func as argument
If you want to pass in extra, you have to wrap an additional decorator function
def decorator_name(additional arguments):
def decorator_name1(wrapped_func, ...):
def wrapper(...):
//additional arguments will be used somewhere in this body
//some logic
wrapped_func()
//some logic
return wrapper
Preserving metadata
During introspection of the wrapped functions (achieved via attributes like __name__, __doc__), the
output is of the wrapped function
Use @[Link](wrapped_func) to prevent this as it copies meta data from wrapped
function to wrapper functions
No wrapper
You don’t always need a wrapper_func Eg:
Scoping
Scope types
Local = function level scope
Enclosing/non-local = enclosing scope of nested function level = outerfunction’s local scope
Global = module level
Built-in = Built-in level (Essentially searches the builtin module)
Namespaces
Scopes are implemented as the dictionary that maps the name to value
Are referred to as namespaces
o locals() return local namespace,
o globals() return global namespace
o etc
Search mechanism + Gotcha
Searches the LEGB scope from innermost to outermost, ie LEGB
Note: Inner scope will shadow outer if there’s variable of same name
Thus accessing will work but modifying won’t
global keyword, globals() namespace
To tell interpreter to refer to the global variable and not create a local one, use global keyword
o Can also use it to define global variables in function to create global variables (smelly)
globals is the global scope’s writable dictionary
Nonlocal keyword
Similar to global but meant to refer to variables in enclosing scope of nested function
Data structures
Range
Iterable
An object capable of returning its members one at a time(by iterating duh). Examples of Iterable:
All sequence types (such as list, str, and tuple)
Some non-sequence types like dict, file objects,
Objects of any classes you define with an __iter__() method
Or __getitem__() method, ie indexed access that implements Sequence semantics.
Iterator
Just like in Java, the same nuance between Iterable and Iterator exists which is the former has
methods that return an Iterator and the latter implements logic for the iteration itself.
To be an iterator == must support following 2 methods to obey the iterator protocol:
1. iterator.__iter__(): Returns iterator itself. Required to allow containers/iterators be used in
for and in statements.
2. iterator.__next__(): Return next item ( StopIteration exception if no more items).
Comprehension
All mutable Iterables have comprehension, i.e., create new iterables based on old ones via a special
syntax (specifically a list is created, and result of expression is appended to that list)
1. Has 1 for statement
o Followed by >=0 if statement (stick to 1, use AND, OR for complicated ones)
2. Repeat step 1 till u dw
NOTE: Each subsequent for or if will be indented (ie nested) 1 level deeper
Nested Comprehension
Essentially, the expression part in the syntax is now another comprehension
Sequences
Sequence types:
An iterable which supports efficient element access using integer indices via
__getitem__() special method and
__len__() method that returns the length of the sequence
Eg: list, str, tuple, bytes
String
1. replace(old, new[, count])
2. split(sep=None, maxsplit=- 1)
3. startswith(prefix[, start[, end]])
4. strip()/rstrip()/lstrip()
5. upper()/lower()/capitalize()/title()
6. isXXX() where XXX = upper/lower/digit/alpha/alnum/space…
7. For concatenation, the more efficient way is using join(iterable)
For more things, refer here
Lists (slicing supported by all Sequences)
Lists in python = Syntax of Java’s array + Java’s ArrayList-esque methods
1. Negative indices are supported.
a. Counts from back of list. list[-n] == list[ len(list) - n]
2. Slicing (return shallow copy) = Invokes slice() method
a. General syntax:
list[start:stop:stride]
start is closed end. stop is open end.
o Default= 0 / len(list) if stride positive
o Default: -1/-len(list)-1 if stride negative
Tip:
Think like this -n means n elements from the back ==
Has len(list)-n elements at start before I reach “-n”th element ==
Is thus “len(list)-n + 1”th element
Stride = dictates order to enumerate
o Default: +ve, enumerate array from left to right
o -ve, enumerate array from right to left
NOTE: list[a:b]+list[b:c] = list[a:c]
NOTE: No out of bounds when slicing
Range
Produces a sequence of numbers meant to be used usually as indices
o can accept any object with the __index__() special method
o Default start=0,step=1
o For +ve step, r[i] = start + step*i where i >= 0 and r[i] < stop.
o For -ve step, r[i] = start + step*i where i >= 0 and r[i] > stop.
Implement the common Sequence operations except concatenation/repetition (both of these
violate a strict pattern that Range follows)
Advantage of Range is that it only stores the start, stop, step values thus memory storage is small
(ie calculate the items and subranges on the fly)
Tuples
Similar to lists with few differences.
1. Use () instead of [] during declaration.
(Technically tuples are defined by the commas, () are for clarity. Thus 1-tuple should have trailing
comma)
2. Lists Tuples
Usually Usually heterogenous
homogenous
Mutable Immutable
Usually Usually accessed via unpacking from return values
accessed via (can omit the bracket since tuples are actually defined by commas)
index
Dictionary
Dictionary in Python = Java’s Map with open-addressing as collision strategy
Updating dictionary from another
Can be done via unpacking dictionary
With 3.10, can do it with | (returns a new dictionary) or |= (in-place)
o Favored over unpacking dictionary cos of 2 reasons:
o {**d1, **d2} ignores the types of the mappings and always returns a dict even if d1, d2
may be other subtypes
o “type(d1)({**d1, **d2}) fails for dict subclasses such as defaultdict that have an
incompatible __init__ method.”
o Refer here
Exceptional handling
try = code block that may throw error
except = code block to handle error
o Standard stuff across langs:
o Catch multiple exceptions, catch and place into variable, etc
o Ensure top except is not parent class (smelly)
o Can catch all with except * (including SystemExit (raised by [Link](), etc.)
and KeyboardInterrupt (triggered by pressing Ctrl + C).) Better to use catch Exception to
only catch exceptions
Although BaseException is the mother of exception, it includes shit like
ArithmeticError, BufferError, LookupError
else = code block if no error
o Better than adding additional code to the try clause because it avoids accidentally catching an
exception that wasn’t raised by the code being protected by the try … except statement.
finally = code block regardless of anything
raise == Java’s throw
o
Can either raise an instance or a class as
instance is implicitly instantiated with no
arguments constructor
o Can manually chain exception via this syntax:
raise <new_exc> from <old_exc>.
Metaclass
A metaclass defines how a class is created (from which instances are created)
Class is an instance of metaclass
o Think of it as a class factory
Refer to these answers when pro:
o [Link]
o [Link]
Terminology
import
from 'x' import 'y'
x: package/module. y: something inside x. Can be module / class,function
Can provide aliases for x and y.
o Eg: import x as aliasForX
o Eg: from A.B import C, D as E, F
Can use the * symbol to import everything from x
Module
A module is typically(not always, insert exception later) a .py file which we specify without the .py
extension
Module search path
Eg: Searches a module named spam
Searches for built-in module with that name
a. These names are listed in sys.builtin_module_names
Else, searches for a file named [Link] according to a list of directions given by the variable
[Link] that is initialized from these locations:
a. Directory containing the input script ie the file you execute(or the current directory when no
file is specified).
b. PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
c. The installation-dependent default, the directory where Python installs its stuff: binaries,
modules, etc
i. by convention, a site-packages/Lib(in Windows) directory is included also which
is handled by the site module
Packages
a package is typically a folder with subpackages/modules and __init__.py file (unless using
a namespace package, a relatively advanced feature)
o Prevents directories with a common name, such as string, from unintentionally hiding valid
modules that occur later on the module search path
o __init__.py can be an empty but It can also execute initialization code for the package or
set the __all__
Importing * from a package
Importing * from a package is inefficient, it won’t get you all the modules, only modules w/o leading
_
o Solution: Define __all__ = [list of modules to import when use *] in __init__.py
o Eg: from [Link] import *, you could have the following:
Intra-package References
Can use relative imports based on name of current module
In surround module, you can
do
Since __name__ of main module is always __main__, relative imports within the main module can
be problematic because __main__ doesn't provide a clear directory reference.
o use absolute imports
__name__
When python interprets a .py file, it initializes some variables, one of them is _name_
o Value is fully qualified name of current module where code is being interpreted. Is to
uniquely identify module in import system
o But for the name of the main module (the script you execute directly), value will just always
be __main__
__main__
__main__ is used for 2 important things,
1. If module is executed in the top-level code environment (basically the entry point of an app,
typically user-specified), its __name__ is set to the string '__main__'.
a. Else, name of the current module
b. Why do this?
2. See this I lazy
_ and __
1. _single_leading_underscore: Convention that identifier should be treated as private(but it’s
not) + cannot be imported
2. single_trailing_underscore_: used by convention to avoid conflicts with Python keyword
3. __double_leading_underscore:
a. “Mangles” the name to avoid name collisions from other classes, specifically subclasses.
by replacing _name to _classname__name internally
this means it’s even more private than _single_leading_underscore
b. Subtypes now can’t accidentally change supertype’s stuff (if it’s a method can’t override)
i. Meant to ensure something internal to superclass isn’t changed and not break
intraclass stuff
4. __leadingtrailingdoubleunderscore__: Are called magic methods. Are helper methods for
some actual method.
a. Overloading them is called “operator overloading” as it overloads built in operators(+, - ,[])
and operations(assignments, invoking method…)
Usage of * and **
Some background definitions:
Unpacking: Assignment of X individual elements of a Iterable to X variables
Packing: Assignment of multiple individual elements to a Iterable
Unpacking, packing in context of assignments
Unpacking iterables
Packing iterables into list with *
NOTE:
Non-starred variables on LHS are mandatory to have values!
Target variable has to be tuple/list thus need trailing comma if single variable
Good use cases for unpacking, packing iterables
1. Assign in parallel
2. Swap without temp
a. Creates a tuple with b,a before assignment.
b. Rmb tuples are defined by commas, () are for clarity
3. Drop unneeded variable
Unpacking dictionaries
NOTE: IF there are common keys, values of right-most dictionaries will win
Variable arguments with *, **
Defining formal argument to
1. *args To pass variable length positional arguments will pack into a tuple
2. **kwargs To pass variable length keyword arguments will pack into a dictionary
Defining actual arguments to
1. *someIterable = will unpack to positional arguments
2. **dictionary = will unpack to keyword arguments of same name
Can use both at same time but will cancel each other out lmao
del statement
Can be used to clear
Element/Slices/entire list
Key-value pair of dictionary
Variable itself
docstrings
Strings for documentation
o Write the strings between “”” and “””
o Write for module at the top,
o Write for classes and methods at start of definition
1st line = short summary
o Shouldn’t include type, name, etc cos there’s other means for that
If got more lines
o 2nd line = blank to visually separate summary from description
Doesn’t strip indentation
o To strip using tools, look at how tools do here
pass keyword
Control pass by w/o doing anything
with keyword
Used to close resources automatically without invoke the close() method. Avoids resource leak.
Something like Java’s try-with.
__repr__ vs __str__
Once pro, refer here
Class
class Student:
def __init__(self, name, roll_no):
[Link] = name
self.roll_no = roll_no
Self
self = reference to instance itself
Is always 1st parameter
By convention, is referred to as self
This is Java’s this EXCEPT with some differences:
Not required when method is called except when using this syntax:
[Link](args...) [Link](instance, args...)
Automatically translated
by Python into this
equivalent form
__init__
Constructor
Defining instance fields
Instance variables
Any variable prefixed with self, i.e., self.some_var defined in the __init__ method
They can also just spring into existence when first assigned to
Static vs instance variable
Static vs Instance variable
static = Anything defined outside of instance method or __init__
o If instance attempts to change it, an instance copy of that
variable is created! BAD
Instance method = Opposite as mentioned here
If both exists, instance is prioritized
Method
For instance-method: self is required as first explicit parameter during method declaration
o Method objects automatically take self as argument too
For static-method: Normal method declaration
Method object, Function object
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
x.f is a method object, x is an instance
MyClass.f is a function
Multiple inheritance
Unlike Java, it’s legal
Method resolution strategy of Java’s “simulated multiple inheritance with Interfaces” differs from
Python’s method resolution strategy
For most cases, the MRO is up the inheritance hierarchy only if all subclasses have been traversed, left-
right.
But in reality, MRO is dynamic because of some complicated algorithm called the C3 linearization:
super().__init__() syntax
In here, I talked about 2 syntaxes. Use the super().__init__() syntax rather than the explicitly
using the superclass name
Since you are not hardcoding, it aids the MRO and thus very beneficial for multiple inheritance
What I’m saying is very hand-wavey because I suck. Refer here and here once pro
Abstract classes
1. No abstract classes
2. Can simulate one by using @abstractmethod
Superclass and subclass constructor
Java Python
Superclass constructor MUST be called first in Not necessary
subclass constructor
Superclass constructor is ALWAYS called implicitly No such thing as “implicitly called”. If it isn’t
(no-args constructor) or explicitly called, it isn’t called.
MRO will determine which
super().__init__() method to invoke when
we call the subclass.
Overriding
Due to dynamic nature, @Override equivalent is not present since return type/argument types can
be anything
If you define a method in a subclass with the same name as a method in its superclass, Python
automatically considers it an overridden method.
a. MRO determines which method is invoked