我遇到一个奇怪的问题,其中一个类具有score_card
属性,另一个类使用super().__init__
继承它,但对象实例表明它确实不拥有score_card
属性。
以下示例正常工作:
class ScoreCard:
def report(self, severity, message):
print('report.')
class Typeable:
@property
def type(self):
return self._type
@type.setter
def type(self, new_type):
self._type = new_type
class Node(Typeable):
def __init__(self, dic):
self.score_card = ScoreCard()
class Definition(Node):
def __init__(self, dic):
self.type = "definition"
super().__init__(dic)
self.score_card.report("critical", "NEEDS_NAME")
class Axiom(Definition):
def __init__(self, dic):
self.type = "axiom"
super().__init__(dic)
#######################
a = Axiom({})
但较大程序中的相同代码不工作。必须有一些完全出乎意料的事情发挥作用:
############################# IMPORTS ############################
import sys
from warnings import warn
import subprocess
import copy
import re
import json
from string import punctuation
from lib import helper
from lib.vote import Votable
from lib.config import ERR
######################## INTERNAL HELPERS ########################
class ScoreCard: # this will eventually be moved to its own file
def __init__(self):
# a stack of reports
self.stack = []
FAILING_SCORE = 40
SEVERITY_WORD_TO_NUMBER = {
"low": 5,
"medium": 10,
"high": 20,
"critical": FAILING_SCORE,
}
def severity_string_to_number(self, string):
assert isinstance(string, str)
words = string.split("-")
numbers = [self.SEVERITY_WORD_TO_NUMBER[word] for word in words]
number = sum(numbers) / len(numbers) # the average severity
return number
def report(self, severity_string, message):
severity_number = self.severity_string_to_number(severity_string)
self.stack.append((severity_number, message))
def total_score(self):
scores = [report[0] for report in self.stack]
return sum(scores)
# why is my score 0? Well, since there is no description, it didn't even attempt to set a descruption in the first place. Maybe the description should be an EMPTY STRING instead of not setting one. Or maybe some smarter way?
def is_passing(self):
return self.total_score() < self.FAILING_SCORE
def as_dict(self):
dic = self.as_dict()
# make sure d.stack is nice
# make sure is_passing is nice
# delete unneeded keys
return dic
def remove_outer_dunderscores(s):
# takes in a string like "__hi__", and returns "hi"
if len(s) >= 4 and s[:2] == "__" and s[-2:] == "__":
s = s[2:-2]
return s
def to_bash():
# include commands here to be executed in bash
bash_out = subprocess.check_output('ls; cd; ls', shell=True)
print(bash_out)
subprocess.call('mkdir test_folder', shell=True)
def move_attribute(dic, aliases, strict=True):
for key, value in dic.items():
if key in aliases:
del dic[key]
return value
if strict:
raise KeyError('Could not find any of the following keys: {} in the soon-to-be Node {}.'.format(aliases, dic))
else:
return None
def is_capitalized(value):
if re.search(r'[\$A-Z]', value): # starts with LaTeX or capital letter
return True
return False
def check_type_and_clean(value, value_type, list_of=False):
if list_of:
if type(value) is list:
clean_value = []
for el in value:
if type(el) is value_type:
if value_type is not str or el.strip() != '': # if value_type is str, we still exclude ''
clean_value.append(el)
elif (el is None or el.strip() == '') and value_type != 'NoneType' and value_type is not None:
pass # ignore the blank entry
else:
raise Exception('Element {} is not of type {}'.format(el, value_type))
value = clean_value
elif value is None:
value = [value_type()] # i'm afraid
else:
assert type(value) is value_type
value = [value]
else:
assert type(value) is value_type
return value
def find_key(dic, keys):
for key in dic:
if key in keys:
return key
raise KeyError('Could not find any of the following keys in the Node input: ' + str(keys))
def dunderscore_count(string):
dunderscore_list = re.findall(r'__', string)
return len(dunderscore_list)
def get_contents_of_dunderscores(string):
list_contents = re.findall(r'(?<=__)[^_]*(?=__)', string) # we only want the first one, but contents is a list
assert list_contents != [] and list_contents[0] != ""
return list_contents[0]
def reduce_string(string):
return re.sub(r'[_\W]', '', string).lower()
######################## EXTERNAL HELPERS ########################
def create_appropriate_node(dic):
# for writers that use shortcut method, we must seek out the type:
if not 'type' in dic:
dic['type'] = find_key(dic, {'axiom', 'definition', 'defn', 'def', 'theorem', 'thm', 'exercise'})
dic['description'] = move_attribute(dic, {'axiom', 'definition', 'defn', 'def', 'theorem', 'thm', 'exercise'})
if dic['type'] in {'definition', 'defn', 'def'}:
return Definition(dic)
elif dic['type'] == 'axiom':
return Axiom(dic)
elif dic['type'] in {'theorem', 'thm'}:
return Theorem(dic)
elif dic['type'] == 'exercise':
return Exercise(dic)
else:
raise ValueError('Cannot detect type of node. "type" key has value: ' + dic['type'])
def find_node_from_id(list_of_nodes, ID):
for node in list_of_nodes:
if node.id == ID:
return node
warn('Could node find node with ID "' + ID + '" within list_of_nodes.')
############################## MAIN ##############################
class Typeable: # created specifically for Node
@property
def type(self):
return self._type
@type.setter
def type(self, new_type):
clean_type = check_type_and_clean(new_type, str)
if clean_type in {'definition', 'defn', 'def'}:
self._type = 'definition'
elif clean_type in {'axiom'}:
self._type = 'axiom'
elif clean_type in {'theorem', 'thm'}:
self._type = 'theorem'
elif clean_type in {'exercise'}:
self._type = 'exercise'
else:
raise TypeError(ERR["BAD_TYPE"](clean_type))
class Node(Typeable, Votable):
MIN_IMPORTANCE = 1
MAX_IMPORTANCE = 10
# ALLOWED_ATTRIBUTES = ['name', 'id', 'type', 'importance', '_importance', 'description', 'intuitions', 'dependencies', 'examples', 'counterexamples', 'notes']
# Pass in a single json dictionary (dic) in order to convert to a node
def __init__(self, dic):
# clean the input dictionary
empty_keys = []
for key, value in dic.items():
if value == "":
empty_keys.append(key)
for key in empty_keys:
del dic[key]
# create a score card to score this node as it is initialized
self.score_card = ScoreCard()
print('score ME')
# populate node
self.description = move_attribute(dic, {'description', 'content'}, strict=False)
self.dependencies = move_attribute(dic, {'dependencies'}, strict=False)
self.importance = move_attribute(dic, {'importance', 'weight'}, strict=False)
self.intuitions = move_attribute(dic, {'intuitions', 'intuition'}, strict=False)
self.examples = move_attribute(dic, {'examples', 'example'}, strict=False)
self.counterexamples = move_attribute(dic, {'counterexample', 'counterexamples', 'counter example', 'counter examples'}, strict=False)
self.notes = move_attribute(dic, {'note', 'notes'}, strict=False)
self.name = move_attribute(dic, {'name'}, strict=False)
# we might want to freeze the score card here, or something
def validate_content_clean(self, value):
if type(value) is list:
for el in value:
self.validate_content_clean(el)
if type(value) is str:
# this needs to know WHICH method is the called. Description callers should get higher penalties, etc.
if not len(value) >= 15: # arbitary length to make sure person actually put some content in
self.score_card.report("low-medium", ERR["LENGTH_TOO_SHORT"])
if not is_capitalized(value):
# we need to move this ONLY to the things that need to be capitalized (for example, not names)
self.score_card.report("low", ERR["NOT_CAPITALIZED"])
return True
return True
def as_dict(self):
dic = self.__dict__
if 'score_card' in dic:
del dic['score_card']
return dic
def as_json(self): # returns json version of self
return json.dumps(self.as_dict())
def __str__(self):
return str(self.as_dict())
def __repr__(self):
return 'Node(' + self.__str__() + ')'
def __hash__(self):
return hash(self.as_dict().values())
def __eq__(self, other):
return self.id == other.id
def __ne__(self, other):
return not self.__eq__(other)
def clone(self):
return copy.deepcopy(self)
@property
def name(self):
if '_name' in self.as_dict():
return self._name
else:
return None
@name.setter
def name(self, new_name):
if new_name is None:
new_name = ""
if new_name is not None:
assert dunderscore_count(new_name) == 0
self._name = check_type_and_clean(new_name, str)
self.id = self.name
else:
self._name = check_type_and_clean(new_name, type(None))
@property
def id(self):
print(self)
return self._id
@id.setter
def id(self, new_id_before_reduction):
self._id = reduce_string(new_id_before_reduction)
@property
def importance(self):
return self._importance
@importance.setter
def importance(self, new_importance):
if new_importance is None:
new_importance = -1
if new_importance is not None:
if isinstance(new_importance, str):
new_importance = int(new_importance)
new_importance = check_type_and_clean(new_importance, int) # but in the future we will accept decimals (floats) too!
if new_importance < self.MIN_IMPORTANCE:
self.score_card.report("low", ERR["IMPORTANCE_TOO_LOW"](self, new_importance))
new_importance = max(self.MIN_IMPORTANCE, new_importance)
if new_importance > self.MAX_IMPORTANCE:
self.score_card.report("low", ERR["IMPORTANCE_TOO_HIGH"](self, new_importance))
new_importance = min(self.MAX_IMPORTANCE, new_importance)
self._importance = new_importance
@property
def description(self):
return self._description
@description.setter
def description(self, new_description):
if new_description is None:
new_description = ""
clean_description = check_type_and_clean(new_description, str)
self.validate_content_clean(clean_description)
self._description = clean_description
@property
def intuitions(self):
return self._intuitions
@intuitions.setter
def intuitions(self, new_intuitions):
clean_intuitions = check_type_and_clean(new_intuitions, str, list_of=True)
self.validate_content_clean(clean_intuitions)
for x in clean_intuitions:
if dunderscore_count(x) > 0:
self.score_card.report("low", ERR["DUNDERSCORES"](x))
self._intuitions = clean_intuitions
@property
def dependencies(self):
return self._dependencies
@dependencies.setter
def dependencies(self, new_dependencies):
cleaned_dependencies = check_type_and_clean(new_dependencies, str, list_of=True)
cleaned_dependencies = remove_outer_dunderscores(cleaned_dependencies)
for d in cleaned_dependencies:
if dunderscore_count(d) > 0:
self.score_card.report("critical", ERR["DUNDERSCORES"](d))
self._dependencies = cleaned_dependencies
@property
def dependency_ids(self):
return [reduce_string(x) for x in self.dependencies]
@property
def examples(self):
return self._examples
@examples.setter
def examples(self, new_examples):
cleaned_examples = check_type_and_clean(new_examples, str, list_of=True)
self.validate_content_clean(cleaned_examples)
for x in cleaned_examples:
if dunderscore_count(x) > 0:
self.score_card.report("low", ERR["DUNDERSCORES"](x))
self._examples = cleaned_examples
@property
def counterexamples(self):
return self._counterexamples
@counterexamples.setter
def counterexamples(self, new_counterexamples):
cleaned_counterexamples = check_type_and_clean(new_counterexamples, str, list_of=True)
self.validate_content_clean(cleaned_counterexamples)
for x in cleaned_counterexamples:
if dunderscore_count(x) > 0:
self.score_card.report("low", ERR["DUNDERSCORES"](x))
self._counterexamples = cleaned_counterexamples
@property
def notes(self):
return self._notes
@notes.setter
def notes(self, new_notes):
cleaned_notes = check_type_and_clean(new_notes, str, list_of=True)
self.validate_content_clean(cleaned_notes)
# notes may mention a synonym, so we will allow dunderscores (open to discussion)
self._notes = cleaned_notes
class Definition(Node):
# ALLOWED_ATTRIBUTES = ['name', 'id', 'type', 'importance', 'description', 'intuitions', 'dependencies', 'examples', 'counterexamples', 'notes', 'plurals']
def __init__(self, dic):
self.type = "definition"
super().__init__(dic)
self.plurals = move_attribute(dic, {'plurals', 'plural', 'pl'}, strict=False)
self.negation = move_attribute(dic, {'negation'}, strict=False)
if self.importance is None:
self.importance = 4
if self.description in [None, '']:
if self.name in [None, '']:
self.score_card.report("critical", NEEDS_NAME)
else:
if self.name in [None, ''] and dunderscore_count(self.description) < 2:
self.score_card.report("critical", NEEDS_NAME)
if self.name not in [None, ''] and dunderscore_count(self.description) < 2:
pass
if self.name in [None, ''] and dunderscore_count(self.description) >= 2:
print('GETTING')
self.name = get_contents_of_dunderscores(self.description)
@property
def plurals(self):
return self._plurals
@plurals.setter
def plurals(self, new_plurals):
if new_plurals is None:
self._plurals = []
else:
cleaned_plurals = check_type_and_clean(new_plurals, str, list_of=True)
self._plurals = cleaned_plurals
@property
def negation(self):
return self._negation
@negation.setter
def negation(self, new_negation):
if new_negation is None:
self._negation = None
else:
clean_negation = check_type_and_clean(new_negation, str)
clean_negation = remove_outer_dunderscores(clean_negation)
if dunderscore_count(clean_negation) > 0:
self.score_card.report("medium", ERR["DUNDERSCORES"](clean_negation))
self._negation = clean_negation
@Node.description.setter
def description(self, new_description):
if new_description is None:
new_description = ""
if dunderscore_count(new_description) < 2:
self.score_card.report("low", ERR["NO_DUNDERSCORES"](new_description))
Node.description.fset(self, new_description)
class Axiom(Definition):
def __init__(self, dic):
self.type = "axiom"
super().__init__(dic)
@property
def dependencies(self):
return []
@dependencies.setter
def dependencies(self, new_deps):
if new_deps is None or (isinstance(new_deps, list) and not new_deps):
self._dependencies = []
else:
if "justification" in dic:
# as Mo pointed out, axioms CAN have dependencies. Perhaps there are some definitions you create first, and then an AXIOM uses those definitions (but still introduces something you must accept on faith alone).
pass
else:
# now we will even allow the user to make axioms with deps, so the fset is below this block.
self.score_card.report("medium", ERR["AXIOM_WITH_DEPENDENCY"])
Node.dependencies.fset(self, new_deps)
class PreTheorem(Node):
# ALLOWED_ATTRIBUTES = ['name', 'id', 'type', 'importance', 'description', 'intuitions', 'dependencies', 'examples', 'counterexamples', 'notes', 'proofs']
def __init__(self, dic):
super().__init__(dic)
print('BELOW COMMENTED OUT TO DISABLE PROOFS')
# self.proofs = move_attribute(dic, {'proofs', 'proof'}, strict=False)
# theorems and exercises CANNOT have plurals: (but since we only created plural for Definitions, we shouldn't need this as long as there is a way to block undefined properties (see other Stack Overflow question))
if 'plurals' in self.as_dict():
raise KeyError('Theorems cannot have plurals.')
@property
def proofs(self):
return self._proofs
@proofs.setter
def proofs(self, new_proofs):
good_type_proofs = check_type_and_clean(new_proofs, dict, list_of=True)
self.validate_content_clean(good_type_proofs)
for proof in good_type_proofs:
proof['description'] = move_attribute(proof, {'description', 'content'}, strict=True)
if dunderscore_count(proof['description']) > 0:
self.score_card.report("low", ERR["DUNDERSCORES"](proof['description']))
if 'type' not in proof:
self.score_card.report("low", ERR["NO_PROOF_TYPE"])
# then give some default type to the proof
proof['type'] = None
proof['type'] = check_type_and_clean(proof['type'], str, list_of=True)
self._proofs = good_type_proofs
@Node.description.setter
def description(self, new_description):
if dunderscore_count(new_description) != 0:
self.score_card.report("medium-high", DUNDERSCORE(new_description))
Node.description.fset(self, new_description)
class Theorem(PreTheorem):
MIN_IMPORTANCE = 3
# ALLOWED_ATTRIBUTES = ['name', 'id', 'type', 'importance', 'description', 'intuitions', 'dependencies', 'examples', 'counterexamples', 'notes', 'proofs']
def __init__(self, dic):
self.type = "theorem"
super().__init__(dic)
if self.importance is None:
self.importance = 6
if self.name is None:
self.score_card.report("critical", ERR["NO_NAME"]) # maybe lower this if we can auto-assign a name
@PreTheorem.name.setter
def name(self, new_name):
if new_name is not None:
self.score_card.report("critical", ERR["NO_NAME"]) # maybe lower this if we can auto-assign a name
PreTheorem.name.fset(self, new_name)
class Exercise(PreTheorem):
MAX_IMPORTANCE = 3
# ALLOWED_ATTRIBUTES = ['name', 'id', 'type', 'importance', 'description', 'intuitions', 'dependencies', 'examples', 'counterexamples', 'notes', 'proofs']
def __init__(self, dic):
self.type = "exercise"
super().__init__(dic)
if self.importance is None:
self.importance = 1
@PreTheorem.description.setter
def description(self, new_description):
PreTheorem.description.fset(self, new_description)
if self.name is None:
self.id = self.description
我为何失踪而感到茫然。堆栈跟踪说:
Traceback (most recent call last):
File "pre-json_to_mongo.py", line 80, in <module>
"type": "axiom",
File "/Users/Matthew/programming/prove-math/server/lib/node.py", line 137, in create_appropriate_node
return Axiom(dic)
File "/Users/Matthew/programming/prove-math/server/lib/node.py", line 431, in __init__
super().__init__(dic)
File "/Users/Matthew/programming/prove-math/server/lib/node.py", line 382, in __init__
self.score_card.report("critical", NEEDS_NAME)
AttributeError: 'Axiom' object has no attribute 'score_card'
其他文件中的原始主叫行是:
dependency_node = create_appropriate_node({
"name": node.dependencies[i],
"type": "axiom",
})
答案 0 :(得分:5)
def as_dict(self):
dic = self.__dict__
if 'score_card' in dic:
del dic['score_card']
return dic
您删除了score_card
属性!这就是你得到AttributeError
的原因。不要那样做。