home.social

#dailypythontip — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #dailypythontip, aggregated by home.social.

fetched live
  1. Python Tip #231 (of 365):

    Use class methods as alternate constructors.

    >>> s = Square.from_area(100)
    >>> s.length
    10.0

    The classmethod decorator makes a method that's meant to be called on the class itself (not on an instance):

    import math

    class Square:
    def __init__(self, length, color=None):
    self.length = length
    self.color = color
    @classmethod
    def from_area(cls, area, color=None):
    return cls(math.sqrt(area), color)

    🧵 (1/4)

    #Python #DailyPythonTip

  2. Python Tip #230 (of 365):

    Use a single underscore prefix for internal attributes.

    This _digits attribute is private... but only by convention:

    import re

    class PhoneNumber:
    def __init__(self, number):
    self._digits = re.sub(r"\D", "", number)
    ...

    This (not fully implemented) class includes str() and an area_code attribute as its public interface:

    >>> wh = PhoneNumber("(202) 456-1414")
    >>> print(wh)
    202-456-1414
    >>> wh.area_code
    '202'

    🧵 (1/3)

    #Python #DailyPythonTip

  3. Python Tip #229 (of 365):

    Don't overuse classes.

    Writing Python code does NOT require writing classes.

    Unlike many programming languages, you can accomplish quite a bit in Python without ever making a class.

    The most common reason to write a class is that a framework REQUIRES one (like Django's Model classes).

    The best reason to write your OWN class: you have data and functionality that clearly belong together.

    🧵 (1/3)

    #Python #DailyPythonTip

  4. Python Tip #228 (of 365):

    Don't catch NameError exceptions. 🧵

    This is an issue:

    try:
    start, end = parse_date(row["start"]), parse_date(row["end"])
    except (ValueError, TypeError, KeyError, NameError):
    print(f"Invalid date on line {n}", file=sys.stderr)
    continue

    A NameError is raised when a variable isn't defined... which usually means a variable name was MISSPELLED.

    🧵 (1/3)

    #Python #DailyPythonTip

  5. Python Tip #227 (of 365):

    Don't rely on clever implementation details. 🧵

    In CPython, two 3's are identical:

    >>> x = 3
    >>> y = 3
    >>> x is y
    True

    But two 300's are not always identical:

    >>> x = 300
    >>> y = 300
    >>> x is y
    False

    Don't rely on this. Compare numbers with ==.

    In CPython, integers from -5 to 256 are created on launch, so every 200 is identical to every other 200.

    Bigger numbers are only identical sometimes:

    >>> x, y = 300, 300
    >>> x is y
    True

    🧵 (1/2)

    #Python #DailyPythonTip

  6. Python Tip #226 (of 365):

    Don't assign to global variables from within a function.

    You CAN do this in Python. But you really shouldn't.

    Yesterday's tip noted that assignments within a function NORMALLY make local variables.

    But they don't always...

    Python's "global" statement is the exception:

    >>> phrase = "Hi world"
    >>> def set_phrase(name):
    ... global phrase
    ... phrase = f"Hello {name}"
    ...
    >>> set_phrase("Trey")
    >>> phrase
    'Hello Trey'

    🧵 (1/2)

    #Python #DailyPythonTip

  7. Python Tip #225 (of 365):

    Avoid creating local variables that share a name with a global variable.

    This is called SHADOWING, and it can make for confusing code.

    Assigning to a variable inside a function normally makes a LOCAL variable:

    >>> phrase = "Hi!"
    >>> def set_phrase(name):
    ... phrase = f"Hello {name}"
    >>> set_phrase("Trey")
    >>> phrase
    'Hi!'

    The global phrase never changed. The function makes a LOCAL phrase that disappears when it returns.

    🧵 (1/3)

    #Python #DailyPythonTip

  8. Python Tip #224 (of 365):

    Constants that are specific to a class often belong ON that class.

    class Circle:
    max_radius = 100
    def __init__(self, radius):
    if radius > self.max_radius:
    raise ValueError(f"Radius can't exceed {self.max_radius}")
    self.radius = radius

    But, why make max_radius a class attribute instead of a module-level MAX_RADIUS constant...?

    🧵 (1/3)

    #Python #DailyPythonTip

  9. Python Tip #223 (of 365):

    Consider copying mutable arguments in your class initializers.

    What happens if two objects are given the SAME list?

    >>> initial_tasks = ['Watch screencast', 'Brush teeth']
    >>> mon = TodoList(initial_tasks)
    >>> tue = TodoList(initial_tasks)

    It depends...

    🧵 (1/4)

    #Python #DailyPythonTip

  10. Python Tip #222 (of 365):

    Be careful with mutable default values in function definitions.

    This class has a bug:

    class TodoList:
    def __init__(self, tasks=[]):
    self.tasks = tasks

    def add_task(self, task):
    self.tasks.append(task)

    Every TodoList object made without arguments shares the SAME list:

    >>> mon = TodoList()
    >>> tue = TodoList()
    >>> mon.add_task("Work on Python exercise")
    >>> tue.tasks
    ['Work on Python exercise']

    🧵 (1/3)

    #Python #DailyPythonTip

  11. Python Tip #221 (of 365):

    Implement appropriate dunder methods.

    Dunder methods are a contract between your class and the Python interpreter.

    • __add__ & __radd__ power +
    • __eq__ powers ==
    • __len__ powers len()

    Dunder methods are what third-party library authors use to make their objects feel like NATIVE Python objects.

    Django QuerySet objects support indexing, iteration, len(...), and truthiness.

    And Pandas DataFrames support a whole BUNCH of syntax.

    🧵 (1/2)

    #Python #DailyPythonTip

  12. Python Tip #220 (of 365):

    Trying quack like a duck? Seek help.

    When implementing a common Python protocol (a sequence, a mapping, etc.), inherit from the matching abstract base class (ABC):

    from collections.abc import MutableSequence

    class MyCustomList(MutableSequence):
    ...

    Benefits:

    1. isinstance & issubclass checks work well (i.e. isinstance(x, Sequence))

    2. Python will complain LOUDLY if you forget to implement a required method for the protocol

    🧵 (1/3)

    #Python #DailyPythonTip

  13. Python Tip #219 (of 365):

    Validate your ducks with goose typing.

    You can use isinstance() WHILE duck typing. This is called goose typing (coined by Alex Martelli).

    >>> from collections‍.abc import Iterable
    >>> isinstance([1, 2, 3], Iterable)
    True

    Point doesn't inherit from Iterable:

    class Point:
    def __init__(self, x, y): self.x, self.y = x, y
    def __iter__(self): yield from (self.x, self.y)

    And yet...

    >>> isinstance(Point(1, 2), Iterable)
    True

    🧵 (1/3)

    #Python #DailyPythonTip

  14. Python Tip #218 (of 365):

    Don't needlessly convert between data types.

    Looping over a file once? There's no reason to convert it to a list first:

    for line in my_file:
    print(line, end="")

    Another example: if you're looping over a set, there's no reason to convert it to a list either:

    >>> colors = {'red', 'blue', 'yellow'}
    >>> for color in colors:
    ... print(color)
    ...
    red
    yellow
    blue

    "for" loops don't need a list. They accept ANY iterable.

    🧵 (1/2)

    #Python #DailyPythonTip

  15. Python Tip #217 (of 365):

    Think in terms of the minimum viable duck.

    In other words: "what's the least this object needs to do to work in this context?"

    For example, csv.reader is usually given a file object... but it accepts ANY iterable of delimited lines:

    >>> import csv
    >>> rows = ['a,b,c', '1,2,3']
    >>> list(csv.reader(rows))
    [['a', 'b', 'c'], ['1', '2', '3']]

    🧵 (1/4)

    #Python #DailyPythonTip

  16. Python Tip #216 (of 365):

    In Python, we often care more about the behavior of an object than its type.

    We say "if it looks like a duck and quacks like a duck, it's a duck".

    When we check for duckness, we don't test DNA, we observe behavior.

    Instead of asking "what TYPE can I use here?", ask "does my object have the BEHAVIOR that's expected here?"

    🧵 (1/3)

    #Python #DailyPythonTip

  17. Python Tip #215 (of 365):

    Know what it means to be a "sequence" in Python.

    Lists, strings, and tuples are all sequences:

    >>> fruits = ["apple", "lemon", "pear"]
    >>> coordinates = (1, 8, 2)
    >>> greeting = "Hi y'all!"

    A sequence is an ordered collection that:

    1. Has a length
    2. Can be indexed from "0" up to one less than its length
    3. Can be looped over

    >>> len(fruits)
    3
    >>> fruits[0]
    'apple'
    >>> for c in coordinates: print(c)
    1
    8
    2

    🧵 (1/3)

    #Python #DailyPythonTip

  18. Python Tip #214 (of 365):

    Use truthiness for integers with caution.

    I DO rely on truthiness checks for integers, but not always. I would recommend only using them when they seem to improve readability.

    I find this:
    if hours: ...

    More readable than this:
    if hours > 0: ...

    But I find this:
    if number % 2 != 0: ...

    More readable than this:
    if number % 2: ...

    🧵 (1/2)

    #Python #DailyPythonTip

  19. Python Tip #213 (of 365):

    Replace "or"-chained equality comparisons with a containment check.

    Instead of this:

    is_vowel = c == "a" or c == "e" or c == "i" or c == "o" or c == "u"

    You can write this:

    is_vowel = c in ("a", "e", "i", "o", "u")

    In fact, you could even give that tuple a name:

    vowels = ("a", "e", "i", "o", "u")
    is_vowel = c in vowels

    🧵 (1/2)

    #Python #DailyPythonTip

  20. Python Tip #212 (of 365):

    Use any() and all() to check a condition across every item in an iterable

    This:

    any_negative = False
    for n in numbers:
    if n < 0:
    any_negative = True
    break

    Can be replaced with:

    any_negative = any(n < 0 for n in numbers)

    And this:

    all_good = True
    for item in iterable:
    if not condition(item):
    all_good = False
    break

    Can be replaced with:

    all_good = all(condition(item) for item in iterable)

    🧵 (1/2)

    #Python #DailyPythonTip

  21. Python Tip #211 (of 365):

    Consider how your Boolean expressions read in English

    For example, you could write "10 > x" or you could write "x < 10".

    They check the same thing, but I find the latter one more readable.

    Steve McConnell's Code Complete recommends writing numeric tests in "number-line order": order the values left to right, from smallest to largest.

    Follow that rule and you'll always use < and <=, never > and >=.

    I don't follow that advice...

    🧵 (1/3)

    #Python #DailyPythonTip

  22. Python Tip #210 (of 365):

    Instead of an "if" under an "else", consider using "elif".

    You likely know about Python's "elif" statement, but you might occasionally underuse it.

    If you find yourself writing an "if" statement just under an "else", ask "should I use an "elif" here instead?"

    🧵 (1/3)

    #Python #DailyPythonTip

  23. Python Tip #209 (of 365):

    Remember Python's "inline ifs"

    Python doesn't have traditional ternary operators (cond ? one : two) but instead has an inline "if" expression (technically a "conditional expression") for the same purpose.

    Instead of:

    if amount == 1:
    noun = "item"
    else:
    noun = "items"

    We can write

    noun = "item" if amount == 1 else "items"

    The condition is in the MIDDLE. The first value is the "true" case and the last is the "false" case.

    🧵 (1/2)

    #Python #DailyPythonTip

  24. Python Tip #208 (of 365):

    Break up long boolean expressions

    Have a long condition in your "if" statement?

    You could:

    1. Wrap it in parentheses to break it up over multiple lines
    2. Use variables to give names to each sub-expression
    3. Use functions to name sub-expressions

    🧵 (1/4)

    #Python #DailyPythonTip

  25. Python Tip #207 (of 365):

    Get fancy with your subprocess‍.run calls.

    When launching a subprocess, consider passing the keyword arguments capture_output, text, and check to subprocess‍.run.

    Passing capture_output=True will capture both stdout & stderr from the subprocess.

    Passing text=True will decode the captured output into strings (instead of providing raw bytes).

    Passing check=True will raise an exception if the subprocess exited with an error code.

    🧵 (1/2)

    #Python #DailyPythonTip

  26. Python Tip #206 (of 365):

    Know your combination and permutation tools in Python.

    Python ships with tools for combinations, permutations, and random selections.

    The itertools module includes various combinatorics-oriented utilities:

    from itertools import combinations, combinations_with_replacement, permutations, product

    This joins tuples of characters into a comma-separated string:

    def join(groups):
    return ", ".join("".join(g) for g in groups)

    🧵 (1/5)

    #Python #DailyPythonTip

  27. Python Tip #205 (of 365):

    Use statistics.multimode to get the most common items.

    To get the most common items in an iterable, you might be tempted to use collections.Counter. Consider using statistics.multimode instead.

    Compare this:

    counts = collections.Counter(items)
    [(_, top_count)] = counts.most_common(1)
    commonest = [
    item
    for item, count in counts.items()
    if count == top_count
    ]

    To this:

    commonest = statistics.multimode(items)

    Simpler, right?

    #Python #DailyPythonTip

  28. Python Tip #204 (of 365):

    Generate unique identifiers with the uuid module

    >>> import uuid
    >>> uid = uuid.uuid4()
    >>> uid
    UUID('922a6cea-c3e9-473d-bf88-efb10793c931')
    >>> str(uid)
    '922a6cea-c3e9-473d-bf88-efb10793c931'

    UUID stands for universally unique identifiers.

    The uuid module has functions for UUID version 1, 3, 4, 5, 6, 7, and 8 from RFC 4122/9562.

    Versions 7 and 8 were added in Python 3.14.

    🧵 (1/2)

    #Python #DailyPythonTip

  29. Python Tip #203 (of 365):

    When you need precise decimal numbers, use decimal.Decimal

    Floating point numbers have precision gotchas.

    For example, 0.1 + 0.02 isn't 0.12:

    >>> one_two = 0.1 + 0.02
    >>> one_two == 0.12
    False
    >>> one_two
    0.12000000000000001

    Using Decimal fixes this:

    >>> from decimal import Decimal
    >>> one_two = Decimal("0.1") + Decimal("0.02")
    >>> one_two == Decimal("0.12")
    True
    >>> one_two
    Decimal('0.12')

    Note that we pass strings to Decimal.

    🧵 (1/4)

    #Python #DailyPythonTip

  30. Python Tip #202 (of 365):

    When prompting the user to enter a password, use getpass

    The getpass.getpass function prompts the user to enter a password and what the user types is completely hidden:

    >>> from getpass import getpass
    >>> password = getpass()
    Password:

    🧵 (1/2)

    #Python #DailyPythonTip