通过 "dir(obj)" 打印属性时获取 "AttributeError"

Getting "AttributeError" while printing attributes through "dir(obj)"

我正在尝试使用 dir() 命令打印 Python 中对象的所有属性。

    if self.play:
        for props in dir(self.play):
            f.write('\n%s:::%s'%(props, getattr(self.play, props)))

但是当我执行这个命令时,我得到,

AttributeError: su(除了回溯之外没有其他信息)在 f.write(...) 行。

因此,我在 Play class 中搜索名为 su 的属性,但我根本找不到它。

它对 su 的唯一引用是在它的初始化方法上(仅发布相关行),

class Play(object):
    def __init__(self, ds):
        self._ds = ds
        self.become = ds['su']

Play 没有覆盖 __dir__() 方法。

那么,为什么我会收到此错误以及如何避免它?

我正在使用 Python 2.7 如果相关的话,我对 Python 编程很陌生,所以请以新手的方式回答明白了。

编辑:

附上 Play class.

的堆栈跟踪和完整代码

错误:

Traceback (most recent call last):
  File "/usr/local/bin/ansible-playbook", line 5, in <module>
    pkg_resources.run_script('ansible==1.9.2', 'ansible-playbook')
  File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 528, in run_script
    self.require(requires)[0].run_script(script_name, ns)
  File "/usr/lib/python2.7/dist-packages/pkg_resources.py", line 1394, in run_script
    execfile(script_filename, namespace, namespace)
  File "/usr/local/lib/python2.7/dist-packages/ansible-1.9.2-py2.7.egg/EGG-INFO/scripts/ansible-playbook", line 324, in <module>
    sys.exit(main(sys.argv[1:]))
  File "/usr/local/lib/python2.7/dist-packages/ansible-1.9.2-py2.7.egg/EGG-INFO/scripts/ansible-playbook", line 264, in main
    pb.run()
  File "/usr/local/lib/python2.7/dist-packages/ansible-1.9.2-py2.7.egg/ansible/playbook/__init__.py", line 348, in run
    if not self._run_play(play):
  File "/usr/local/lib/python2.7/dist-packages/ansible-1.9.2-py2.7.egg/ansible/playbook/__init__.py", line 730, in _run_play
    self.callbacks.on_play_start(play.name)
  File "/usr/local/lib/python2.7/dist-packages/ansible-1.9.2-py2.7.egg/ansible/callbacks.py", line 720, in on_play_start
    call_callback_module('playbook_on_play_start', name)
  File "/usr/local/lib/python2.7/dist-packages/ansible-1.9.2-py2.7.egg/ansible/callbacks.py", line 179, in call_callback_module
    method(*args, **kwargs)
  File "/home/mobins/ansibletemp/callback_plugins/email_reporter.py", line 210, in playbook_on_play_start
    self.writeProp(f)
  File "/home/mobins/ansibletemp/callback_plugins/email_reporter.py", line 18, in writeProp
    f.write('\n%s:::%s'%(props, getattr(self.play, props)))
AttributeError: su

播放Class:

class Play(object):

    _pb_common = [
        'accelerate', 'accelerate_ipv6', 'accelerate_port', 'any_errors_fatal', 'become',
        'become_method', 'become_user', 'environment', 'force_handlers', 'gather_facts',
        'handlers', 'hosts', 'name', 'no_log', 'remote_user', 'roles', 'serial', 'su', 
        'su_user', 'sudo', 'sudo_user', 'tags', 'vars', 'vars_files', 'vars_prompt', 
        'vault_password',
    ]

    __slots__ = _pb_common + [
        '_ds', '_handlers', '_play_hosts', '_tasks', 'any_errors_fatal', 'basedir',
        'default_vars', 'included_roles', 'max_fail_pct', 'playbook', 'remote_port',
        'role_vars', 'transport', 'vars_file_vars',
    ]

    # to catch typos and so forth -- these are userland names
    # and don't line up 1:1 with how they are stored
    VALID_KEYS = frozenset(_pb_common + [
        'connection', 'include', 'max_fail_percentage', 'port', 'post_tasks',
        'pre_tasks', 'role_names', 'tasks', 'user',
    ])

    # *************************************************

    def __init__(self, playbook, ds, basedir, vault_password=None):
        ''' constructor loads from a play datastructure '''

        for x in ds.keys():
            if not x in Play.VALID_KEYS:
                raise errors.AnsibleError("%s is not a legal parameter of an Ansible Play" % x)

        # allow all playbook keys to be set by --extra-vars
        self.vars             = ds.get('vars', {})
        self.vars_prompt      = ds.get('vars_prompt', {})
        self.playbook         = playbook
        self.vars             = self._get_vars()
        self.vars_file_vars   = dict() # these are vars read in from vars_files:
        self.role_vars        = dict() # these are vars read in from vars/main.yml files in roles
        self.basedir          = basedir
        self.roles            = ds.get('roles', None)
        self.tags             = ds.get('tags', None)
        self.vault_password   = vault_password
        self.environment      = ds.get('environment', {})

        if self.tags is None:
            self.tags = []
        elif type(self.tags) in [ str, unicode ]:
            self.tags = self.tags.split(",")
        elif type(self.tags) != list:
            self.tags = []

        # make sure we have some special internal variables set, which
        # we use later when loading tasks and handlers
        load_vars = dict()
        load_vars['playbook_dir'] = os.path.abspath(self.basedir)
        if self.playbook.inventory.basedir() is not None:
            load_vars['inventory_dir'] = self.playbook.inventory.basedir()
        if self.playbook.inventory.src() is not None:
            load_vars['inventory_file'] = self.playbook.inventory.src()

        # We first load the vars files from the datastructure
        # so we have the default variables to pass into the roles
        self.vars_files = ds.get('vars_files', [])
        if not isinstance(self.vars_files, list):
            raise errors.AnsibleError('vars_files must be a list')
        processed_vars_files = self._update_vars_files_for_host(None)

        # now we load the roles into the datastructure
        self.included_roles = []
        ds = self._load_roles(self.roles, ds)

        # and finally re-process the vars files as they may have been updated
        # by the included roles, but exclude any which have been processed
        self.vars_files = utils.list_difference(ds.get('vars_files', []), processed_vars_files)
        if not isinstance(self.vars_files, list):
            raise errors.AnsibleError('vars_files must be a list')

        self._update_vars_files_for_host(None)

        # template everything to be efficient, but do not pre-mature template
        # tasks/handlers as they may have inventory scope overrides. We also
        # create a set of temporary variables for templating, so we don't
        # trample on the existing vars structures
        _tasks    = ds.pop('tasks', [])
        _handlers = ds.pop('handlers', [])

        temp_vars = utils.combine_vars(self.vars, self.vars_file_vars)
        temp_vars = utils.combine_vars(temp_vars, self.playbook.extra_vars)

        try:
            ds = template(basedir, ds, temp_vars)
        except errors.AnsibleError, e:
            utils.warning("non fatal error while trying to template play variables: %s" % (str(e)))

        ds['tasks'] = _tasks
        ds['handlers'] = _handlers

        self._ds = ds

        hosts = ds.get('hosts')
        if hosts is None:
            raise errors.AnsibleError('hosts declaration is required')
        elif isinstance(hosts, list):
            try:
                hosts = ';'.join(hosts)
            except TypeError,e:
                raise errors.AnsibleError('improper host declaration: %s' % str(e))

        self.serial           = str(ds.get('serial', 0))
        self.hosts            = hosts
        self.name             = ds.get('name', self.hosts)
        self._tasks           = ds.get('tasks', [])
        self._handlers        = ds.get('handlers', [])
        self.remote_user      = ds.get('remote_user', ds.get('user', self.playbook.remote_user))
        self.remote_port      = ds.get('port', self.playbook.remote_port)
        self.transport        = ds.get('connection', self.playbook.transport)
        self.remote_port      = self.remote_port
        self.any_errors_fatal = utils.boolean(ds.get('any_errors_fatal', 'false'))
        self.accelerate       = utils.boolean(ds.get('accelerate', 'false'))
        self.accelerate_port  = ds.get('accelerate_port', None)
        self.accelerate_ipv6  = ds.get('accelerate_ipv6', False)
        self.max_fail_pct     = int(ds.get('max_fail_percentage', 100))
        self.no_log           = utils.boolean(ds.get('no_log', 'false'))
        self.force_handlers   = utils.boolean(ds.get('force_handlers', self.playbook.force_handlers))

        # Fail out if user specifies conflicting privelege escalations
        if (ds.get('become') or ds.get('become_user')) and (ds.get('sudo') or ds.get('sudo_user')):
            raise errors.AnsibleError('sudo params ("become", "become_user") and su params ("sudo", "sudo_user") cannot be used together')
        if (ds.get('become') or ds.get('become_user')) and (ds.get('su') or ds.get('su_user')):
            raise errors.AnsibleError('sudo params ("become", "become_user") and su params ("su", "su_user") cannot be used together')
        if (ds.get('sudo') or ds.get('sudo_user')) and (ds.get('su') or ds.get('su_user')):
            raise errors.AnsibleError('sudo params ("sudo", "sudo_user") and su params ("su", "su_user") cannot be used together')

        # become settings are inherited and updated normally
        self.become           = ds.get('become', self.playbook.become)
        self.become_method    = ds.get('become_method', self.playbook.become_method)
        self.become_user      = ds.get('become_user', self.playbook.become_user)

        # Make sure current play settings are reflected in become fields
        if 'sudo' in ds:
            self.become=ds['sudo']
            self.become_method='sudo'
            if 'sudo_user' in ds:
                self.become_user=ds['sudo_user']
        elif 'su' in ds:
            self.become=True
            self.become=ds['su']
            self.become_method='su'
            if 'su_user' in ds:
                self.become_user=ds['su_user']

        # gather_facts is not a simple boolean, as None means  that a 'smart'
        # fact gathering mode will be used, so we need to be careful here as
        # calling utils.boolean(None) returns False
        self.gather_facts = ds.get('gather_facts', None)
        if self.gather_facts is not None:
            self.gather_facts = utils.boolean(self.gather_facts)

        load_vars['role_names'] = ds.get('role_names', [])

        self._tasks      = self._load_tasks(self._ds.get('tasks', []), load_vars)
        self._handlers   = self._load_tasks(self._ds.get('handlers', []), load_vars)

        # apply any missing tags to role tasks
        self._late_merge_role_tags()

        # place holder for the discovered hosts to be used in this play
        self._play_hosts = None

如您所料,Play class 来自一个名为 ansible 的工具,因为我不太了解它的代码和这是调用结构,我在调试它时遇到了困难。

我只是希望这个错误在使用 dir() 时很常见,并且有人已经遇到过它。但如果情况并非如此,我想我可能应该使用其他方法,而不用太担心调试这个问题。

当您定义 __slots__ 时,即使您没有为它们分配任何值,属性也会显示:

>>> class foo(object):
...     __slots__ = 'a', 'b'
... 
>>> x = foo()
>>> dir(x)
[..., 'a', 'b']

忽略未设置属性的简单方法是使用默认参数 getattr()。如果 None 是任何变量的有效值(可能),那么您可以创建一个特殊的 Undefined class 仅用于显示尚未设置变量:

>>> class Undefined:
...      pass
... 
>>> for name in dir(x):
...     value = getattr(x, name, Undefined)  # Default to Undefined
...     print name, value
... 
__class__ <class '__main__.foo'>
__delattr__ <method-wrapper '__delattr__' of foo object at 0xb6a5e1cc>
__doc__ None
__format__ <built-in method __format__ of foo object at 0xb6a5e1cc>
__getattribute__ <method-wrapper '__getattribute__' of foo object at 0xb6a5e1cc>
__hash__ <method-wrapper '__hash__' of foo object at 0xb6a5e1cc>
__init__ <method-wrapper '__init__' of foo object at 0xb6a5e1cc>
__module__ __main__
__new__ <built-in method __new__ of type object at 0x8335200>
__reduce__ <built-in method __reduce__ of foo object at 0xb6a5e1cc>
__reduce_ex__ <built-in method __reduce_ex__ of foo object at 0xb6a5e1cc>
__repr__ <method-wrapper '__repr__' of foo object at 0xb6a5e1cc>
__setattr__ <method-wrapper '__setattr__' of foo object at 0xb6a5e1cc>
__sizeof__ <built-in method __sizeof__ of foo object at 0xb6a5e1cc>
__slots__ ('a', 'b')
__str__ <method-wrapper '__str__' of foo object at 0xb6a5e1cc>
__subclasshook__ <built-in method __subclasshook__ of type object at 0xb6d9d82c>
a __main__.Undefined
b __main__.Undefined

如果 value is Undefined.

,您将可以轻松地在循环中进行测试以忽略任何结果