Skip to content

Instantly share code, notes, and snippets.

@wshayes
Last active December 10, 2019 20:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wshayes/47b62d545ed38cf010eb859d64e1f11d to your computer and use it in GitHub Desktop.
Save wshayes/47b62d545ed38cf010eb859d64e1f11d to your computer and use it in GitHub Desktop.
[python snippets] Python snippets that are useful #python

Tips and tricks

Sources

Skipping Begining of Iterable

Sometimes you have to work with files which you know that start with variable number of unwanted lines such as comments. itertools again provides easy solution to that:

This code snippet produces only lines after initial comment section. This approach can be useful in case we only want to discard items (lines in this instance) at the beginning of the iterable and don't know how many of them there are.

string_from_file = """
// Author: ...
// License: ...
//
// Date: ...

Actual content...
"""

import itertools

for line in itertools.dropwhile(lambda line: line.startswith("//"), string_from_file.split("\n")):
	print(line)

Functions with only Keyword Arguments (kwargs)

It can be helpful to create function that only takes keyword arguments to provide (force) more clarity when using such function:

As you can see this can be easily solved by placing single * argument before keyword arguments. There can obviously be positional arguments if we place them before the * argument.

def test(*, a, b):
	pass

test("value for a", "value for b")  # TypeError: test() takes 0 positional arguments...
test(a="value", b="value 2")  # Works...

Creating Object That Supports with Statements

We all know how to, for example open file or maybe acquire locks using with statement, but can we actually implement our own? Yes, we can implement context-manager protocol using enter and exit methods:

class Connection:
	def __init__(self):
		...

	def __enter__(self):
		# Initialize connection...

	def __exit__(self, type, value, traceback):
		# Close connection...

with Connection() as c:
	# __enter__() executes
	...
	# conn.__exit__() executes

This is the most common way to implement context management in Python, but there is easier way to do it:

from contextlib import contextmanager

@contextmanager
def tag(name):
	print(f"<{name}>")
	yield
	print(f"")

with tag("h1"):
	print("This is Title.")
    

The snippet above implements the content management protocol using contextmanager manager decorator. The first part of the tag function (before yield) is executed when entering the with block, then the block is executed and finally rest of the tag function is executed.

Saving Memory with slots

If you ever wrote a program that was creating really big number of instances of some class, you might have noticed that your program suddenly needed a lot of memory. That is because Python uses dictionaries to represent attributes of instances of classes, which makes it fast but not very memory efficient, which is usually not a problem. However, if it becomes a problem for your program you might try using slots:

class Person:
	__slots__ = ["first_name", "last_name", "phone"]
	def __init__(self, first_name, last_name, phone):
		self.first_name = first_name
		self.last_name = last_name
		self.phone = phone

What happens here is that when we define slots attribute, Python uses small fixed-size array for the attributes instead of dictionary, which greatly reduces memory needed for each instance. There are also some downsides to using slots - we can't declare any new attributes and we are restricted to using ones on slots. Also classes with slots can't use multiple inheritance.

Limiting CPU and Memory Usage

If instead of optimizing your program memory or CPU usage, you want to just straight up limit it to some hard number, then Python has a library for that too:

import signal
import resource
import os

# To Limit CPU time
def time_exceeded(signo, frame):
	print("CPU exceeded...")
	raise SystemExit(1)

def set_max_runtime(seconds):
	# Install the signal handler and set a resource limit
	soft, hard = resource.getrlimit(resource.RLIMIT_CPU)
	resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard))
	signal.signal(signal.SIGXCPU, time_exceeded)

# To limit memory usage
def set_max_memory(size):
	soft, hard = resource.getrlimit(resource.RLIMIT_AS)
	resource.setrlimit(resource.RLIMIT_AS, (size, hard))

Here we can see both options to set maximum CPU runtime as well as maximum memory used limit. For CPU limit we first get soft and hard limit for that specific resource (RLIMIT_CPU) and then set it using number of seconds specified by argument and previously retrieved hard limit. Finally, we register signal that causes system exit if CPU time is exceeded. As for the memory, we again retrieve soft and hard limit and set it using setrlimit with size argument and retrieved hard limit.

Comparison Operators the Easy Way

It can be pretty annoying to implement all the comparison operators for one class, considering there are quite a few of them - lt , le , gt , or ge. But what if there was an easier way to do it? functools.total_ordering to the rescue:

from functools import total_ordering

@total_ordering
class Number:
	def __init__(self, value):
		self.value = value

	def __lt__(self, other):
		return self.value < other.value

	def __eq__(self, other):
		return self.value == other.value

print(Number(20) > Number(3))
print(Number(1) < Number(5))
print(Number(15) >= Number(15))
print(Number(10) <= Number(2))

How does this actually work? total_ordering decorator is used to simplify the process of implementing ordering of instances for our class. It's only needed to define lt and eq, which is the minimum needed for mapping of remaining operations and that's the job of decorator - it fills the gaps for us.

Defining Multiple Constructors in a Class

One feature that is very common in programming languages, but not in Python, is function overloading. Even though you can't overload normal functions, you can still (kinda) overload constructors using class methods:

import datetime

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def today(cls):
        t = datetime.datetime.now()
        return cls(t.year, t.month, t.day)

d = Date.today()
print(f"{d.day}/{d.month}/{d.year}")
# 14/9/2019

You might be inclined to put all the logic of alternate constructors into init and solve it using *args, **kwargs and bunch of if statements instead of using class methods. That could work, but it can become hard to read and hard to maintain. I would therefore recommend to put very little logic into init and perform all the operations in separate methods/constructors. This way you will get code that is clean and clear both for the maintainer and user of the class.

Caching Function Calls Using Decorator

Have you ever wrote a function that was performing expensive I/O operations or some fairly slow recursive function that could benefit from caching (memoizing) of it's results? If you did, then there is easy solution to that using lru_cache from functools:

from functools import lru_cache
import requests

@lru_cache(maxsize=32)
def get_with_cache(url):
    try:
        r = requests.get(url)
        return r.text
    except:
        return "Not Found"


for url in ["https://google.com/",
            "https://martinheinz.dev/",
            "https://reddit.com/",
            "https://google.com/",
            "https://dev.to/martinheinz",
            "https://google.com/"]:
    get_with_cache(url)

print(get_with_cache.cache_info())
# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)

In this example we are doing GET requests that are being cached (up to 32 cached results). You can also see that we can inspect cache info of our function using cache_info method. The decorator also provides a clear_cache method for invalidating cached results. I want to also point out, that this should not be used with a functions that have side-effects or ones that create mutable objects with each call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment