Question Details

No question body available.

Tags

anti-patterns parameters dynamic-typing

Answers (7)

March 27, 2025 Score: 20 Rep: 47,288 Quality: Expert Completeness: 100%

The code in your question is an example of function overloading, which is far from an anti-pattern in languages that support this feature. Python does not because it is dynamically-typed (see Python function overloading on StackOverflow). We have the same problem in JavaScript. There are valid reasons to support overloads of the same function, but you need to take care in dynamically-typed languages so you don't confuse future programmers.

  • Consider using multipledispatch, which is a pip package enabling function overloading.
  • Add pydoc comments to the function or method describing the parameter types you accept.
  • Rename the function to make it more clear.
  • Add another function with a different name: processdata versus processdatafrom_json for example.

Refactoring existing code might not always be possible. I would opt for using multipledispatch or pydoc comments on existing code. For new code I would use multipledispatch or adding a new method with a slightly different name.

I don't think this is an anti-pattern. Function overloading is a feature in many languages, and that is essentially what the code snippet is doing. We have some challenges with languages that don't support function overloading, but this isn't necessarily confusing based on the use case for the function. Processing data from some outside source makes sense to load it from a string. Conceptually, the code snippet isn't so far off that it would confuse me.

And I think that's the litmus test: is this function confusing for the developers? It becomes a matter of taste in lieu of real function overloading. This might be an idiom in your project. If you find multiple functions that do this sort of type inference then you might just need to learn the idiom for the old code, but consider some alternate design choices for new code.

March 27, 2025 Score: 9 Rep: 31,392 Quality: High Completeness: 60%

This is a great question and when I first started working with Python and initially encountered this approach, I had the same concerns. That was before Python's popularity exploded. The language lacked type annotations at that time and the community of Python developers was different from what it is today.

At a fundamental level, I believe this approach comes from the fact that you cannot create overloaded methods in Python. If you want different versions of a method that take different types, you need to name them differently. The approach you are describing follows an aesthetic of that earlier era that was not just acceptable but considered normal.

Over time, I have found that this approach can be workable or even preferable in narrow cases and can become a problematic obfuscation when used excessively (as you likely suspect.)

An example of when I think this is mostly fine is something like your example or something like this:

def load(file):
    if isinstance(file, str):
        file = open(file)

// do things with file

Now, to be clear, the above can be a little problematic in larger projects or APIs. The issue I have run into is that, because I don't have to worry about which type is being passed in, the code using this kind of function starts to become hard to understand due to the heterogenous types being passed around. That can lead to more of this kind of thing in a vicious cycle.

It becomes awkward with type annotations. I think it's one of those things that used to be a standard Pythonic approach (like using try/except for logic) that have fallen out of favor. I still use this in rare cases e.g. allowing a function to accept a date as well as a str in a specific format. But I only do this if it makes the code cleaner and doesn't create largely different execution paths.

March 27, 2025 Score: 4 Rep: 2,659 Quality: Medium Completeness: 60%

Already stated: it is overloading or maybe polymorphism, being able to call something in different ways.

The advantage is that you ensure the same processing. You do not have slightly different called small methods calling a unified large method.

There are several variations possible more or less eliminating the dynamic test isinstance. A middle way might be:

def processdata(arg):
    if isinstance(arg, str):
        onprocessdata(json.loads(arg))
    elif isinstance(arg, dict):
        onprocessdata(arg)

def onprocess_data(dictarg)

There is a disadvantage however: you are doing things in different ways. And this might even happen at several spots, and might even be repetitions of allowing alternatives. That is definitely an anti-pattern. Offering alternative usages must be severely limited (at least in a finalizing stage).

This greatly simplifies understanding, limits repeated wrappers and keeps the data flow lean (preventing data containers repeated on several layers).

Using alternatives is missing software consolidation. However a bit of alternatives is natural. And then not necessarily an "anti-pattern."

In a slightly higher level programming language one would check the data structures - should some more powerful or more generalized data structure have been used?

March 27, 2025 Score: 2 Rep: 111,005 Quality: Low Completeness: 40%

Being liberal in what you accept is not in itself an anti-pattern. You lose some API simplicity and type safety, but you gain usability and flexibility. It depends on your specific requirements which has more impact on project success.

What would be really bad is if you had several methods that do the same dance around different data representations. Then you should definitely refactor it to a reusable normalizeinputrepresentation() method.

March 27, 2025 Score: 0 Rep: 567 Quality: Low Completeness: 80%

I think it's fine, broadly what you are asking is ways to achieve ad hoc polymorphism in Python.

In Java for instance I would think achieve this by just explicitly making new wrapper classes which conformed to a common interface. Personally this is the way I like the most.

In Scala I would think either do this plus some implicits to make it look nicer (typical mechanism for implementing type classes), or do basically as you've done and have function which differentiates on type. That being said, if I were to do that in Scala I would use pattern matching so as to hide the explicit instanceof checks.

Python is in my view a lot like Scala in that it is multi-paradigm and you can be functional, imperative or OO.

And in fact there is https://docs.python.org/3/tutorial/controlflow.html#tut-match so you can pattern match in Python (now) and that will look a lot nicer than your proposed code

e.g.


def whereis(point):
    match point:
        case Point(x=0, y=0):
            print("Origin")
        case PointIn3D(x=0, y=y, z = 2):
            print(f"aha a 3 point")
        case :
            print("idk not a case I knew about")

If I were to say anything was an antipattern in your code I would say it's explicit instanceof checks which are something you can usually factor away so that you don't see them.

March 27, 2025 Score: -1 Rep: 185 Quality: Low Completeness: 50%

It depends on how much sense the accepted range of parameter types makes. OK, this is Python and it's not formally typed, so, yes, that will affect the community's tolerance for this behavior.

So, let's instead take Polars, a dataframe library written in Rust, with what seems to be very deliberate intent to have a logical API.

A number of functions, in the Python flavor of its API, will take either one string, expected to mean a column name. Or a list/iterable of strings, expected to mean, several column names to use in sorting. This applies throughout the API, mostly where you would expect it to and it feels quite natural.

Python Polars sort API

I am less sure about the Rust side of their sort API however and each search seems to bring me back to either Python or some lower level bits of their system. At a glance, it looks like sort takes a one-item array which may be a better fit for Rust's typing mechanism.

In your case, a dictionary vs a JSON-loadable string seems to be similarly coherent. However, that still depends a bit on the context: users may assume that any string will be OK, which is incorrect. In fact, a JSON array representation would most probably be a problem as well, since the function seems designed to process dictionaries.

March 27, 2025 Score: -4 Rep: 4,896 Quality: Low Completeness: 20%

API design

API is not extended with new capabilities unless absolutely necessary. A frivolous type manipulation can't be considered necessary.

Typing

Modern Python's content assist, documentation traversal and linting depend heavily on type annotations. Using untyped input deprives client and service code of these benefits.

In this particular case argument could be annotated as Union, but this would make it less readable.

Conclusion

Do not use this approach in new APIs. Use type annotations in internal code.