enum, copy, pprint, logging and Other Essentials
Five more modules that appear in almost every real program: named constants, safe copying, readable output, proper logging, and identifiers and hashes.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
enum: named constants
from enum import Enum
class Status(Enum):
DRAFT = "draft"
PUBLISHED = "published"
ARCHIVED = "archived"
print(Status.DRAFT) # Status.DRAFT
print(Status.DRAFT.name) # DRAFT
print(Status.DRAFT.value) # draft
print(Status("draft")) # Status.DRAFT - look up by value
print(Status["DRAFT"]) # Status.DRAFT - look up by name
print(list(Status)) # every member, in definition order# Without an enum: any string is accepted, including a typo
def set_status(note, status):
note["status"] = status
set_status({}, "publised") # silently wrong
# With an enum: only the defined members exist
def set_status(note, status: Status):
note["status"] = status.value
# set_status({}, Status.PUBLISED) # AttributeError, immediatelyThat is the whole argument for enums: a misspelling becomes an error at the point you write it, rather than a wrong value discovered weeks later.
from enum import Enum, auto, IntEnum, Flag
class Colour(Enum):
RED = auto() # 1, 2, 3 assigned automatically
GREEN = auto()
BLUE = auto()
class Priority(IntEnum): # behaves as an int as well
LOW = 1
MEDIUM = 5
HIGH = 10
print(Priority.HIGH > Priority.LOW) # True
print(Priority.HIGH + 1) # 11
print(sorted(Priority, reverse=True))
class Permission(Flag): # combinable with bitwise operators
READ = 1
WRITE = 2
EXECUTE = 4
access = Permission.READ | Permission.WRITE
print(access) # Permission.READ|WRITE
print(Permission.READ in access) # True
print(Permission.EXECUTE in access) # Falsefrom enum import Enum
class Status(Enum):
DRAFT = "draft"
PUBLISHED = "published"
@property
def is_visible(self):
return self is Status.PUBLISHED
def __str__(self):
return self.value.title()
print(Status.PUBLISHED.is_visible) # True
print(str(Status.DRAFT)) # Draftcopy: shallow and deep
import copy
original = {"name": "report", "tags": ["draft", "q3"], "meta": {"pages": 12}}
alias = original # not a copy at all
shallow = copy.copy(original) # or original.copy()
deep = copy.deepcopy(original)
original["tags"].append("urgent")
original["meta"]["pages"] = 20
print(alias["tags"]) # ['draft', 'q3', 'urgent']
print(shallow["tags"]) # ['draft', 'q3', 'urgent'] <- inner list shared
print(deep["tags"]) # ['draft', 'q3'] <- fully independentshallow copy deep copy
outer dict ──► new dict outer dict ──► new dict
│ │ │ │
└──► [list] ◄──┘ └──► [list] └──► [new list]
(one shared object) (two separate objects)| Use | When |
|---|---|
| Assignment | You want another name for the same object |
copy.copy | Every value inside is immutable |
copy.deepcopy | There are nested mutable values you will change |
deepcopy is slow and it copies everything, including objects you may not want duplicated. Reach for it when you need it and not by default.
pprint: readable output
from pprint import pprint, pformat
data = {
"name": "Lumen Works",
"departments": [
{"name": "engineering", "staff": ["Meera", "Arun", "Sara"]},
{"name": "design", "staff": ["Ravi"]},
],
"founded": 2019,
}
print(data) # one long unreadable line
print("---")
pprint(data) # indented and wrapped
pprint(data, width=40, depth=2)
pprint(data, sort_dicts=False) # keep insertion order
text = pformat(data) # the same, returned as a string
print(len(text))For debugging nested structures, pprint is the difference between reading the output and squinting at it.
logging: better than print
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(name)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
logger.debug("detailed state, hidden at INFO level")
logger.info("service started")
logger.warning("disk usage at 85 percent")
logger.error("could not reach the database")
logger.critical("shutting down")2026-08-22 14:30:00 INFO __main__ service started
2026-08-22 14:30:00 WARNING __main__ disk usage at 85 percent
2026-08-22 14:30:00 ERROR __main__ could not reach the database
2026-08-22 14:30:00 CRITICAL __main__ shutting down| Level | Value | Use for |
|---|---|---|
DEBUG | 10 | Detail useful only while diagnosing |
INFO | 20 | Normal progress worth recording |
WARNING | 30 | Something unexpected, still working |
ERROR | 40 | An operation failed |
CRITICAL | 50 | The program cannot continue |
Why not print
- Levels: turn detail on or off without editing code.
- Destinations: console, file, or both, decided by configuration.
- Context: timestamp, module name and line number, automatically.
- Exceptions:
logger.exceptionrecords the full traceback.
import logging
logger = logging.getLogger(__name__)
try:
1 / 0
except ZeroDivisionError:
logger.exception("calculation failed") # message plus full traceback
# Pass arguments separately - formatting is skipped if the level is disabled
logger.info("user %s completed %d tasks", "meera", 5)
# not: logger.info(f"user {name} completed {count} tasks")import logging
logger = logging.getLogger("notesapp")
logger.setLevel(logging.DEBUG)
console = logging.StreamHandler()
console.setLevel(logging.WARNING) # only warnings on screen
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG) # everything to the file
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
console.setFormatter(formatter)
file_handler.setFormatter(formatter)
logger.addHandler(console)
logger.addHandler(file_handler)
logger.debug("only in the file")
logger.warning("in both")Calllogging.getLogger(__name__)in each module and configure logging once, in the program's entry point. A library should never callbasicConfig; that decision belongs to the application using it.
uuid, hashlib and textwrap
import uuid
print(uuid.uuid4()) # a random unique identifier
print(uuid.uuid4().hex) # without the dashes
print(str(uuid.uuid4())[:8]) # a short id, when collisions are tolerableimport hashlib
data = b"the quick brown fox"
print(hashlib.sha256(data).hexdigest())
print(hashlib.md5(data).hexdigest()) # for checksums only, never security
def file_checksum(path, algorithm="sha256"):
digest = hashlib.new(algorithm)
with open(path, "rb") as handle:
while chunk := handle.read(65536):
digest.update(chunk)
return digest.hexdigest()Hashing is not encryption; it is one way. For storing passwords, a plain SHA-256 is not enough - a deliberately slow algorithm is required. The security note covers this.
import textwrap
long_text = "Python is a high level language whose design emphasises readability above almost everything else."
print(textwrap.fill(long_text, width=40))
print()
print(textwrap.indent(textwrap.fill(long_text, 40), "> "))
print()
print(textwrap.shorten(long_text, width=50, placeholder=" ..."))
code = """
def f():
return 1
"""
print(textwrap.dedent(code)) # strips the common leading whitespaceCommon mistakes
- Comparing an enum member to its raw value:
Status.DRAFT == "draft"isFalsefor a plainEnum. - Using
copy.copyon nested data and being surprised by shared inner objects. - Using
printfor diagnostics in anything long lived. - Formatting a log message with an f-string, so the work happens even when the level is off.
- Calling
basicConfigfrom inside a library. - Using
md5or a baresha256for passwords.
Best practices
- Use an
Enumfor any fixed set of named options, andIntEnumwhen ordering matters. - Use
deepcopyonly when nested mutable data will be modified. - Use
pprintwhile debugging nested structures. - Use
loggingwithgetLogger(__name__)in every module, configured once at the entry point. - Pass log arguments separately rather than pre-formatting.
- Use
uuid4for identifiers andsecretsfor anything that must be unguessable.
Practice
- Define an enum for order states and write a function that only accepts its members.
- Demonstrate the difference between
copyanddeepcopyon a three level structure. - Configure logging so that
DEBUGgoes to a file andWARNINGgoes to the console. - Write a checksum function and prove two identical files produce the same digest.
- Use
textwrapto format a paragraph into a 60 character quoted block.
Conclusion
enum replaces loose strings with checked names, copy decides how much of a structure you duplicate, pprint makes nested data readable, and logging replaces print everywhere it matters. All four appear in more or less every real Python program.