Determining if __init__() is called as a result of method call

时间:2018-03-25 20:57:50

标签: python python-3.x pandas

I have a case where, evidently, __init__() is called as a result of a method call. (Not precisely sure why; suspect it may have to do with needing to return a copy.)

Example of the class's design:

import copy
import pandas as pd

class TSeries(pd.Series):
    # See:
    # https://pandas.pydata.org/pandas-docs/stable/internals.html

    _metadata = ['some_new_attr']

    def __init__(self, *args, **kwargs):
        # Copy needed because we would otherwise inadvertantly alter
        # mutable arguments such as pd.Series, TSeries, etc.
        args = tuple(copy.deepcopy(arg) for arg in args)
        some_new_attr = kwargs.pop('some_new_attr', None)
        super().__init__(*args, **kwargs)
        print('WE ARE "IN IT."')

        # Put a calculation here that that we *don't* want to call
        # if class instance is instantiated as a result of
        # of self.new_method().
        #
        # _do_some_conditional_calculation(self)

    @property
    def _constructor(self):
        # Used when a manipulation result has the same
        # dimesions as the original.  Fairly sure
        # self.new_method() uses this.
        return TSeries

    def new_method(self):
        return self * 100

Calling a method, as a result of which __init__() is called:

>>> x = TSeries([1, 2, 3])
WE ARE "IN IT."

>>> x.new_method()
WE ARE "IN IT."      # <-- __init__() called
0    100
1    200
2    300
dtype: int64

My desire is to do some inplace operation on self within __init__(), but not if the call occurred because of a method call. (Or, more directly, "do the operation only if the user instantiates an instance of TSeries directly.")

How can I differentiate between these two cases?


Update - this illustrates what I'm trying to do, but seems dangerous.

IS_METHOD_CALL = False


class TSeries(pd.Series):
    # See:
    # https://pandas.pydata.org/pandas-docs/stable/internals.html

    _metadata = ['some_new_attr']

    def __init__(self, *args, **kwargs):
        # Copy needed because we would otherwise inadvertantly alter
        # mutable arguments such as pd.Series, TSeries, etc.
        args = tuple(copy.deepcopy(arg) for arg in args)
        some_new_attr = kwargs.pop('some_new_attr', None)
        super().__init__(*args, **kwargs)
        print('WE ARE "IN IT."')

        # Put a calculation here that that we *don't* want to call
        # if class instance is instantiated as a result of
        # of self.new_method().

        global IS_METHOD_CALL
        if not IS_METHOD_CALL:
            print('Some conditional calculation')
        IS_METHOD_CALL = False

    @property
    def _constructor(self):
        # Used when a manipulation result has the same
        # dimesions as the original.  Fairly sure
        # self.new_method() uses this.
        return TSeries

    def new_method(self):
        # We'd need to do within every single method...
        global IS_METHOD_CALL
        IS_METHOD_CALL = True
        return self * 100

Conditional calc is skipped:

>>> x = TSeries([1, 2, 3])
WE ARE "IN IT."
Some conditional calculation

>>> x.new_method()
WE ARE "IN IT."
0    100
1    200
2    300
dtype: int64

1 个答案:

答案 0 :(得分:1)

I think you'll have to explicitly tell TSeries.__init__ to perform the conditional calculation. There's nothing the really distinguishes between explicit and implicit calls to TSeries.__new__ (via type.__call__(TSeries, ...)).

class TSeries(pd.Series):
    def __init__(self, ..., extra_config=False):
        ...
        if extra_config:
            self._do_some_conditional_calculation()

Now, the only way to execute _do_some_conditional_calculation is to request it explicitly: x = TSeries([1,2,3], extra_config=True).