代码之家  ›  专栏  ›  技术社区  ›  Tom

在可能失败的构造函数中运行代码的错误做法?

  •  15
  • Tom  · 技术社区  · 17 年前

    我的问题相当于一个设计问题。 在python中,如果“constructor”中的代码失败,则对象最终不会被定义。因此:

    someInstance = MyClass("test123") #lets say that constructor throws an exception
    someInstance.doSomething() # will fail, name someInstance not defined.
    

    不过,我确实遇到过这样的情况,如果从构造函数中删除容易出错的代码,那么会发生大量代码复制。基本上,我的构造器填充了一些属性(通过IO,其中很多可能出错),这些属性可以通过各种getter访问。如果我从构造函数中删除代码,我将有10个getter和复制粘贴代码,比如:

    1. 属性真的设置了吗?
    2. 执行一些IO操作以填充属性
    3. 返回相关变量的内容

    我不喜欢这样,因为我所有的getter都包含很多代码。相反,我在一个中心位置(构造函数)执行IO操作,并填充所有的属性。

    做这件事的正确方法是什么?

    8 回复  |  直到 17 年前
        1
  •  4
  •   Andy White    17 年前

    我不是Python开发人员,但一般来说,最好避免在构造函数中进行复杂/容易出错的操作。解决这个问题的一种方法是在类中放置一个“loadfromfile”或“init”方法来从外部源填充对象。然后,必须在构造对象之后单独调用此加载/初始化方法。

        2
  •  32
  •   Alan jiggunjer    11 年前

    C++中的构造函数与 __init__ 方法 在蟒蛇中。在C++中,构造函数的任务是构造一个对象。如果失败了, 未调用析构函数。因此,如果在 引发异常,应在退出构造函数之前完成清理。 因此,有些人更喜欢两阶段施工,大部分施工都已完成。 在建设者(ugh)之外。

    python有一个更干净的两阶段结构(构造,然后 初始化)。然而,许多人混淆了 爱因斯坦 方法(初始值设定项) 有一个构造函数。调用python中的实际构造函数 __new__ . 与C++不同,它不带实例,而是 返回一个。任务 爱因斯坦 是初始化创建的实例。 如果在中引发异常 爱因斯坦 ,析构函数 __del__ (如果有的话) 将按预期调用,因为此时对象已被创建(即使未正确初始化) 爱因斯坦 被叫来。

    回答您的问题:

    在python中,如果 “constructor”失败,对象结束 没有被定义。

    这不完全正确。如果 爱因斯坦 引发异常,对象是 已创建但未正确初始化(例如,某些属性 指定的)。但在它被提出的时候,你可能没有任何参考 这个对象,所以属性没有被分配的事实并不重要。只有析构函数(如果有)需要检查属性是否实际存在。

    做这件事的正确方法是什么?

    在python中,初始化 爱因斯坦 别担心例外。 在C++中,使用 RAII .


    更新 [关于资源管理]:

    在垃圾收集语言中,如果您处理的是资源,特别是有限的资源,如数据库连接,最好不要在析构函数中释放它们。 这是因为对象是以一种非确定性的方式被破坏的,如果发生这种情况, 要有一个引用循环(这并不总是容易辨别的),并且循环中至少有一个对象定义了析构函数,它们将永远不会被销毁。 垃圾收集语言还有其他处理资源的方法。在python中,它是 with statement .

        3
  •  20
  •   anon    17 年前

    至少在C++中,在构造函数中放置容易出错的代码并没有什么错误——如果发生错误,只需抛出异常即可。如果需要代码来正确地构造对象,那么实际上没有其他选择(尽管您可以将代码抽象为子函数,或者更好地抽象为子对象的构造函数)。最糟糕的做法是半构造对象,然后期望用户调用其他函数以某种方式完成构造。

        4
  •  4
  •   Ralph    17 年前

    从本质上来说,这并不是一个坏习惯。

    但我认为你可能在追求一种不同的东西。在您的示例中,当MyClass构造函数失败时,将不会调用doSomething()方法。尝试以下代码:

    class MyClass:
    def __init__(self, s):
        print s
        raise Exception("Exception")
    
    def doSomething(self):
        print "doSomething"
    
    try:
        someInstance = MyClass("test123")
        someInstance.doSomething()
    except:
        print "except"
    

    它应该打印:

    test123
    except
    

    对于您的软件设计,您可以问以下问题:

    • someInstance变量的范围应该是什么?它的用户是谁?他们的要求是什么?

    • 如果您的10个值中有一个不可用,那么应该在哪里以及如何处理错误?

    • 所有10个值应该在构建时缓存,还是在第一次需要时逐个缓存?

    • 是否可以将I/O代码重构为一个助手方法,这样做10次类似的操作不会导致代码重复?

        5
  •  3
  •   laalto    17 年前

    一种常见的模式是两阶段结构,安迪·怀特也提出了这种模式。

    第一阶段:常规构造函数。

    第二阶段:可能失败的操作。

    二者的集成:添加一个工厂方法来完成这两个阶段,并使构造函数受到保护/私有化,以防止在工厂方法之外进行实例化。

    哦,我不是Python开发人员。

        6
  •  0
  •   jerryjvl    17 年前

    如果初始化各种值的代码非常广泛,以至于复制它是不可取的(在您的情况下听起来是这样),我个人会选择将所需的初始化放入一个私有方法中,添加一个标志以指示初始化是否发生,并使所有访问器调用初始化方法(如果尚未初始化)。

    在线程场景中,您可能需要添加额外的保护,以防有效语义只允许进行一次初始化(这可能是,也可能不是,因为您处理的是一个文件)。

        7
  •  0
  •   Justin    17 年前

    再说一次,我对Python没有什么经验,但是在C语言中,最好尝试避免使用抛出异常的构造函数。之所以会想到这一点的一个例子是,如果您希望将构造函数放在一个不可能用try catch块包围它的点上,例如初始化类中的字段:

    class MyClass
    {
        MySecondClass = new MySecondClass();
        // Rest of class
    }
    

    如果mysecondclass的构造函数抛出了一个您希望在myclass内部处理的异常,那么您需要重构上面的异常——这肯定不是世界末日,而是一个很好的异常。

    在这种情况下,我的方法可能是将容易出错的初始化逻辑移动到初始化方法中,并让getter在返回任何值之前调用该初始化方法。

    作为优化,您应该让getter(或初始化方法)将某种“IsInitialized”布尔值设置为true,以指示不需要再次进行(可能代价高昂)初始化。

    在伪代码中(C因为我会把python的语法搞乱):

    class MyClass
    {
        private bool IsInitialised = false;
    
        private string myString;
    
        public void Init()
        {
            // Put initialisation code here
            this.IsInitialised = true;
        }
    
        public string MyString
        {
            get
            {
                if (!this.IsInitialised)
                {
                    this.Init();
                }
    
                return myString;
            }
        }
    }
    

    这当然不是线程安全的,但我不认为多线程是在Python中常用的,所以这对您来说可能是一个没有问题的问题。

        8
  •  0
  •   Tom    17 年前

    尼尔似乎有一个很好的观点:我的朋友刚刚指给我:

    http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

    这基本上就是尼尔说的…