In Python, it’s entirely idiomatic to take different types of input and do different things. Look at the pandas dataframe constructor or just about anything in the datascience ecosystem (e.g., matplotlib). Look at the open() builtin—the value of the second string argument changes the return value type. Things aren’t much better in JS land where a function might take an arg or a list or a config object, etc. We see dozens of type errors in production every day.
For what its worth, the datascience ecosystem is probably the worst place to get general idioms from. They tend to err towards their own domain language and build stuff for people who arent programmers. i.e. for caller flexibility instead of maintainability.
Besides that, i only said it was a code smell, i dont debate that its useful sometimes. Just that its a signal to be careful, check you really need itor there is t some other constraint you can break.
My only advice is that narrowing your parameter types at the earliest opportunity is a good practice and results in much easier to understand code.
I kinda wish Python took the Rust approach of having multiple factory static-methods, rather than one constructor that does different things based on what arguments you pass in.
Yeah, it seems not very idiomatic. I regularly see "senior engineers" doing I/O, throwing exceptions, etc in constructors. I staunchly believe a constructor should take exactly one parameter per member variable and set the member variable. For conveniences, make static methods that invoke that constructor. This will make your code far more understandable and testable (you can initialize your object in any state without having to jump through hoops). This is language agnostic advice, but some language communities adhere to it better than others. Python is not very good about this.
I wonder if dataclasses might change this. They provide an __init__ that does what you describe, and though you can supply your own __init__ instead, classmethods seem the easier way to add custom initialization logic.
Yeah, it would be a very good thing for the Python community if dataclasses became idiomatic and vanilla classes came to be regarded as a code smell. Basically what every language should have is the concept of a "struct" or a "struct pointer". Constructors imply that there is one right way to construct an object which is rarely true except in the one-param-per-member-field sense.
"Convenience constructors" (i.e., static methods for creating instances from a set of parameters) are fine, but should often not be in the same module (or perhaps even the same package) as the class anyway. For example, if you have a Book class and it has a convenience constructor for creating a book from a database connection string and a book ID, this is probably reasonable for certain applications, but if it's likely that others would want to use your book class in non-database applications, then it would be a travesty to make the non-database application take a dependency on sqlalchemy, a database driver, etc (especially since database drivers tend to be C-extensions which may or may not be difficult to install on various target platforms).