🎉 75% of content is free forever — Unlock Premium from $10/mo →
CW
Search courses…
💼 Servicesℹ️ About✉️ ContactView Pricing Plansfrom $10

Python Variables and Data Types — The Complete Guide

Python BasicsVariables and Data Types🟢 Free Lesson

Advertisement

Python Variables and Data Types — The Complete Guide

Learning Objectives

By the end of this tutorial, you will be able to:

  • Understand how Python variables differ from variables in statically-typed languages like C and Java
  • Identify and use all major Python data types including numeric, text, boolean, sequence, mapping, set, and binary types
  • Perform type checking with type() and isinstance(), and convert between types safely
  • Use all relevant built-in functions for type inspection, conversion, and manipulation
  • Apply dynamic typing principles to write flexible, idiomatic Python code
  • Recognize and avoid common mistakes related to variable assignment and type behavior
  • Understand memory management with id(), hash(), and reference counting

What Are Variables?

In Python, a variable is a name that refers to a value stored in memory. Unlike C or Java where variables have fixed types declared at compile time, Python variables are labels (or references) bound to objects. When you write x = 42, Python creates an integer object with the value 42 and binds the name x to it.

Python uses reference counting for memory management. Every object tracks how many names refer to it. When the reference count drops to zero, the memory is freed automatically.

FeatureC / JavaPython
Type declarationRequired (int x = 42;)Not required (x = 42)
Type bindingCompile-time (static)Run-time (dynamic)
Memory managementManual or GCReference counting + garbage collector
Variable meaningMemory locationReference to an object
Mutability of variablesVariables can be constVariables are always rebindable

Creating Variables

Creating a variable in Python is as simple as assigning a value. No type declaration is required.

age = 25                 # int
temperature = 98.6       # float
name = "Alice"           # str
is_active = True         # bool

x, y, z = 10, 20, 30    # Multiple assignment
a = b = c = 0            # Same value to all

m, n = 5, 10
m, n = n, m              # Swap — Pythonic way
print(m, n)              # 10 5

Assignment Types

# Simple assignment
x = 10

# Multiple assignment
x, y, z = 1, 2, 3

# Unpacking an iterable
a, b, c = [10, 20, 30]

# Star unpacking
first, *rest = [1, 2, 3, 4, 5]
print(first)  # 1
print(rest)   # [2, 3, 4, 5]

# Ignoring values
_, important, _ = (1, 42, 3)

# Augmented assignment
x = 10
x += 5    # x = x + 5 -> 15
x -= 3    # x = x - 3 -> 12
x *= 2    # x = x * 2 -> 24
x /= 4    # x = x / 4 -> 6.0
x //= 2   # x = x // 2 -> 3.0
x **= 3   # x = x ** 3 -> 27.0
x %= 5    # x = x % 5 -> 2.0

Python supports optional type annotations (Python 3.6+) for clarity and static analysis, but they do not enforce types at runtime:

count: int = 42
name: str = "Bob"
count = "now I'm a string"  # Valid — type hints are not enforced

Python Data Types Deep Dive

Numeric Types

int — Integers

Python integers have unlimited precision. Unlike C's int, they can be arbitrarily large.

big_number = 10 ** 100   # No overflow

binary_num = 0b1010      # Binary: 10
octal_num = 0o17         # Octal: 15
hex_num = 0xFF           # Hex: 255
population = 7_900_000_000  # Underscores for readability (3.6+)

# Integer from different bases
print(int("42"))          # 42
print(int("1010", 2))     # 10 (binary to int)
print(int("FF", 16))      # 255 (hex to int)
print(int("17", 8))       # 15 (octal to int)

float — Floating-Point Numbers

Python floats are IEEE 754 double-precision (64-bit).

speed_of_light = 3e8     # Scientific notation: 300000000.0

# ⚠️ Precision gotcha
print(0.1 + 0.2)         # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False

import math
print(math.isclose(0.1 + 0.2, 0.3))  # True — use this instead

print(math.inf)          # inf (positive infinity)
print(float('nan') == float('nan'))  # False — NaN != NaN

# Float from different formats
print(float("3.14"))     # 3.14
print(float("inf"))      # inf
print(float("-inf"))     # -inf
print(float("nan"))      # nan

complex — Complex Numbers

z = 3 + 4j
print(z.real)    # 3.0
print(z.imag)    # 4.0
print(abs(z))    # 5.0 (magnitude: sqrt(3² + 4²))
print(z.conjugate())  # (3-4j)

# Create complex from values
z2 = complex(3, 4)
print(z2)        # (3+4j)

# Complex from string
z3 = complex("1+2j")
print(z3)        # (1+2j)

decimal — Precise Decimal Arithmetic

For financial calculations where IEEE 754 floating-point errors are unacceptable:

from decimal import Decimal, getcontext, ROUND_HALF_UP
getcontext().prec = 28

price = Decimal('19.99')
tax = Decimal('0.08')
total = (price * tax).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(total)  # 1.60

# Compare with float
print(Decimal('0.1') + Decimal('0.2'))  # 0.3 (exact)
print(0.1 + 0.2)                         # 0.30000000000000004 (inexact)

Text Type

str — Strings

Strings in Python are immutable sequences of Unicode characters.

s = "Hello, World!"
raw = r"C:\new\folder\test"       # Raw string — no escape processing
multi = """Triple quotes
span multiple lines"""

# f-strings (3.6+)
name = "Alice"
age = 30
print(f"My name is {name} and I'll be {age + 1} next year.")

# Strings are immutable
# s[0] = "h"  # TypeError
s_new = "h" + s[1:]  # Create a new string

# Common methods
text = "  Hello, Python World!  "
print(text.strip())                          # "Hello, Python World!"
print(text.lower())                          # "  hello, python world!  "
print(text.replace("World", "Universe"))     # "  Hello, Python Universe!  "
print(text.split(","))                       # ["  Hello", " Python World!  "]
print("-".join(["a", "b", "c"]))             # a-b-c

Boolean Type

bool — Booleans

Booleans are a subclass of integers. True equals 1, False equals 0.

print(True + True)         # 2
print(isinstance(True, int))  # True

# Truthy and Falsy values
# Everything evaluates to False except:
# False, None, 0, 0.0, 0j, "", [], (), {}, set(), frozenset(), range(0)

print(bool(""))           # False
print(bool("False"))      # True — non-empty string!
print(bool(0))            # False
print(bool(-1))           # True — any non-zero number
print(bool([]))           # False
print(bool([0]))          # True — non-empty list

Sequence Types

list — Lists (Mutable, Ordered)

fruits = ["apple", "banana", "cherry"]
mixed = [1, "hello", 3.14, True, None]  # Mixed types allowed

fruits.append("date")
fruits.insert(1, "blueberry")
fruits.remove("banana")
popped = fruits.pop()

# Slicing
print(fruits[0:2])    # ['apple', 'blueberry']
print(fruits[::-1])   # Reversed copy

# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]

# Sorting
numbers = [3, 1, 4, 1, 5]
numbers.sort()                        # In-place
sorted_list = sorted(numbers, reverse=True)  # New list

tuple — Tuples (Immutable, Ordered)

coordinates = (10, 20)
single = (42,)  # Trailing comma required for single-item tuple

# Tuple unpacking
x, y = coordinates
print(x, y)  # 10 20

# When to use tuples: fixed collections, dict keys, return values
# Named tuples for readability
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p.x, p.y)  # 3 4

range — Range Objects

print(list(range(5)))       # [0, 1, 2, 3, 4]
print(list(range(2, 8)))    # [2, 3, 4, 5, 6, 7]
print(list(range(0, 10, 2))) # [0, 2, 4, 6, 8]

r = range(1_000_000)
print(999_999 in r)  # True — O(1) membership test, no list in memory

Mapping Type

dict — Dictionaries (Mutable, Key-Value Pairs)

person = {"name": "Alice", "age": 30}

person["email"] = "a@b.com"     # Add
person["age"] = 31              # Update
del person["email"]             # Delete

print(person.get("phone", "N/A"))  # Default if key missing

# Dict comprehension
squares = {x: x**2 for x in range(6)}

# Merging (3.9+)
defaults = {"color": "blue", "size": "medium"}
custom = {"color": "red"}
merged = defaults | custom  # {'color': 'red', 'size': 'medium'}

# Iteration
for key, value in person.items():
    print(f"{key}: {value}")

Set Types

set and frozenset

colors = {"red", "green", "blue"}  # Duplicates removed automatically
empty_set = set()  # Not {} — that creates an empty dict!

colors.add("yellow")
colors.discard("red")  # No error if missing

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)   # Union:        {1, 2, 3, 4, 5, 6}
print(a & b)   # Intersection: {3, 4}
print(a - b)   # Difference:   {1, 2}
print(a ^ b)   # Sym diff:     {1, 2, 5, 6}

fs = frozenset([1, 2, 3])  # Immutable set — can be dict key or set element

Binary Types

bytes, bytearray, memoryview

# bytes — immutable
b = b"Hello"
print(b[0])  # 72 (ASCII for 'H')

# bytearray — mutable
ba = bytearray(b"Hello")
ba[0] = 74  # Change to 'J'
print(ba)    # b'Jello'

# Encoding/decoding
text = "Hello, 世界"
encoded = text.encode("utf-8")
decoded = encoded.decode("utf-8")
print(decoded)  # Hello, 世界

# memoryview — zero-copy access to binary data
data = bytearray(b"Hello, World!")
mv = memoryview(data)
mv[0] = 74
print(data)  # bytearray(b'Jello, World!')

Built-In Functions for Types

type() — Get the Type of an Object

print(type(42))          # <class 'int'>
print(type(3.14))        # <class 'float'>
print(type("hello"))     # <class 'str'>
print(type(True))        # <class 'bool'>
print(type(None))        # <class 'NoneType'>
print(type([1, 2]))      # <class 'list'>
print(type((1, 2)))      # <class 'tuple'>
print(type({1: 2}))      # <class 'dict'>
print(type({1, 2}))      # <class 'set'>
print(type(b"hello"))    # <class 'bytes'>

# type() with three arguments creates a new type dynamically
MyType = type('MyType', (object,), {'value': 42})
obj = MyType()
print(obj.value)  # 42
print(type(obj))  # <class '__main__.MyType'>

id() — Get Unique Identity of an Object

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(id(a))    # e.g., 140234866534400
print(id(b))    # e.g., 140234866534720 (different!)
print(id(c))    # e.g., 140234866534400 (same as a!)

print(a is c)   # True — same object
print(a is b)   # False — different objects with same value

# Small integer caching: Python caches -5 to 256
x = 256; y = 256
print(x is y)   # True — same cached object
x = 257; y = 257
print(x is y)   # False (usually) — different objects

isinstance() — Check Type (Including Inheritance)

print(isinstance(42, int))            # True
print(isinstance(3.14, float))        # True
print(isinstance("hello", str))       # True
print(isinstance(True, int))          # True — bool is subclass of int!
print(isinstance(True, bool))         # True

# Check against multiple types
print(isinstance(42, (int, float)))   # True
print(isinstance("hi", (int, float))) # False

# Works with inheritance
class MyInt(int):
    pass

num = MyInt(42)
print(isinstance(num, int))   # True — MyInt inherits from int
print(type(num) == int)       # False — type() doesn't recognize subclasses

hasattr() — Check if Object Has an Attribute

class Person:
    def __init__(self, name):
        self.name = name

p = Person("Alice")
print(hasattr(p, "name"))     # True
print(hasattr(p, "age"))      # False

# Works with any object
print(hasattr([1, 2], "append"))  # True — lists have append
print(hasattr(42, "append"))      # False — ints don't have append

getattr() — Get an Attribute Value

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 30)
print(getattr(p, "name"))          # Alice
print(getattr(p, "age"))           # 30
print(getattr(p, "email", "N/A"))  # N/A — default if not found

# getattr with strings (dynamic attribute access)
attr_name = "name"
print(getattr(p, attr_name))  # Alice

setattr() — Set an Attribute Value

class Person:
    def __init__(self, name):
        self.name = name

p = Person("Alice")
setattr(p, "age", 30)
print(p.age)  # 30

# Dynamic attribute setting
for key, value in [("city", "NYC"), ("job", "Engineer")]:
    setattr(p, key, value)
print(p.city)  # NYC
print(p.job)   # Engineer

delattr() — Delete an Attribute

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 30)
print(hasattr(p, "age"))   # True
delattr(p, "age")
print(hasattr(p, "age"))   # False

# Also works with del keyword
p.name = "Bob"
del p.name
print(hasattr(p, "name"))  # False

hash() — Get Hash Value of an Object

print(hash(42))           # 42
print(hash("hello"))      # some integer
print(hash((1, 2, 3)))    # some integer

# Mutable objects are not hashable
# hash([1, 2])            # TypeError: unhashable type: 'list'
# hash({1: "a"})          # TypeError: unhashable type: 'dict'

# Custom hash
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __hash__(self):
        return hash((self.x, self.y))

p1 = Point(1, 2)
p2 = Point(1, 2)
print(hash(p1) == hash(p2))  # True

repr() and str() — String Representations

# str() — readable string (for users)
print(str(42))        # "42"
print(str(3.14))      # "3.14"
print(str("hello"))   # "hello"
print(str([1, 2, 3])) # "[1, 2, 3]"

# repr() — unambiguous string (for developers/debugging)
print(repr(42))       # "42"
print(repr(3.14))     # "3.14"
print(repr("hello"))  # "'hello'"  — includes quotes!
print(repr([1, 2]))   # "[1, 2]"

# The difference matters most with strings
s = "Hello\nWorld"
print(str(s))    # Hello
                 # World
print(repr(s))   # 'Hello\nWorld'

# In f-strings
print(f"{s!r}")  # 'Hello\nWorld'
print(f"{s!s}")  # Hello

int(), float(), complex() — Numeric Constructors

# int() — convert to integer
print(int("42"))       # 42
print(int(3.9))        # 3 (truncates, does NOT round!)
print(int("1010", 2))  # 10 (binary string)
print(int("FF", 16))   # 255 (hex string)
print(int(True))       # 1
print(int(False))      # 0

# float() — convert to float
print(float("3.14"))   # 3.14
print(float(42))       # 42.0
print(float("inf"))    # inf
print(float("nan"))    # nan

# complex() — create complex numbers
print(complex(3, 4))       # (3+4j)
print(complex("1+2j"))     # (1+2j)
print(complex(5))           # (5+0j)

bool() — Convert to Boolean

# Falsy values
print(bool(0))         # False
print(bool(0.0))       # False
print(bool(0j))        # False
print(bool(""))        # False
print(bool([]))        # False
print(bool(()))        # False
print(bool({}))        # False
print(bool(set()))     # False
print(bool(None))      # False
print(bool(range(0)))  # False

# Truthy values
print(bool(1))         # True
print(bool(-1))        # True
print(bool("0"))       # True — non-empty string!
print(bool([0]))       # True — non-empty list!
print(bool(" "))       # True — whitespace is a character!

bin(), oct(), hex() — Base Conversions

# bin() — to binary string
print(bin(10))      # "0b1010"
print(bin(255))     # "0b11111111"

# oct() — to octal string
print(oct(10))      # "0o12"
print(oct(255))     # "0o377"

# hex() — to hexadecimal string
print(hex(10))      # "0xa"
print(hex(255))     # "0xff"

chr() and ord() — Character/Code Conversion

# chr() — integer to character
print(chr(65))       # "A"
print(chr(97))       # "a"
print(chr(8364))     # "€" (Euro sign)
print(chr(0x1F600))  # "😀" (emoji)

# ord() — character to integer
print(ord("A"))       # 65
print(ord("a"))       # 97
print(ord("€"))       # 8364
print(ord("😀"))      # 128512

abs(), round(), pow(), divmod() — Math Operations

# abs() — absolute value
print(abs(-5))       # 5
print(abs(3.14))     # 3.14
print(abs(-3+4j))    # 5.0 (magnitude of complex)

# round() — round to N decimal places
print(round(3.14159, 2))   # 3.14
print(round(3.145, 2))     # 3.14 or 3.15 (banker's rounding)
print(round(2.5))           # 2 (banker's rounding: rounds to even!)
print(round(3.5))           # 4

# pow() — power (with optional modulo)
print(pow(2, 10))           # 1024
print(pow(2, 10, 1000))     # 24 (2^10 mod 1000)
print(pow(2, -1))           # 0.5

# divmod() — returns (quotient, remainder)
print(divmod(17, 5))        # (3, 2) because 17 = 3*5 + 2
print(divmod(10.5, 3))      # (3.0, 1.5)

bytes(), bytearray(), memoryview() — Binary Constructors

# bytes() — create immutable bytes
print(bytes(5))                    # b'\x00\x00\x00\x00\x00' (5 zero bytes)
print(bytes([65, 66, 67]))         # b'ABC'
print(bytes("hello", "utf-8"))     # b'hello'

# bytearray() — create mutable bytearray
ba = bytearray(5)
print(ba)                          # bytearray(b'\x00\x00\x00\x00\x00')
ba[0] = 72
print(ba)                          # bytearray(b'H\x00\x00\x00\x00')

# memoryview() — zero-copy view
data = b"Hello, World!"
mv = memoryview(data)
print(mv[0])                       # 72 (H)
print(mv[0:5].tobytes())           # b'Hello'

format() — Format Values

# Positional
print(format(42, "d"))            # "42"
print(format(3.14159, ".2f"))     # "3.14"
print(format(255, "x"))           # "ff"
print(format(255, "08b"))         # "11111111"

# Using the built-in format() function
print(format(1234567, ","))       # "1,234,567"
print(format(0.856, "%"))         # "85.600000%"
print(format(0.856, ".1%"))       # "85.6%"

Type Checking and Conversion

Checking Types

x = 42
print(type(x))            # <class 'int'>
print(isinstance(x, int)) # True — preferred for most cases

# isinstance() recognizes subclasses; type() does not
class MyInt(int): pass
num = MyInt(42)
print(type(num) == int)      # False
print(isinstance(num, int))   # True

Type Conversion Rules

FunctionExampleResultNotes
int("42")"42" -> 42intint(3.9) -> 3 (truncates, not rounds)
float("3.14")"3.14" -> 3.14floatfloat(5) -> 5.0
str(42)42 -> "42"strstr(True) -> "True"
bool("")"" -> FalseboolSee truthy/falsy section
list("abc")"abc" -> ["a","b","c"]list
tuple([1,2])[1,2] -> (1,2)tuple
set([1,1,2])[1,1,2] -> {1, 2}setDuplicates removed
dict([("a",1)])[("a",1)] -> {"a":1}dictList of pairs

Common Gotchas

print(bool("False"))     # True — non-empty string!
print(bool("0"))         # True — non-empty string!
print(int("3.14"))       # ValueError — use int(float("3.14")) -> 3

# Safe conversion pattern
def safe_int(value, default=0):
    try:
        return int(value)
    except (ValueError, TypeError):
        return default

print(safe_int("hello"))  # 0
print(safe_int("42"))     # 42

Constants in Python

Python has several built-in constants:

ConstantValueDescription
TrueboolBoolean true
FalseboolBoolean false
NoneNoneTypeAbsence of value
__debug__boolTrue if not running with -O flag
print(True)       # True
print(False)      # False
print(None)       # None

# __debug__ — True in normal mode, False with python -O
print(__debug__)  # True

# None is the canonical "null" value
x = None
print(x is None)  # True
print(x == None)  # True, but use `is` instead!

Dynamic Typing Explained

Variables don't have fixed types — the objects they reference do. Reassignment changes which object the name refers to.

x = 42
x = "Hello"   # Now refers to a string
x = [1, 2, 3] # Now refers to a list

# is vs ==
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)  # True — same value
print(a is b)  # False — different objects
print(a is c)  # True — same object

# Small integer caching: Python caches -5 to 256
x = 256; y = 256
print(x is y)  # True — same cached object
x = 257; y = 257
print(x is y)  # False (usually) — different objects

Variable Naming Rules and Best Practices

ElementConventionExample
Variablessnake_caseuser_name, total_count
Functionssnake_caseget_user(), calculate_total()
ClassesPascalCaseUserProfile, DataProcessor
ConstantsUPPER_SNAKE_CASEMAX_RETRIES, PI
PrivateLeading underscore_internal_var
Modulesnake_casemy_module.py
# Valid names
_name = "valid"
user_name = "valid"
_count2 = "valid"
__double_underscore = "valid"

# Invalid names
# 2name = "invalid"    # Starts with number
# my-var = "invalid"   # Hyphen
# class = "invalid"    # Reserved keyword

import keyword
print(keyword.kwlist)  # All reserved keywords

Memory and Variables

Reference Counting

import sys
a = [1, 2, 3]
print(sys.getrefcount(a))  # 2 (variable + function call argument)

b = a
print(sys.getrefcount(a))  # 3

del b
print(sys.getrefcount(a))  # 2

Shallow vs Deep Copy

import copy

# Shallow copy — copies references, not objects
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0][0] = 99
print(original[0][0])  # 99 — nested list is shared!

# Deep copy — recursively copies everything
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0][0] = 99
print(original[0][0])  # 1 — original is safe

Common Mistakes

1. Mutable Default Arguments

# ❌ WRONG — default list shared across calls
def append_to(item, target=[]):
    target.append(item)
    return target

print(append_to(1))  # [1]
print(append_to(2))  # [1, 2] — unexpected!

# ✅ CORRECT
def append_to_fixed(item, target=None):
    if target is None:
        target = []
    target.append(item)
    return target

2. Floating-Point Comparison

# ❌ WRONG
if 0.1 + 0.2 == 0.3: print("Equal")

# ✅ CORRECT
import math
if math.isclose(0.1 + 0.2, 0.3): print("Equal")

3. Modifying a List While Iterating

# ❌ WRONG — skips elements
numbers = [1, 2, 3, 4, 5]
for n in numbers:
    if n % 2 == 0:
        numbers.remove(n)

# ✅ CORRECT — list comprehension
numbers = [n for n in numbers if n % 2 != 0]

4. Confusing = with ==

x = 5       # Assignment
if x == 5:  # Comparison — two equals signs
    print("x is 5")

5. Using is for Value Comparison

# ❌ WRONG — may fail due to interning behavior
x = "hello"
if x is "hello":
    print("match")

# ✅ CORRECT — use == for value comparison
if x == "hello":
    print("match")

# ✅ CORRECT use of `is` — checking for None
if x is None:
    print("no value")

Practice Exercises

Exercise 1: Type Explorer

Write a function that accepts a string and converts it to the most appropriate type.

Exercise: Create identify_and_convert(value) that returns True/False for "True"/"False", an int for integer strings, a float for float strings, and the original string otherwise.

See Solution

def identify_and_convert(value):
    if value.lower() == "true":
        return True
    if value.lower() == "false":
        return False
    try:
        return int(value)
    except ValueError:
        pass
    try:
        return float(value)
    except ValueError:
        pass
    return value

print(identify_and_convert("True"))   # True
print(identify_and_convert("42"))     # 42
print(identify_and_convert("3.14"))   # 3.14
print(identify_and_convert("hello"))  # "hello"

Exercise 2: Variable Swap and Conversion

Exercise: Given a = 10, b = "20", c = 30.0: swap a and c, convert b to int, build a list of all three, and print each element's type.

See Solution

a = 10
b = "20"
c = 30.0

a, c = c, a          # Swap
b = int(b)            # Convert
my_list = [a, b, c]

for i, item in enumerate(my_list):
    print(f"Element {i}: value={item}, type={type(item).__name__}")
# Element 0: value=30.0, type=float
# Element 1: value=20, type=int
# Element 2: value=10, type=int

Exercise 3: Conversion Chain

Exercise: Write conversion_chain(value) that converts any value to string, list, bool, and numeric (if possible), printing each step.

See Solution

def conversion_chain(value):
    print(f"Original: {value!r} ({type(value).__name__})")
    print(f"  String: {str(value)!r} (len {len(str(value))})")
    print(f"  List: {list(str(value))}")
    print(f"  Bool: {bool(value)}")
    try:
        print(f"  Int: {int(value)}")
    except (ValueError, TypeError):
        try:
            print(f"  Float: {float(value)}")
        except (ValueError, TypeError):
            print("  Numeric: not possible")
    print()

conversion_chain(42)
conversion_chain("Hello")
conversion_chain(None)

Exercise 4: Attribute Manipulation

Write a function that creates an object, sets attributes dynamically, and retrieves them.

def attribute_demo():
    class Dynamic:
        pass

    obj = Dynamic()

    # Set attributes dynamically
    attrs = {"name": "Alice", "age": 30, "city": "NYC"}
    for key, value in attrs.items():
        setattr(obj, key, value)

    # Retrieve and display
    for key in attrs:
        print(f"{key} = {getattr(obj, key)}")

    # Check existence
    print(f"Has 'name': {hasattr(obj, 'name')}")
    print(f"Has 'email': {hasattr(obj, 'email')}")

    # Delete an attribute
    delattr(obj, "city")
    print(f"Has 'city' after delete: {hasattr(obj, 'city')}")

attribute_demo()

Key Takeaways

  1. Python variables are names, not containers. They reference objects in memory. The object has a type; the variable does not.
  2. Dynamic typing makes Python flexible but requires discipline. Always ensure variables hold the expected type in larger programs.
  3. Use isinstance() over type() for type checking when you want to support inheritance hierarchies.
  4. Be mindful of mutability. Lists, dicts, and sets are shared by reference. Use .copy() or slicing for independent copies.
  5. Prefer math.isclose() for floating-point comparison instead of ==. IEEE 754 arithmetic introduces precision errors.
  6. Follow PEP 8 naming conventions. snake_case for variables/functions, PascalCase for classes, UPPER_SNAKE_CASE for constants.
  7. hash() is essential for dict keys and sets. Only immutable (hashable) objects can be dictionary keys or set elements.
  8. repr() gives unambiguous output; str() gives human-readable output. Use repr() in debugging.
  9. hasattr(), getattr(), setattr(), delattr() enable dynamic attribute manipulation without knowing the class structure at write time.
  10. Booleans are subclasses of integers. True == 1 and False == 0, so boolean arithmetic works: True + True == 2.

Premium Content

Python Variables and Data Types — The Complete Guide

Unlock this lesson and 900+ advanced tutorials with a Premium plan.

🎯End-to-end Projects
💼Interview Prep
📜Certificates
🤝Community Access

Already a member? Log in

Need Expert Python Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement