PythonMastery
reference 3 min read · lesson 47 of 60 in Errors

TypeError: takes 1 positional argument but 2 were given

1 · The lesson

read

What this error means

A function received more positional arguments than its def line has room for. Python counts every positional value that arrives — including the one it slips in for you — and the count came out higher than the function accepts.

The numbers in the message are the whole story. If they look off by one from what you typed, the extra one is self.

When you see it

python
Traceback (most recent call last):
  File "main.py", line 9, in <module>
    cart.add("earl grey")
    ~~~~~~~~^^^^^^^^^^^^^
TypeError: Cart.add() takes 1 positional argument but 2 were given

You passed one argument. Python says two. That gap is the classic missing self.

Why it happens

When you call cart.add("earl grey"), Python rewrites it as Cart.add(cart, "earl grey"). The object goes in first, always. So a method defined as def add(item): has room for exactly one value, and the object has already taken it.

python
class Cart:
    def __init__(self):
        self.items = []

    def add(item):                # forgot self
        self.items.append(item)

cart = Cart()
cart.add("earl grey")             # TypeError: Cart.add() takes 1 positional argument but 2 were given

The other two causes are less sneaky:

  • You really did pass too many. A function that used to take three arguments was trimmed to two, and one caller still sends three.
  • The parameter is keyword-only. Anything after a bare * in the def line must be passed by name. Passing it positionally counts as one positional too many.

How to fix it

Add self as the first parameter of every instance method. This fixes the large majority of cases:

python
class Cart:
    def __init__(self):
        self.items = []

    def add(self, item):
        self.items.append(item)

cart = Cart()
cart.add("earl grey")
cart.add("oat milk")
print(cart.items)                 # ['earl grey', 'oat milk']

If the method genuinely doesn't need the object, say so with @staticmethod instead of adding a self you never use:

python
class Price:
    @staticmethod
    def pence(pounds):
        return round(pounds * 100)

print(Price.pence(4.99))          # 499
print(Price().pence(4.99))        # 499 — works on an instance too

For keyword-only parameters, pass them by name. The * is there on purpose, usually to stop someone mixing up two strings:

python
def send_invoice(invoice_id, *, copy_to=None):
    target = f" (copy to {copy_to})" if copy_to else ""
    print(f"Sent {invoice_id}{target}")

send_invoice("INV-1042", copy_to="grace@example.com")   # Sent INV-1042 (copy to grace@example.com)
# send_invoice("INV-1042", "grace@example.com")         # takes 1 positional argument but 2 were given

For a genuine extra argument, check the signature you're actually calling — help(func) or inspect.signature(func) — rather than the one you remember:

python
import inspect

def ship(order_id, courier):
    return f"{order_id} via {courier}"

print(inspect.signature(ship))    # (order_id, courier)

When you'd actually see this in real code

  • Writing your first class and leaving self out of a method. Nearly everyone does it once.
  • threading.Thread(target=process, args=("orders.csv")). Without a trailing comma that is a string, not a tuple, so each of its 10 characters becomes an argument: process() takes 1 positional argument but 10 were given. Write args=("orders.csv",).
  • Moving a module-level helper into a class by indenting it, and not adding self.
  • A callback that a framework calls with an extra event or request argument your function doesn't declare.
  • Upgrading pandas to 2.x, where most optional parameters became keyword-only. Old code like df.drop("notes", 1) now fails with DataFrame.drop() takes from 1 to 2 positional arguments but 3 were given; write df.drop(columns="notes").

See Also

Found this useful? Share it with a friend who’s learning Python.