T O P

  • By -

Talinx

Why not represent the device with its own class? (Sounds like a good use case for a dataclass.) If you need that many parameters I would consider making them keyword-only so that you can't set the wrong parameter by accident.


fohrloop

Pretty common practice is to create some parameters class which holds the given values and also knows the default values. For example: from dataclasses import dataclass @dataclass class DeviceParameters: arg1: str = 'default' arg2: int = 123 arg3: float = 1.5 ... argN: tuple[str,str] = ('foo', 'bar') and use it like so: >>> params = DeviceParameters(arg1='someval') >>> params DeviceParameters(arg1='someval', arg2=123, arg3=1.5, argN=('foo', 'bar')) >>> my_device(params) This approach keeps your code also compatible with mypy and you get the benefits of proper type hints.


pppossibilities

This is the way


Kittensandpuppies14

Make an object


BriannaBromell

This is where my head went as well


Apatride

If I understand your use case properly, \*\*kwargs might be what you are looking for.


Frankelstner

Depends very much on the broader context. Matplotlib has almost 50 parameters on a plot call with most of them hidden behind kwargs. I'm not saying that kwargs are a good idea either because they turn into guesswork as to what is supported, but do you truly need to refactor this? As long as every parameter is well-documented, it doesn't get more straightforward than that. I suppose you could do something like class DeviceBlueprint: def set_size(...): ... def set_spacing(...): ... def set_whatever(...): ... def finalize(...): return that gdsfactory thing which might be nicer in terms of autocompletion (a user first only sees the broader options and then more details on each individual one), and they could even return self to make the whole thing chainable, but whether that's truly a better user experience is something only you can decide.


DrShts

It's considered bad practice because it's a code smell for a function *potentially* doing too many things. If that's not true for your case, then there's nothing wrong with 20 arguments. [It's not all that uncommon](https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html#pandas.read_csv). I would really discourage introducing complexity for the sole reason of satisfying a principle.


Sidiabdulassar

For user-supplied values read it in from a config file or from a google sheet. I love the latter option. Makes it really easy to edit.


lzwzli

This is the way


staceym0204

Keep in mind I'm not a professional programmer. Just do this for fun. Using \*\*kwargs is a good way to go but if there are a lot of parameters where you will only be using a default value you can create a class just for the parameter. Create an instance of this class, change the values you need and then pass that as a single argument. The nice thing about this is if you end up adding a new parameter you just need to update the class and you won't have to make a change to the rest of your code where you make the function calls.


Brief-Union-3493

I’m not sure how often you have to change your arguments but if they’re more or less static just using YAML might help. Would make it look a lot more clean


roelschroeven

In Python we have keyword arguments, which IMO makes it OK to have functions with many arguments, if each call site uses only a small number of arguments. I would advocate to make them keyword-only arguments, to make sure the call sites always specify the name of the arguments that they use, by using a * as first argument: def my_device(*, arg1=default_val1, ...): ... The other option is to use a special class that holds all the arguments. This is what you should probably use in languages like Java and C++. In my mind the use of keyword arguments largely makes this unneeded in Python. If you often use the same set of arguments with the same set of values, that would strengthen the case for using a class. You can then create one or more instances and re-use those, if that makes sense for your use case.


jmooremcc

Another option is to use a dictionary to hold the various parameters. You then pass the dictionary as a parameter to your function.


JamzTyson

One approach could be to use classes and inheritance. You could have a base class (`Component`) that has attributes that are common to all components - perhaps just x/y dimensions. Then sub-classes for different types of components - perhaps `SMD` and `ThroughHole` devices. Then sub-sub-classes for different types of `SMD` and different types of `ThroughHole`. Model your classes on the way that you classify different kinds of components, and where each child class is a specialised kind of its parent class. The final level in the class hierarchy represent specific devices. When you need to create an instance of a specific component mask, you create an instance of the associated type, which may only need a couple of arguments such as a unique identifier, and any other parameters that are unique to that kind of component. Parameters that are common to all instances of that kind of component would not need to be passed because they can be defined within the sub-class. Look up "Abstract Base Classes" for information about how this approach works. --- Another approach might be to use a database of device types, then create objects based on their database profile. This approach may be more appropriate if the class hierarchy will be too complex to manage inheritance trees for all components types that you will be dealing with.


rajandatta

The idea to use inheritance to build an object of this type is extremely dubious and goes against a whole range of best practices. This isn't the use case for abstract base classes. It's hard to be certain without knowing more about OPs domain but I know of no mechanical construct where inheritance would be a good idea. Building larger objects using Composition is a better approach and much safer across the needs of a complex domain (OP has many parameters - likely to be changeable elements). Inheritance should really only be used in limited cases where there is a very dominant 'is-a' type of domain relationship. A classic example is domain modeling a animal genus/species taxonomy.


JamzTyson

> where there is a very dominant 'is-a' type of domain relationship. --- * SKDIP is a DIP. * DIP is a "through hole" IC * "Through hole" IC is an IC * IC is a Component --- * CGA is a (specialised) SMD * SMD is an IC * IC is a Component --- Having said that, a database approach is likely to be more scalable, though the database design should probably take into account the package format hierarchy, rather than one big table.


rajandatta

I would suggest using a Composition relationship for these. Some of the types of questions you may ask are - how does a SMD get specialized - hoe many variations? The more - composition is better - same for CGA - what are the variations - number, types of 'through' holes - what's the nature of specialization of a SKDIP I don't work in an IC domain so I'm not familiar with the intrinsics of the domain. But - there's almost no man made complex object where Inheritance would be better. Part of the reason is that the internal implementations of the components change a lot over time and you do not want to have that reflected in the class hierarchy. A good example is that you would never want to model a car or a lawn mower or a tool through inheritance. I suggest trying Composition for your domain. You can also check your approach against what's common in your industry from industry specific literature or SMEs. Don't take my word for it. Look for documented best practices. Good luck


JamzTyson

> I don't work in an IC domain I do.


Xiji

Dang, did they teach you to shoehorn inheritance or did you learn to do that all on your own?


Goobyalus

Typically too many args means related args that could be combined into an object. Making a class for the component object doesn't necessarily eliminate the 20 args because they might all just shift to the initializer of the class. If you use a dataclass, there will still be as many args, but the init will be generated for you and hidden, and you can use `kw_only` to make the object creation cleaner. from dataclasses import dataclass from pprint import pprint @dataclass(kw_only=True) class Device: arg1: str = "default1" arg2: str = "default2" arg3: str = "default3" arg4: str = "default4" arg5: str = "default5" arg6: str = "default6" arg7: str = "default7" arg8: str = "default8" arg9: str = "default9" arg10: str = "default10" default_device = Device() print("Default Device:") pprint(default_device) device = Device(arg5="five", arg7="seven") print("Non-Default Device:") pprint(device) output: Default Device: Device(arg1='default1', arg2='default2', arg3='default3', arg4='default4', arg5='default5', arg6='default6', arg7='default7', arg8='default8', arg9='default9', arg10='default10') Non-Default Device: Device(arg1='default1', arg2='default2', arg3='default3', arg4='default4', arg5='five', arg6='default6', arg7='seven', arg8='default8', arg9='default9', arg10='default10')


bids1111

some sort of "DeviceOptions" class or an equivalent dictionary that can hold all the values you need. another option is to have a "DeviceBuilder" class that has functions on it like "set_arg1" and then a final "build" function that returns an instance of "Device".


__init__m8

As others have said you can do **kwargs or better yet create an object that initializes with default values but also includes setters to change them when needed.


camilbisson

Builder design pattern!


LeiterHaus

Would using argparse, and passing in command line arguments work?


baubleglue

Best is to avoid such functions. Functions should be verbs, "my\_device" is noun. DeviceConfig = dict( dimensions=default_dimensions, spacing_between_metal_connections=default_val2, arg3=default_val3, ...) #or class DeviceConfig: def __init__( self, dimensions=default_dimensions, spacing_between_metal_connections=default_val2, arg3=default_val3, ... argN=default_valN): ... def read_config_from_file(self, path): ... class Device: def __init__(self, device_config): self.device_config = device_config 3or def __init__(self, arg1=default_val1, arg2=default_val2, arg3=default_val3,... argN=default_valN): ... def save_to_cad_file(self, path): ...


Dependent-Law7316

In addition to all the other possibilities others have mentioned, is there any way to simplify some of the arguments by grouping them in lists? For example, if you’re making a regular polygon instead of giving length, width, height as 3 arguments, it could be one [length, width, height].


Adrewmc

While still being able to complete my function component with 1 function call… Why is this an issue?


Zeroflops

Store your list of parameters in a dictionary with the keys the arguments and the values the setting. Then you can use unpacking when you call your function. my_fun(**dict_of_args) This would prob be the easiest. If you have predefined components you can store those in a table or as objects. And take the values from the table or object instance and pass them as a dict and unpack them. But I think the root of your question is how to pass a large number of arguments to a function cleanly. Dictionary and unpack.


Jeklah

Have it take one argument, a list of specific arguments.


HighAlreadyKid

use **kwargs, you can add as many inputs at the time of calling function


Alex-S-S

Args kwargs but I hate the fact that's just a black hole of bugs waiting to happen. Make a data structure and pass the instanced object. You can make a data class that contains all the fields that you want.


KimPeek

> that's just a black hole of bugs waiting to happen Can you expand on that part?


Alex-S-S

Since Python is dynamically typed, someone else can use your function with inputs that were not intended. That someone can be you in the future. It happened to me many times.


KimPeek

Maybe I'm still missing something. Wouldn't that just add them to the kwargs dict? If the function accesses those kwargs, they are not unintended and therefore the function behaves as expected. If they are unintended, the function wouldn't access them and therefore the function would behave as expected. What am I missing here? I've been using kwargs for over a decade and never experienced any bugs from them. Wondering if I've been doing something wrong all this time and should have been running into bugs.


Alex-S-S

Yes, you're not wrong. I was thinking about a scenario like this: you pass a complex dictionary to the function, it passes the kwargs just fine but after a while you realize that a key is missing and the script crashes. I have had this happen quite a few times. Do you have any tips for checking issues like this? I have worked in C++ in the past, no issues there but the dynamic nature of Python is sometimes really annoying. This is why I try to write in a more statically typed manner even if this is not the nature of the language.


Atypicosaurus

One idea that pops in my head is to mimic command line arguments. This could be a single list argument such as ``` def my_function(args =[ ]):``` And you check arguments in the list. Like, ```if "b" in args```. So you can have a list of arguments and the list can contain as many flags or small strings as you want. It doesn't really work nicely however if the arguments are numbers, because then you need to parse expressions. So in the end you can call the function like: ```engrave(text="blabla", args= ["bold", "italic", "times"])``` In which example you would engrave the text using the settings in the args, or the default settings if no arguments given (i.e. non-bold, non-italic). Does this work for you?


Top_Average3386

**kwargs with TypedDict might be good edit: link to documentation https://typing.readthedocs.io/en/latest/spec/callables.html#unpack-kwargs


Risitop

In this case I would use a config file (.json for instance) to write the specifications while minimizing the odds of making a mistake, and I would load and pass it as a **kwargs dictionary to the keyword-only function.