【问题标题】:Problem creating new distinct class instance in python 2.7在 python 2.7 中创建新的不同类实例的问题
【发布时间】:2018-12-20 01:51:29
【问题描述】:

我有一个类,它本质上应该能够初始化自身并根据一行文本(字符串)设置内部值。 这在创建单个实例时似乎工作正常,但是,在创建第二个实例时,输入第一个实例的文本行显然被输入到第二个实例的一个内部变量中! 类的 init 构造函数定义了所有相关参数的默认值,这些参数传递给相应的内部变量。具体来说,“prefixComments”参数的默认值为 [],这意味着“self.PrefixComments”应该设置为相同的东西(一个空列表)......不幸的是,它显然被设置为文本行用于创建前一个对象(或者,至少,这是我的最佳猜测)。

我真的对这里发生的事情感到困惑。关于如何解决它的任何想法。代码和输出如下:

代码:

import collections
from collections import OrderedDict
import numpy as np
import string
import re
import gc

class AtomEntry:
    def __init__(self,atomName="",atomType="",charge=0.0,
                 comment="",prefixComments=[],
                 verbose=False):
        self.Name=atomName
        self.Type=atomType
        self.Charge=charge
        self.Comment=comment
        self.PrefixComments=prefixComments

    def from_line_string(self,line):
        #returns 1 if an error is encountered, 0 if successful
        lineTokens=line.split()
        if len(lineTokens)<4:
            print("Error: too few entries to construct ATOM record")
            return(1)
        elif lineTokens[0] != "ATOM":
            print("ERROR: atom entries must begin with the keyword 'ATOM'")
            return(1)
        else:
            self.Name=lineTokens[1]
            self.Type=lineTokens[2]
            self.Charge=float(lineTokens[3])
            if len(lineTokens) >=5:
                self.Comment=string.replace(
                    s=' '.join(lineTokens[5:len(lineTokens)]),
                    old='!',new='')
        return(0)

    def to_str(self,nameOnly=False):
        if nameOnly:
            return "%s"%(self.Name)
        else: 
            return repr(self)

    def __repr__(self):
        outStrs=self.PrefixComments
        outStrs.append(
            "ATOM %6s %6s %6.3f !%s"%(
                self.Name,self.Type,self.Charge,self.Comment))
        return ''.join(outStrs)

tempAtom1=AtomEntry()
tempAtom1.from_line_string("ATOM S1     SG2R50 -0.067 !   93.531")
print tempAtom1
print ""
gc.collect()
tempAtom2=AtomEntry()
tempAtom2.from_line_string("ATOM C1     CG2R53  0.443 !   83.436")
print tempAtom2
print""

print tempAtom2.Name
print tempAtom2.Type
print tempAtom2.Charge
print tempAtom2.Comment
print tempAtom2.PrefixComments

gc.collect()

输出:

ATOM     S1 SG2R50 -0.067 !93.531

ATOM     S1 SG2R50 -0.067 !93.531ATOM     C1 CG2R53  0.443 !83.436

C1
CG2R53
0.443
83.436
['ATOM     S1 SG2R50 -0.067 !93.531', 'ATOM     C1 CG2R53  0.443 !83.436']

【问题讨论】:

  • 请尽量发minimal reproducible example,强调Minimal
  • @Prune:实际上是两个不同的可变list 问题;一个匹配你的副本,但他们也有第二个问题。我已重新打开以确保不会错过该部分。
  • 既然我知道这是核心问题,是否应该将“mutablelist”关键字添加到这篇文章中?

标签: python class python-2.x


【解决方案1】:

您有两个问题,都与重用 lists 有关。一,您为prefixComments/self.PrefixComments 使用了一个可变的默认参数。 Don't do that。将初始化程序更改为:

def __init__(self,atomName="",atomType="",charge=0.0,
             comment="",prefixComments=(),
             verbose=False):
    self.Name=atomName
    self.Type=atomType
    self.Charge=charge
    self.Comment=comment
    self.PrefixComments = list(prefixComments)

首先避免接收可变参数,并将其显式浅复制到新的list,以便调用者的参数与属性取消链接。

其次,你的__repr__ 正在修改这个属性,所以__repr__ 不是幂等的;每次您调用它时,它都会不断增加。也解决这个问题:

def __repr__(self):
    outStrs=self.PrefixComments[:]  # Shallow copy the l
    outStrs.append(
        "ATOM %6s %6s %6.3f !%s"%(
            self.Name,self.Type,self.Charge,self.Comment))
    return ''.join(outStrs)

旁注:from_line_string 确实应该是一个替代构造函数,因此您可以直接使用它从字符串创建一个新实例,而不是只创建一个空白对象以在下一行重新初始化它。这是一个简单的修复;只需将其更改为解析的classmethod,然后调用常规构造函数(并在错误时引发异常,而不是使用容易错过错误的 C 样式返回代码):

# Makes sure this works either on the class or an instance of the class
# as a constructor of a brand new instance
@classmethod
def from_line_string(cls, line):
    # Returns a new instance, or raises an exception if an error is encountered
    lineTokens = line.split()
    if len(lineTokens) < 4:
        raise ValueError("too few entries to construct ATOM record")
    elif lineTokens[0] != "ATOM":
        raise ValueError("atom entries must begin with the keyword 'ATOM'")

    name=lineTokens[1]
    type=lineTokens[2]
    charge=float(lineTokens[3])
    # This works fine, producing an empty string, even if lineTokens is
    # doesn't have an index 5 or higher; comment will be the empty string
    comment = ' '.join(lineTokens[5:]).replace('!', '')
    return cls(name, type, charge, comment)

这将简化您的使用:

tempAtom1=AtomEntry()
tempAtom1.from_line_string("ATOM S1     SG2R50 -0.067 !   93.531")

到:

tempAtom1 = AtomEntry.from_line_string("ATOM S1     SG2R50 -0.067 !   93.531")

您可能还希望将__init__ 的大部分参数设为强制(没有默认值,除了commentprefixComment),因为其他三个属性是必需的,并且您不再需要空实例只是为了用from_line_string重新初始化它们。

【讨论】:

  • 令人着迷。所以基本上我设置为默认值的 [] 正在被使用和修改。没有意识到这可能会发生。很高兴知道。谢谢您的帮助。不过,对于最后一点,我实际上确实需要能够创建具有一些默认值和/或空白值的“虚拟”原子。我当然可以每次都喂它们,但这似乎很麻烦。当我从文件中读取它时,所有字段都需要作为数据完整性检查出现,但这将用于其他可能需要的地方,本质上是一个空的占位符。
  • 在这个 AtomEntry 类的情况下,使用 read_from_file 命令作为替代构造函数是完美的......但是,这对于更复杂的类如何工作,可能需要从多个迭代加载数据文件?例如。以动态方式建立原子列表。
  • @wmsmith:如果您将许多原子构建成一个 list,您将使用备用构造函数来生成每个原子,但不要加载整个文件(这不是备用构造函数,因为它没有创建该类的单个实例)。如果文件足够规则,则可以使用 read_from_file 备用构造函数读取固定数量的行(或仅一行)并将该行解析为单个实例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多