Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
147225 stories
·
33 followers

How to Use the Factory Pattern in Python - A Practical Guide

1 Share

Design patterns are proven solutions to common problems in software development. If you've ever found yourself writing repetitive object creation code or struggling to manage different types of objects, the factory pattern might be exactly what you need.

In this tutorial, you'll learn what the factory pattern is, why it's useful, and how to implement it in Python. We'll build practical examples that show you when and how to use this pattern in real-world applications.

You can find the code on GitHub.

Prerequisites

Before we start, make sure you have:

  • Python 3.10 or higher installed

  • Understanding of Python classes and methods

  • Familiarity with object-oriented programming (OOP) concepts

Let’s get started!

Table of Contents

What Is the Factory Pattern?

The factory pattern is a creational design pattern that provides an interface for creating objects without specifying their exact classes. Instead of calling a constructor directly, you call a factory method that decides which class to instantiate.

Think of it like ordering food at a restaurant. You don't go into the kitchen and make the food yourself. You tell the waiter what you want, and the kitchen (the factory) creates it for you. You get your meal without worrying about the recipe or cooking process.

The factory pattern is useful when:

  • You have multiple related classes and need to decide which one to instantiate at runtime

  • Object creation logic is complex and you want to encapsulate it

  • You want to make your code more maintainable and testable

A Simple Factory Example

Let's start with a basic example. Say you're building a notification system that can send messages via email, SMS, or push notifications.

Without a factory, you might write code like this everywhere in your application:

# Bad approach - tight coupling
if notification_type == "email":
    notifier = EmailNotifier()
elif notification_type == "sms":
    notifier = SMSNotifier()
elif notification_type == "push":
    notifier = PushNotifier()

This gets messy quickly. Let's use a factory instead:

class EmailNotifier:
    def send(self, message):
        return f"Sending email: {message}"

class SMSNotifier:
    def send(self, message):
        return f"Sending SMS: {message}"

class PushNotifier:
    def send(self, message):
        return f"Sending push notification: {message}"

class NotificationFactory:
    @staticmethod
    def create_notifier(notifier_type):
        if notifier_type == "email":
            return EmailNotifier()
        elif notifier_type == "sms":
            return SMSNotifier()
        elif notifier_type == "push":
            return PushNotifier()
        else:
            raise ValueError(f"Unknown notifier type: {notifier_type}")

In this code, we define three notifier classes, each with a send method.

Note: In a real application, these would have different implementations for sending notifications.

The NotificationFactory class has a static method called create_notifier. This is our factory method. It takes a string parameter and returns the appropriate notifier object.

The @staticmethod decorator means we can call this method without creating an instance of the factory. We just use NotificationFactory.create_notifier().

# Using the factory
notifier = NotificationFactory.create_notifier("email")
result = notifier.send("Hello, World!")

Now, whenever we need a notifier, we call the factory instead of instantiating classes directly. This centralizes our object creation logic in one place.

Using a Dictionary for Cleaner Code

The if-elif chain in our factory can get unwieldy as we add more notifier types. Let's refactor using a dictionary:

class NotificationFactory:
    notifier_types = {
        "email": EmailNotifier,
        "sms": SMSNotifier,
        "push": PushNotifier
    }

    @staticmethod
    def create_notifier(notifier_type):
        notifier_class = NotificationFactory.notifier_types.get(notifier_type)
        if notifier_class:
            return notifier_class()
        else:
            raise ValueError(f"Unknown notifier type: {notifier_type}")

This approach is much cleaner. We store a dictionary that maps strings to class objects and not instances. The keys are notifier type names, and the values are the actual class references.

The get method retrieves the class from the dictionary. If the key doesn't exist, it returns None. We then instantiate the class by calling it with parentheses: notifier_class().

# Test with different types
email_notifier = NotificationFactory.create_notifier("email")
sms_notifier = NotificationFactory.create_notifier("sms")
push_notifier = NotificationFactory.create_notifier("push")

This makes adding new notifier types easier. You just add another entry to the dictionary.

Factory Pattern with Parameters

Real-world objects often need configuration. Let's extend our factory to handle notifiers that require initialization parameters.

We'll create a document generator that produces different file formats with custom settings:

class PDFDocument:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.format = "PDF"

    def generate(self):
        return f"Generating {self.format}: '{self.title}' by {self.author}"

class WordDocument:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.format = "DOCX"

    def generate(self):
        return f"Generating {self.format}: '{self.title}' by {self.author}"

class MarkdownDocument:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.format = "MD"

    def generate(self):
        return f"Generating {self.format}: '{self.title}' by {self.author}"

class DocumentFactory:
    document_types = {
        "pdf": PDFDocument,
        "word": WordDocument,
        "markdown": MarkdownDocument
    }

    @staticmethod
    def create_document(doc_type, title, author):
        document_class = DocumentFactory.document_types.get(doc_type)
        if document_class:
            return document_class(title, author)
        else:
            raise ValueError(f"Unknown document type: {doc_type}")

The key difference here is that our factory method now accepts additional parameters.

The create_document method takes doc_type, title, and author as arguments. When we instantiate the class, we pass the title and author to the create_document constructor: document_class(title, author).

# Create different documents with parameters
pdf = DocumentFactory.create_document("pdf", "Python Guide", "Tutorial Team")
word = DocumentFactory.create_document("word", "Meeting Notes", "Grace Dev")
markdown = DocumentFactory.create_document("markdown", "README", "DevTeam")

This lets us create fully configured objects through the factory while keeping the creation logic centralized.

Using Abstract Base Classes

To make our factory more robust, we can use Python's Abstract Base Classes (ABC) to enforce a common interface.

Let's create a super simple payment processing system:

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

    @abstractmethod
    def refund(self, transaction_id):
        pass

class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount):
        return f"Processing ${amount} via Credit Card"

    def refund(self, transaction_id):
        return f"Refunding credit card transaction {transaction_id}"

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount):
        return f"Processing ${amount} via PayPal"

    def refund(self, transaction_id):
        return f"Refunding PayPal transaction {transaction_id}"

class PaymentFactory:
    processors = {
        "credit_card": CreditCardProcessor,
        "paypal": PayPalProcessor
    }

    @staticmethod
    def create_processor(processor_type):
        processor_class = PaymentFactory.processors.get(processor_type)
        if processor_class:
            return processor_class()
        else:
            raise ValueError(f"Unknown processor type: {processor_type}")

Here, the PaymentProcessor class defines an interface that all payment processors must implement. The @abstractmethod decorator marks methods that subclasses must override.

You cannot instantiate PaymentProcessor directly. It only serves as a blueprint. All concrete processors (CreditCardProcessor, PayPalProcessor) must implement both process_payment and refund methods. If they don't, Python will raise an error. This guarantees that any object created by our factory will have the expected methods, making our code more predictable and safer.

You can use the factory like so:

processor = PaymentFactory.create_processor("paypal")

A More Helpful Example: Database Connection Factory

Let's build something practical: a factory that creates different database connection objects based on configuration.

class MySQLConnection:
    def __init__(self, host, database):
        self.host = host
        self.database = database
        self.connection_type = "MySQL"

    def connect(self):
        return f"Connected to {self.connection_type} at {self.host}/{self.database}"

    def execute_query(self, query):
        return f"Executing on MySQL: {query}"

class PostgreSQLConnection:
    def __init__(self, host, database):
        self.host = host
        self.database = database
        self.connection_type = "PostgreSQL"

    def connect(self):
        return f"Connected to {self.connection_type} at {self.host}/{self.database}"

    def execute_query(self, query):
        return f"Executing on PostgreSQL: {query}"

class SQLiteConnection:
    def __init__(self, host, database):
        self.host = host
        self.database = database
        self.connection_type = "SQLite"

    def connect(self):
        return f"Connected to {self.connection_type} at {self.host}/{self.database}"

    def execute_query(self, query):
        return f"Executing on SQLite: {query}"

class DatabaseFactory:
    db_types = {
        "mysql": MySQLConnection,
        "postgresql": PostgreSQLConnection,
        "sqlite": SQLiteConnection
    }

    @staticmethod
    def create_connection(db_type, host, database):
        db_class = DatabaseFactory.db_types.get(db_type)
        if db_class:
            return db_class(host, database)
        else:
            raise ValueError(f"Unknown database type: {db_type}")

    @staticmethod
    def create_from_config(config):
        """Create a database connection from a configuration dictionary"""
        return DatabaseFactory.create_connection(
            config["type"],
            config["host"],
            config["database"]
        )

This example shows a more realistic use case. We have multiple database connection classes, each with the same interface but different implementations.

The factory has two creation methods: create_connection for direct parameters and create_from_config for configuration dictionaries.

The create_from_config method is particularly useful because it lets you load database settings from a config file or environment variables and create the appropriate connection object.

This pattern makes it easy to switch between different databases without changing your application code. You just change the configuration as shown:

# Use with direct parameters
db1 = DatabaseFactory.create_connection("mysql", "localhost", "myapp_db")
print(db1.connect())
print(db1.execute_query("SELECT * FROM users"))

# Use with configuration dictionary
config = {
    "type": "postgresql",
    "host": "db.example.com",
    "database": "production_db"
}
db2 = DatabaseFactory.create_from_config(config)

When to Use the Factory Pattern

The factory pattern is useful when you have the following:

  1. Multiple related classes: When you have several classes that share a common interface but have different implementations (like the payment processors or database connections we had in the examples).

  2. Runtime decisions: When you need to decide which class to instantiate based on user input, configuration, or other runtime conditions.

  3. Complex object creation: When creating an object involves multiple steps or requires specific logic that you want to encapsulate.

However, don't use the factory pattern when:

  • You only have one or two simple classes

  • Object creation is straightforward with no special logic

  • The added abstraction makes your code harder to understand

Wrapping Up

The factory pattern is a useful tool for managing object creation in Python. It helps you write cleaner, more maintainable code by centralizing creation logic and decoupling your code from specific class implementations. We've covered:

  • Basic factory implementation with simple examples

  • Using dictionaries for cleaner factory code

  • Passing parameters to factory-created objects

  • Using abstract base classes for cleaner interfaces

The key takeaway is this: whenever you find yourself writing repetitive object creation code or need to decide which class to instantiate at runtime, consider using the factory pattern. Start simple and add complexity only when needed. The basic dictionary-based factory is often all you need for most applications.

Happy coding!



Read the whole story
alvinashcraft
20 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How to Save Multiple Drafts in Git: A Guide to Using Stash

1 Share

Writing code can be similar to writing tutorials. In both cases, you’ll typically need to create and work on multiple drafts before reaching the final version.

In an ideal setting, you would write code for a feature, add that code to the staging area, and then commit it before going to the next part. This helps keep your commit history clean. (Don't worry if you don't have a clean history. Many of us don’t.)

But now, imagine a scenario where you have multiple features to build. You've committed the first. You've started the third, but then you found out you needed to build the second one first, because the third depends on it. It might seem like you have to go back in time and build out that second feature without mixing in the code changes for the third, and without deleting the code changes for the third.

So how do you do that?

In this article, you’re going to learn about Git stash, what it is, and the basic commands you need to be able to use it.

What We’ll Cover:

To understand and get the most out of this tutorial, you’ll need to have a basic understanding of Git.

What is Git Stash?

Git stash provides storage (in the form of a stack) where you can store changes to your code. It lets you keep these changes separate from the current working directory until you're ready to apply them.

git stash is a relatively simple command, and has a few variations like:

  • git stash push

  • git stash pop

  • git stash apply

  • git stash drop

  • git stash clear

  • git stash show

  • git stash list

How to Use Git Stash

Pushing Code Changes to the Stash

To push uncommitted changes from the working directory to the stash, you can use git stash push. When you do that, the changes disappear from the working directory and are saved in the stash. The working directory is then set back to the last commit (which, in the example below, is the Feature one).

git stash push

git stash push screenshot

You can also write the git stash push command as just git stash – it means the same thing and performs the same way. You can also add a message to it with the -m or --message flag:

git stash push -m "Feature two I was working on"

You can use the -u flag or --include-untracked to include untracked changes in the stash. This way, the changes not yet tracked by Git can be included in the stash.

git stash push -u
git stash push --include-untracked

On the other hand, you can decide to push only staged changes to the stash with the -s or --staged flag:

git stash push -s

You can also suppress feedback messages with the -q or --quiet flag:

git stash push -q

When you're done with your edits on the current working directory, you can commit and push without the older changes bleeding into it. They're safely stowed away in the stash.

Anytime you use the git stash command, you add a stash to the stash list. The stashes are identified by their index numbers.

Applying Changes from the Stash to the Working Directory

Let’s say you had a quick fix to do. You’re done committing that, and you want to continue with what you were working on. You can restore the changes from the stash to continue working on them.

You can do this by using the git stash apply command or the git stash pop command:

git stash apply

git stash apply screenshot

git stash apply applies the latest changes from the stash to the working directory, but doesn’t delete the code from the stash. You can select a particular entry to apply by index number:

git stash apply --index stash@{1}
# You'd need to use quotes "stash@{1}" if you're writing this in PowerShell

git stash list screenshot

git stash pop, on the other hand, applies the latest changes from the stash, then deletes them from the stash. Basically, popping from a stack. You can select a particular stash entry to pop by index number.

git stash pop
git stash pop --index stash@{1}
# You'd need to use quotes "stash@{1}" if you're writing this in PowerShell

Listing the Items in the Stash List

You can use git stash list to list out the stashes in the stash list. It’s arranged by index number (like {0}, {1}, and so on). Any time you do a git stash push, it adds a stash to the stash list.

git stash list
stash@{0}: WIP on master: b2a2709 Feature one
stash@{1}: WIP on master: b2a2709 Feature one

git stash clear screenshot

Removing Items from the Stash

You can use git stash clear to clear the stash list. But if you just want to drop a particular entry from the stash list, you can use git stash drop and specify the entry you want to drop by the index number. This doesn’t apply its changes to the working directory.

git stash clear
git stash drop stash@{0}
# You'd need to use quotes "stash@{0}" if you're writing this in PowerShell

git stash list screenshot

Creating a New Branch from Stash

You can also create a new branch from a stash using git stash branch. The syntax is git stash branch <branchname> [<stash>].

git stash branch premium-branch stash@{0}
# You'd need to use quotes "stash@{0}" if you're writing this in PowerShell

git stash branch screenshot

If you don’t add the stash index, it will just use the last stash.

git stash branch premium-branch

Showing the Changes in the Stash

You can use git stash show to show the changes you’ve made in your stashed changes.

C:\file-path\git_stash_example> git stash show
 text.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

git stash show screenshot

Conclusion

Git stash is one of those quiet tools that becomes indispensable once your workflow starts to get messy. It allows you to shelve unfinished ideas, switch context without panic, and keep your commits clean. With it, you can safely carry out urgent fixes, and juggle dependent features without muddling up your commit history.

If you enjoyed this article, share it with others. You can also reach me on LinkedIn or X.



Read the whole story
alvinashcraft
28 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Building a TX Text Control Project with GitHub Actions and the Text Control NuGet Feed

1 Share
This tutorial provides a step-by-step guide to setting up a clean, reproducible environment using GitHub Actions. It starts with a brand-new project and ends with a fully automated build and test pipeline. The tutorial uses the private Text Control NuGet feed, available to customers with an active maintenance agreement.

Read the whole story
alvinashcraft
38 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

ACID in Practice for .NET: Isolation Levels, Anomalies, and Transaction Pitfalls

1 Share

## 1 Beyond the Acronym: Why ACID Is Not a Silver Bullet

ACID is often described as a guarantee, something that magically keeps data correct as long as you “use transactions.” Senior developers know that production systems do not behave that way. ACID defines constraints, not outcomes, and those constraints interact with real workloads, concurrency, and infrastructure in ways that are easy to misunderstand.

Atomicity, Consistency, and Durability usually behave the way developers expect. Atomicity ensures all-or-nothing execution within a transaction boundary. Consistency relies mostly on schema constraints, foreign keys, and application logic. Durability is largely solved by modern databases with write-ahead logging and replicated storage. These properties matter, but they are rarely where production systems break.

Isolation is different. Isolation is subtle, configurable, and full of trade-offs. And in .NET systems backed by SQL Server, isolation is the property most likely to violate your assumptions even when everything appears “correct.”

Read the whole story
alvinashcraft
44 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Hands on with New Multi-Agent Orchestration in VS Code

1 Share
A proof of concept shows how multi-agent orchestration in Visual Studio Code 1.109 can turn a fragile, one-pass AI workflow into a more reliable, auditable process by breaking long tasks into smaller, verifiable steps.
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

What should I do if a wait call reports WAIT_ABANDONED?

2 Shares

If you call a wait function like Wait­For­Single­Object and receive the code WAIT_ABANDONED, what does it mean and what should you do?

The documentation says that WAIT_ABANDONED means that you successfully claimed a mutex, but the thread that previously owned the mutex failed to release the mutex before it exited. This could be an oversight because the code encountered a code path that forgot to release the mutex. Or it could be because the thread crashed before it could release the mutex.

The documentation also suggests that “If the mutex was protecting persistent state information, you should check it for consistency.” This is to handle the second case: The thread crashes before it can release the mutex. If the purpose of the mutex was to prevent other threads from accessing the data while it is in an inconsistent state, then the fact that the thread crashed while holding the mutex means that the data might still be in that inconsistent state.

Now, maybe you have no way to check whether the data is in an inconsistent state or have no way to repair it if such an inconsistent state is discovered. (Most people don’t bother to design their data structures with rollback or transactions, because the point of the mutex was to avoid having to write that fancy code in the first place!) In that case, you really have only two choices.

One option is to just cover your ears and pretend you didn’t hear anything. Just continue operating normally and hope that any latent corruption is not going to cause major problems.

Another option is to give up and abandon the operation. However, if that’s your choice, you have to give up properly.

The abandoned state is not sticky; is reported only to the first person to wait for the mutex after it was abandoned. Subsequent waits succeed normally. Therefore, if you decide, “Oh it’s corrupted, I’m not touching it,” and release the mutex and walk away, then the next person to wait for the mutex will receive a normal successful wait, and they will dive in, unaware that the data structures are corrupted!

One solution is to add a flag inside your data that says “Possibly corrupted.” The code that detects the WAIT_ABANDONED can set that flag, and everybody who acquires the mutex can check the flag to decide if they want to take a chance by operating on corrupted data.

I’m not saying that you have to do it that way, but it’s a choice you’re making. In for a penny, in for a pound.

In summary, here are some options when you encounter an abandoned mutex:

  • Try to fix the problem.
  • Ignore the problem.
  • Give up and create a warning to others.
  • Give up and make everybody else think that everything is fine.

The final choice doesn’t make sense, because if you’re going to make everybody else think that everything is fine, then that’s the same as having everybody else simply ignore the problem. In which case, you may as well ignore the problem too!

Related reading: Understanding the consequences of WAIT_ABANDONED.

Bonus chatter: Don’t forget that if you get WAIT_ABANDONED, the mutex is owned by you, so make sure to release it.

The post What should I do if a wait call reports <CODE>WAIT_<WBR>ABANDONED</CODE>? appeared first on The Old New Thing.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories