Chapter 2_Core Data Types


I. Strings:

1. Basics:

a = "Hello" <=> 'Hello'
b =  """Lorem ipsum dolor sit amet,
consectetur adipiscing elit """ # Multiple line str

print(a[1])  # # Strings are sequences (indexable & iterable)

2. Access:

2.1 Indexing:

x = 'abc'
print(x[0]) # a

2.2 Slicing

b = "Hello, World!"
print(b[2:5])   # llo
print(b[:5])    # Hello
print(b[2:])    # llo, World!
print(b[-5:-2]) # orl

2.3 Membership

thing in stringVar
thing not in smth

2.4 Length:

len(str1)

3. Modification:

3.1 concatenation:

a = "Hello"
b = "World"
c = a + " " + b
print(c)  # Hello World

3.2 builtin-methods:

a = "Hello, World!"
print(a.upper())        # HELLO, WORLD!
print(a.lower())        # hello, world!
print(a.strip())        # removes whitespace
print(a.replace("H", "J"))  # Jello, World!
print(a.split(","))     # ['Hello', ' World!']
print(a.find(','))      # returns the position of `,` (first occurence) (-1 -> element not found)

3.3 Essential String Methods

upper(), lower(), capitalize()
startswith(), endswith()

split(c) # splits from the left, c = seperator char, whitespace (default)
rsplit() # Default separator = whitespace

strip(c), rstrip(), lstrip() # default is whitespace

index()
count()
replace(a, b)
find(smth)

4. f-string:

x = 1
print(f"Value is {x}")

x = 32
f"Value {x}"      # placeholder {}
f"Result {1+2}"   # expression 

# modifier ( A modifier is included by adding a colon `:` followed by a legal formatting type, like `.2f`)
price = 59
txt = f"The price is {price:.2f} dollars"

5. Raw Strings & Regex:

✅ Regex = raw string + re functions

"\\d+"     # ❌ interpreted by Python
r"\\d+"    # ✅ correct for regex

print(r"C:\new\folder")  # raw string






II. Booleans:

1. Most Values are True

✅ True:

  • Non-empty strings
  • Non-zero numbers
  • Non-empty lists, tuples, sets, dicts

❌ False:

False <=> None, 0, "", (), [], {}

2. Custom len Example

👉 Objects with __len__() that returns 0 or False evaluate to False

class myclass():
  def __len__(self):
    return 0

myobj = myclass()
print(bool(myobj))  # False






III. Operators:

1. Arithmetic Operators

# Binary Operators:
x + y   # Addition
x - y   # Subtraction
x * y   # Multiplication
x / y   # Division
x % y   # Modulus
x ** y  # Exponentiation
x // y  # Floor division

# Unary Operators: (+/-) (signes)
+1, -1 

2. Assignment Operators

=, +=, -=, *=, /=, %=, //=, **=, &=, |=, ^=, >>=, <<=, := ( walrus operator )

Note (Walrus operator):

In Python, conditions allow expressions only, not statements.

❌ Invalid:

if x = 3:
    pass

:= evaluates an expression, assigns it to a variable, and returns the value

if (n := len(data)) > 10:
    print(n)

3. Comparison Operators

==, !=, >, <, <=, >=

Python allows chaining comparison operators:
 ( 1 < x < 10 ) <=> ( 1 < x  and x < 10 )

4. Logical Operators

and, or, not

5. Identity Operators

is not
is : returns true if both vars points to the same object

6. Membership Operators

in, not in  

7. Bitwise Operators:

& : bitwise and
| : bitwise or
^ : xor
~ : bitwise not
x<<n : left shift (adding n 0 )
x>>n : right shift

8. Python Operator Precedence:

()
**
+ - ~ : unary
* / // %
+ - : binary
<< >>
&
^
|
comparisons, identity, membership
not
and 
or


# same order => L -> R evaluation

9. Special unary operator: (Asterisk *)

  • Using * and ** to pass arguments to a function
  • Using * and ** to capture arguments passed into a function
  • Using * to accept keyword-only arguments
  • Using * to capture items during tuple/list unpacking
  • Using * to unpack iterables into a list/tuple
  • Using ** to unpack dictionaries into other dictionaries
  • More details
  • etc
  • When * and ** CAPTURE values:

  • * collects positional values

    • tuple in function definitions (*args)
    • list in assignment unpacking (a, *b = ...)
  • ** collects keyword values ( it always produces a dict )

    • dict in function definitions (**kwargs)

1) Using * and ** to pass arguments to a function

* for positional arguments

fruits = ['lemon', 'pear', 'tomato']
print(*fruits)

This sends each item in the list as a separate positional argument.


** for keyword arguments

date_info = {'year': '2020', 'month': '01'}
"{year}-{month}".format(**date_info)

This sends each key/value pair as a named argument.

Python 3.5+ even allows multiple * or ** in a call:

print(*nums, *fruits)

2) Using * and ** to capture arguments passed into a function

*args _ capture any number of positional arguments

def roll(*dice):
    ...

Here dice becomes a tuple of all extra positional args.


**kwargs — capture keyword arguments

def tag(name, **attributes):
    ...

attributes becomes a dictionary of extra named args.


3) Using * to accept keyword-only arguments

If you place a * alone in a function signature, all following parameters must be passed by name:

def get_multiple(*keys, dictionary, default=None):
    ...

Here dictionary and default cannot be passed positionally.

This mechanism is used in built-in functions like sorted to make some parameters keyword-only.


4) Using * to capture items during tuple/List unpacking

Python lets you use * in assignments to grab multiple items:

first, *rest = fruits

rest becomes a list of the remaining items.


This also works between variables:
first, *middle, last = fruits

Only one * expression is allowed at a given unpacking level.


5) Using * to unpack iterables into a list/tuple

Python 3.5+ allows you to “dump” items from an iterable into a new list, tuple, or set:

[*sequence, *reversed(sequence)]

This is a powerful alternative to chaining with + or converting manually.
You can also unpack into tuples and sets:

(*fruits[1:], fruits[0])
{*fruits, *uppercase_fruits}

6) Using ** to unpack dictionaries into other dictionaries

Python 3.5+ lets you expand key/value pairs from one or more dicts into a new dict:

all_info = {**date_info, **track_info}

You can also merge with additional keys and override values.


7) More details:

  • 7.1) Function Parameter Boundaries (*, *args, /)

    DefinitionEffect
    def f(a, b, *, c, d)c, d → keyword-only
    def f(a, *b, c, d)c, d → keyword-only
    def f(a, b, /, c, d)a, b → positional-only
  • 7.2) * in function calls force positional evaluation order

def f(a, b, c):
    ...

args = (1, 2, 3)
f(*args)   # positional mapping happens left → right
f(*args, c=5)  # ❌ multiple values for c
  • 7.3) ** cannot capture non-string keys
{**{1: "a"}}   # ❌ TypeError
  • 7.4) * unpacking works in return statements
def f():
    return *range(3), 10

# Equivalent to returning a tuple: (0, 1, 2, 10)