<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def feedin_proportional(feedin, generators, curtailment_timeseries, edisgo, curtailment_key, **kwargs):
 """ Implements curtailment methodology 'feedin-proportional'. The curtailment that has to be met in each time step is allocated equally to all generators depending on their share of total feed-in in that time step. Parameters feedin : :pandas:`pandas.DataFrame<dataframe>` Dataframe holding the feed-in of each generator in kW for the technology (and weather cell) specified in `curtailment_key` parameter. Index of the dataframe is a :pandas:`pandas.DatetimeIndex<datetimeindex>`. Columns are the representatives of the fluctuating generators. generators : :pandas:`pandas.DataFrame<dataframe>` Dataframe with all generators of the type (and in weather cell) specified in `curtailment_key` parameter. See return value of :func:`edisgo.grid.tools.get_gen_info` for more information. curtailment_timeseries : :pandas:`pandas.Series<series>` The curtailment in kW to be distributed amongst the generators in `generators` parameter. Index of the series is a :pandas:`pandas.DatetimeIndex<datetimeindex>`. edisgo : :class:`edisgo.grid.network.EDisGo` curtailment_key::obj:`str` or :obj:`tuple` with :obj:`str` The technology and weather cell ID if :obj:`tuple` or only the technology if :obj:`str` the curtailment is specified for. """

    # calculate curtailment in each time step of each generator
    curtailment = feedin.divide(feedin.sum(axis=1), axis=0). \
        multiply(curtailment_timeseries, axis=0)

    # substitute NaNs from division with 0 by 0
    curtailment.fillna(0, inplace=True)

    # check if curtailment target was met
    _check_curtailment_target(curtailment, curtailment_timeseries,
                              curtailment_key)

    # assign curtailment to individual generators
    _assign_curtailment(curtailment, edisgo, generators, curtailment_key)