在 Python 3.6 中实例化自定义 class 后,内部 class 对象未重置

Internal class objects not resetting after instantiating a custom class in Python 3.6

我正在使用 Python 3.6.4 并构建了一个自定义 class,我正在其中进行计算并更改其中一个内部 class 变量。我注意到当我 运行 算法(例如实例化 class 等)时它总是正确运行并且第二次总是失败。即使是同一行代码连续重复两次。我已经能够以更简单的形式复制错误。

为什么第一个对象中正在更改的 lr_space 传播到第二个实例化对象?

class testing(object):
    def __init__(self, 
                n_iter=5,
                n_space=10,
                model_type="logistic",
                lr_space={
                    "C":(1e-6, 1.0),
                    "penalty":["l1", "l2"],
                },
                lr_kws=dict(max_iter=10000, solver="liblinear"),
                ):
        self.n_iter = n_iter
        self.n_space = n_space
        # Logistic Regression
        self.lr_space = lr_space
        self.lr_kws = lr_kws
        print("", self, self.lr_space,"", sep="\n\t")
        self.model_type = model_type.lower()
        self.models = self._test_function()

    def _test_function(self):
        """
        Internal: Label models
        Need to extend this for using different hyperparameters
        """
        models = list()
        self.param_index = OrderedDict()

        # Indexing for hyperparameters and models
        a = np.ones(self.n_iter*2)
        b = np.arange(a.size)
        if self.model_type == "logistic":
            self.lr_space["C"] = np.linspace(*self.lr_space["C"], self.n_space)


        return models
print("=====Instantiating and running `instance_1`=====")
instance_1 = testing()
print("=====Instantiating and running `instance_2`=====")
instance_2 = testing()

输出:

=====Instantiating and running `instance_1`=====

    <__main__.testing object at 0x136154400>
    {'C': (1e-06, 1.0), 'penalty': ['l1', 'l2']}

=====Instantiating and running `instance_2`=====

    <__main__.testing object at 0x127649390>
    {'C': array([  1.00000000e-06,   1.11112000e-01,   2.22223000e-01,
         3.33334000e-01,   4.44445000e-01,   5.55556000e-01,
         6.66667000e-01,   7.77778000e-01,   8.88889000e-01,
         1.00000000e+00]), 'penalty': ['l1', 'l2']}

错误:
---------------------------------------------- -------------------------- TypeError Traceback(最后一次调用) 在 () 38 instance_1 = 测试() 39 print("=====实例化和运行instance_2=====") ---> 40 instance_2 = 测试()

<ipython-input-342-24f241984973> in __init__(self, n_iter, n_space, model_type, lr_space, lr_kws)
     17         print("", self, self.lr_space,"", sep="\n\t")
     18         self.model_type = model_type.lower()
---> 19         self.models = self._test_function()
     20 
     21     def _test_function(self):

<ipython-input-342-24f241984973> in _test_function(self)
     31         b = np.arange(a.size)
     32         if self.model_type == "logistic":
---> 33             self.lr_space["C"] = np.linspace(*self.lr_space["C"], self.n_space)
     34 
     35 

TypeError: linspace() takes from 2 to 6 positional arguments but 11 were given

解决方案:

如果您在 init 函数中分配默认值 lr_space,它会起作用:

 from collections import OrderedDict
 import numpy as np

 class testing(object):
     def __init__(self, 
                 n_iter=5,
                 n_space=10,
                 model_type="logistic",
                 lr_space=None,
                 lr_kws=dict(max_iter=10000, solver="liblinear"),
                 ):
         if lr_space is None:
           lr_space = {
                     "C":(1e-6, 1.0),
                     "penalty":["l1", "l2"],
           }
         self.n_iter = n_iter
         self.n_space = n_space
         # Logistic Regression
         self.lr_space = lr_space
         self.lr_kws = lr_kws
         print("", self, self.lr_space,"", sep="\n\t")
         self.model_type = model_type.lower()
         self.models = self._test_function()

     def _test_function(self):
         """
         Internal: Label models
         Need to extend this for using different hyperparameters
         """
         models = list()
         self.param_index = OrderedDict()

         # Indexing for hyperparameters and models
         a = np.ones(self.n_iter*2)
         b = np.arange(a.size)
         if self.model_type == "logistic":
             self.lr_space["C"] = np.linspace(*self.lr_space["C"], self.n_space)


         return models
 print("=====Instantiating and running `instance_1`=====")
 instance_1 = testing()
 print("=====Instantiating and running `instance_2`=====")
 instance_2 = testing()

为什么:

当您为 def __init__(...) 中的参数分配默认值时,它们会在新实例存在 之前分配。这在使用简单的非可变值(如 5"logistic" 时无关紧要,但是如果你使用字典,你将在实例外部创建一个对象,然后将其分配给实例中的引用__init__呼唤。

这是一个危险的反模式,您应该避免。您可以在这里阅读更多相关信息:Using a mutable default value as an argument

当您创建新实例时,将再次分配引用,但它仍然引用同一个对象。您上面的代码相当于:

default_dict = {
    "C":(1e-6, 1.0),
    "penalty":["l1", "l2"],
}

class testing(object):
     def __init__(self, 
            n_iter=5,
            n_space=10,
            model_type="logistic",
            lr_space=default_dict,
            lr_kws=dict(max_iter=10000, solver="liblinear"),
            ):

我最后做的是使用 copy 内置的 deepcopy

import copy
class testing(object):
    def __init__(self, 
                n_iter=5,
                n_space=10,
                model_type="logistic",
                lr_space={
                    "C":(1e-6, 1.0),
                    "penalty":["l1", "l2"],
                },
                lr_kws=dict(max_iter=10000, solver="liblinear"),
                ):
        self.n_iter = n_iter
        self.n_space = n_space
        # Logistic Regression
        self.lr_space = copy.deepcopy(lr_space)
        self.lr_kws = lr_kws
        print("", self, self.lr_space,"", sep="\n\t")
        self.model_type = model_type.lower()
        self.models = self._test_function()

    def _test_function(self):
        """
        Internal: Label models
        Need to extend this for using different hyperparameters
        """
        models = list()
        self.param_index = OrderedDict()

        # Indexing for hyperparameters and models
        a = np.ones(self.n_iter*2)
        b = np.arange(a.size)
        if self.model_type == "logistic":
            self.lr_space["C"] = np.linspace(*self.lr_space["C"], self.n_space)


        return models