Skip to content
Back to skills

Python Bio Decorators

ASecurity

Python decorator patterns for bioinformatics: timing, validation, memoization, and stacking.

  • 4 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 27, 2026
data-aipython

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add Pavel-Kravchenko/Bioinformatics --skill python-bio-decorators --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Python Bio Decorators?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Python Bio Decorators
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pavel-kravchenko-python-bio-decorators/badge)](https://www.skillsdirectory.com/skills/pavel-kravchenko-python-bio-decorators)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: python-bio-decorators
description: "Python decorator patterns for bioinformatics: timing, validation, memoization, and stacking."
tool_type: python
primary_tool: Python
---

# Decorators for Bioinformatics

`@decorator` above `def f():` is exactly `f = decorator(f)` at definition time.

## Core Pattern

```python
import functools

def timer(func):
    @functools.wraps(func)   # preserves __name__, __doc__
    def wrapper(*args, **kwargs):
        import time
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"[timer] {func.__name__}: {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timer
def gc_content(seq: str) -> float:
    seq = seq.upper()
    return (seq.count('G') + seq.count('C')) / len(seq) * 100
```

## Decorator Factory (with arguments)

Three levels: factory → decorator → wrapper.

```python
def validate_sequence(valid_chars: str, seq_type: str = "DNA"):
    valid_set = set(valid_chars.upper())

    def decorator(func):
        @functools.wraps(func)
        def wrapper(seq, *args, **kwargs):
            invalid = set(seq.upper()) - valid_set
            if invalid:
                raise ValueError(
                    f"Invalid {seq_type} characters {invalid} in {func.__name__}()"
                )
            return func(seq, *args, **kwargs)
        return wrapper
    return decorator


@validate_sequence('ATGC', seq_type='DNA')
def complement(seq: str) -> str:
    return seq.upper().translate(str.maketrans('ATGC', 'TACG'))
```

## Memoization

```python
# Manual memoize (cache by args tuple)
def memoize(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    wrapper.cache = cache
    return wrapper

# Built-in (prefer this)
from functools import lru_cache

@lru_cache(maxsize=128)
def translate_codon(codon: str) -> str:
    table = {
        'TTT':'F','TTC':'F','TTA':'L','TTG':'L','TCT':'S','TCC':'S','TCA':'S','TCG':'S',
        'TAT':'Y','TAC':'Y','TAA':'*','TAG':'*','TGT':'C','TGC':'C','TGA':'*','TGG':'W',
        'CTT':'L','CTC':'L','CTA':'L','CTG':'L','CCT':'P','CCC':'P','CCA':'P','CCG':'P',
        'CAT':'H','CAC':'H','CAA':'Q','CAG':'Q','CGT':'R','CGC':'R','CGA':'R','CGG':'R',
        'ATT':'I','ATC':'I','ATA':'I','ATG':'M','ACT':'T','ACC':'T','ACA':'T','ACG':'T',
        'AAT':'N','AAC':'N','AAA':'K','AAG':'K','AGT':'S','AGC':'S','AGA':'R','AGG':'R',
        'GTT':'V','GTC':'V','GTA':'V','GTG':'V','GCT':'A','GCC':'A','GCA':'A','GCG':'A',
        'GAT':'D','GAC':'D','GAA':'E','GAG':'E','GGT':'G','GGC':'G','GGA':'G','GGG':'G',
    }
    return table.get(codon.upper(), 'X')
```

## Stacking Decorators

Applied bottom-up (closest to the function first).

```python
@timer                              # applied second: f = timer(validate_sequence(...)(f))
@validate_sequence('ATGC')         # applied first
def analyze(seq: str) -> dict:
    seq = seq.upper()
    return {'length': len(seq), 'gc': (seq.count('G') + seq.count('C')) / len(seq) * 100}
```

## Closure Pattern

```python
def make_motif_counter(motif: str):
    motif = motif.upper()
    def counter(sequence: str) -> int:
        return sequence.upper().count(motif)
    return counter

count_cpg = make_motif_counter("CG")   # counter "remembers" motif
count_cpg("GCGCGCATCG")               # 3
```

## Pitfalls

- **Always use `functools.wraps`**: without it, `func.__name__` becomes `'wrapper'`, breaking logging, `help()`, and stack traces.
- **Decorator factories need 3 levels**: `@validate_sequence('ATGC')` requires factory → decorator → wrapper; a 2-level decorator receives the *argument* as the function, causing a confusing `TypeError`.
- **Stacking order matters**: `@A @B def f` = `A(B(f))`; validation should be inner (runs first), timing outer (measures total including validation).
- **`lru_cache` requires hashable arguments**: lists, dicts, and numpy arrays cannot be cached; convert to `tuple` or `bytes` before passing.
- **`lru_cache` holds strong references**: cached results are never GC'd until cache is cleared; use `maxsize` and call `.cache_clear()` when needed.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…