Saturday, June 28, 2025

The Artwork of Writing Readable Python Features



Picture by Creator | Ideogram

 

When you’re studying this text, you’ve most likely been coding in Python for some time. Let’s discuss one thing that may degree up your coding sport: writing readable capabilities.

Give it some thought: we spend about 10x extra time studying code than writing it. So each time you create a operate that is clear and intuitive, you are saving each time and frustration for your self and your crew.

On this article, I am going to stroll you thru seven sensible strategies to remodel complicated code into clear, maintainable capabilities. We’ll have a look at before-and-after examples, with explanations of why the modifications matter. Let’s get began

 

1. Use Descriptive Perform and Parameter Names

 
Perform names needs to be verbs that clearly describe the motion being carried out. And parameter names needs to be descriptive as effectively.
 

Dangerous Instance

this operate, are you able to inform what it does?

def course of(d, t):
    return d * (1 + t/100)

 
The identify “course of” is obscure, and the single-letter parameters “d” and “t” give no indication of their objective. Is that this calculating a reduction? Making use of curiosity? It is not possible to know with out how the operate is used elsewhere within the code.
 

Good Instance

This model is instantly clear: we’re making use of a tax charge to a value.

def apply_tax_to_price(value, tax_rate):
    return value * (1 + tax_rate/100)

 
The operate identify tells us precisely what motion is being carried out, and the parameter names clearly point out what every worth represents. Even somebody unfamiliar with the code can perceive this operate at a look.

 

2. Restrict the Variety of Parameters

 
Features with many parameters are onerous to know and use appropriately. When you’ll want to cross a number of associated values, group them logically.
 

Dangerous Instance

This operate has 9 parameters:

def send_notification(user_id, e-mail, cellphone, message, topic, 
                     precedence, send_email, send_sms, attachment):
    # code goes right here...

 
When calling this operate, you’d want to recollect the proper order of all these parameters, which is error-prone. It is also not clear which parameters are required and that are optionally available.

While you see a operate name like send_notification(42, "consumer@instance.com", "+1234567890", "Good day", "Greeting", 2, True, False, None), it is onerous to know what every worth represents with out wanting up the operate definition.
 

Good Instance

This model reduces the parameter depend by grouping associated parameters into logical objects:

def send_notification(consumer, notification_config, message_content):
    """
    Ship a notification to a consumer based mostly on configuration settings.
    
    Parameters:
    - consumer: Consumer object with contact data
    - notification_config: NotificationConfig with supply preferences
    - message_content: MessageContent with topic, physique, and attachments
    """
    # code goes right here...

 
Now if you name send_notification(consumer, config, message), it is a lot clearer what every argument represents. This method additionally makes the operate extra versatile.

If you’ll want to add new notification choices sooner or later, you may add them to the NotificationConfig class with out altering the operate signature.

 

3. Write Clear and Useful Docstrings

 
docstring explains what the operate does, its inputs, outputs, and any unwanted side effects. Do not simply repeat the operate identify!
 

Dangerous Instance

This docstring is actually ineffective:

def validate_email(e-mail):
    """This operate validates e-mail."""
    # code goes right here...

 
It simply repeats what the operate identify already tells us with out offering any extra data.

We do not know what “validates” means precisely: does it test the format? Confirm the area exists? Contact the mail server? We additionally do not know what the operate returns or if it would elevate exceptions.
 

Good Instance

This docstring gives clear, helpful data:

def validate_email(e-mail: str) -> bool:
    """
    Verify if an e-mail deal with has legitimate format.
    
    Parameters:
    - e-mail: String containing the e-mail deal with to validate
    
    Returns:
    - True if the e-mail is legitimate, else False
    
    Observe:
    - This validation checks format solely, not if the deal with truly exists
    """
    # code goes right here...

 
Particularly, it:

  • Specifies what sort of validation is carried out: format checking
  • Paperwork what the operate expects as enter: a string
  • Explains what the operate returns: a boolean
  • Clarifies limitations: does not confirm the deal with exists

The kind annotations additionally add readability, exhibiting that the operate takes a string and returns a boolean.

 

4. Follow One Goal per Perform

 
Features ought to do one factor effectively. If you end up utilizing “and” to explain what a operate does, it is most likely doing an excessive amount of.
 

Dangerous Instance

I feel you’d agree. This operate is certainly attempting to do too many issues without delay:

def process_order(order):
    # Validate order
    # Replace stock
    # Cost buyer
    # Ship affirmation e-mail
    # Replace analytics

 
It is dealing with validation, stock administration, fee processing, communication, and analytics. That’s 5 separate issues in a single operate!

This makes the operate:

  • Tougher to check as a result of you’ll want to mock many dependencies
  • Tougher to take care of as modifications to anybody side have an effect on the entire operate
  • Much less reusable; effectively, you may’t use simply the validation logic elsewhere with out bringing every part else alongside

 

Good Instance

Break it down into single-purpose capabilities:

def process_order(order):
    """Course of a buyer order from validation by affirmation."""
    validated_order = validate_order(order)
    update_inventory(validated_order)
    payment_result = charge_customer(validated_order)
    if payment_result.is_successful:
        send_confirmation_email(validated_order, payment_result)
        update_order_analytics(validated_order)
    return OrderResult(validated_order, payment_result)

 

This model nonetheless handles the complete order processing circulate, but it surely delegates every particular activity to a devoted operate. The advantages are:

  • Every single-purpose operate may be examined in isolation
  • Adjustments to e-mail logic, for instance, solely require modifications to send_confirmation_email()

The principle operate reads nearly like pseudocode, making the general circulate straightforward to know.

 

5. Add Sort Annotations for Readability

 
Python’s kind hints make code extra self-documenting and assist catch errors earlier than runtime.
 

Dangerous Instance

This operate works, but it surely lacks readability in a number of methods:

def calculate_final_price(value, low cost):
    return value * (1 - low cost / 100)

 
What models is low cost in? Proportion factors or a decimal fraction? Might or not it’s damaging? What does the operate return precisely?

With out kind annotations, a brand new developer may cross incorrect values or misuse the return worth.
 

Good Instance

The kind annotations make it clear that each inputs and the output are floating-point numbers:

def calculate_final_price(value: float, discount_percentage: float) -> float:
    """
    Calculate ultimate value after making use of the low cost.
    
    Parameters:
    - value: Unique value of the merchandise
    - discount_percentage: Proportion low cost to use (0-100)
    
    Returns:
    - Discounted value
    """
    return value * (1 - discount_percentage / 100)

 

The parameter identify discount_percentage (reasonably than simply low cost) clarifies that the worth needs to be offered as a share (like 20 for 20%), not a decimal fraction (like 0.2). The docstring additional clarifies the anticipated vary (0-100).

 

6. Use Default Arguments and Key phrase Arguments Correctly

 
Default arguments make capabilities extra versatile, however they arrive with obligations.
 

Dangerous Instance

This operate has a number of points:

def create_report(information, include_charts=True, format="pdf", output_path="report.pdf"):
    # code goes right here...

 
The parameter format shadows a built-in Python operate. The hardcoded output_path means reviews will at all times overwrite one another by default.

And since all parameters may be handed positionally, calls like create_report(customer_data, False, 'xlsx') are allowed however unclear. What does that False check with?
 

Good Instance

Right here’s a a lot better model:

def create_report(
    information: Record[Dict[str, Any]],
    *,  # Power key phrase arguments for readability
    include_charts: bool = True,
    format_type: Literal['pdf', 'html', 'xlsx'] = 'pdf',
    output_path: Optionally available[str] = None
) -> str:
    """
    Generate a report from the offered information.
    
    Parameters:
    - information: Record of data to incorporate within the report
    - include_charts: Whether or not to generate charts from the info
    - format_type: Output format of the report
    - output_path: The place to save lots of the report (if None, makes use of a default location)
    
    Returns:
    - Path to the generated report
    """
    if output_path is None:
        timestamp = datetime.now().strftime('%Ypercentmpercentd_percentHpercentMpercentS')
        output_path = f"reviews/report_{timestamp}.{format_type}"
    
    # code goes right here...
    
    return output_path

 
This improved model has a number of benefits:

  • The * after the primary parameter forces key phrase arguments for the optionally available parameters, making calls like create_report(information, include_charts=False) extra readable
  • Renamed format to format_type to keep away from shadowing the built-in
  • Utilizing None because the default for output_path with dynamic technology prevents unintentional overwrites
  • The kind annotation Literal['pdf', 'html', 'xlsx'] paperwork the allowed values for format_type

 

7. Use Guard Clauses for Early Returns

 
Use guard clauses to deal with edge instances early reasonably than nesting conditionals deeply.
 

Dangerous Instance

This operate makes use of deeply nested conditionals, which creates the “pyramid of doom” or “arrow code” sample:

def process_payment(fee):
    if fee.is_valid:
        if fee.quantity > 0:
            if not fee.is_duplicate:
                # Precise fee processing logic (buried in conditionals)
                return success_result
            else:
                return DuplicatePaymentError()
        else:
            return InvalidAmountError()
    else:
        return InvalidPaymentError()

 
The precise enterprise logic (the glad path) is buried deep within the nesting, making it onerous to determine at a look. Every new situation we’d must test would create one other degree of nesting, making the code more and more unreadable.
 

Good Instance

This model makes use of guard clauses to deal with all of the error instances up entrance, then proceeds with the principle enterprise logic with none nesting.

def process_payment(fee: Fee) -> PaymentResult:
    """
    Course of a fee transaction.
    
    Returns a PaymentResult or raises acceptable exceptions.
    """
    # Guard clauses for validation
    if not fee.is_valid:
        elevate InvalidPaymentError("Fee validation failed")
        
    if fee.quantity <= 0:
        elevate InvalidAmountError(f"Invalid fee quantity: {fee.quantity}")
        
    if fee.is_duplicate:
        elevate DuplicatePaymentError(f"Duplicate fee ID: {fee.id}")
    
    # Predominant logic - now on the prime degree with no nesting
    transaction_id = submit_to_payment_processor(fee)
    update_payment_records(fee, transaction_id)
    notify_payment_success(fee)
    
    return PaymentResult(
        success=True,
        transaction_id=transaction_id,
        processed_at=datetime.now()
    )

 
Every validation is a separate, clear test that fails quick if one thing is incorrect. The trail for profitable fee processing is now on the prime degree of the operate, making it a lot simpler to comply with.

This method can also be simpler to increase: including new validations simply means including new guard clauses, not rising the nesting depth.

 

Wrapping Up

 
By spending time in writing clear, readable capabilities, you are creating code that:

  • Has fewer bugs
  • Is less complicated to check
  • Might be maintained by different builders (or your self in 6 months)
  • Serves as its personal documentation
  • Is extra prone to be reused reasonably than rewritten

Keep in mind, code is learn way more usually than it’s written. And I actually hope you discovered just a few key takeaways from this text.

 
 

Bala Priya C is a developer and technical author from India. She likes working on the intersection of math, programming, information science, and content material creation. Her areas of curiosity and experience embody DevOps, information science, and pure language processing. She enjoys studying, writing, coding, and low! At the moment, she’s engaged on studying and sharing her information with the developer neighborhood by authoring tutorials, how-to guides, opinion items, and extra. Bala additionally creates partaking useful resource overviews and coding tutorials.



Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles

PHP Code Snippets Powered By : XYZScripts.com