通过属性名或索引选项访问的结构

7 投票
3 回答
2404 浏览
提问于 2025-04-15 20:50

我刚开始学习Python,想弄明白怎么创建一个对象,这个对象的值可以通过属性名或者索引来访问。比如,os.stat()返回的stat_result或者pwd.getpwnam()返回的struct_passwd。

在尝试理解这个问题时,我只找到了一些C语言的实现,没看到Python里具体的例子。请问在Python中,创建这种对象的标准方法是什么呢?

如果这个问题已经被广泛讨论过,我很抱歉。我在寻找答案的过程中,可能遗漏了一些基本概念,所以找不到合适的答案。

3 个回答

0

一个对象可以通过属性名或者索引来访问它的值。

我不太明白你觉得哪里难。

通过索引可以访问的集合会实现 __getitem__ 这个功能。

通过名字可以访问的集合会实现 __getattr__(或者 __getattribute__)。

你可以很轻松地同时实现这两种功能。或者,你也可以使用 namedtuple

为了让事情更简单,你可以扩展 tuple 类,这样就不需要自己实现 __getitem__ 了。或者你可以定义一个普通的类,同时实现 __getitem__,这样就不需要去处理 __getattr__ 了。

比如说:

>>> class Foo( object ):
...     def __init__( self, x, y, z ):
...         self.x= x
...         self.y= y
...         self.z= z
...     def __getitem__( self, index ):
...         return { 0: self.x, 1: self.y, 2: self.z }[index]
... 
>>> f= Foo(1,2,3)
>>> f.x
1
>>> f[0]
1
>>> f[1]
2
>>> f[2]
3
>>> f.y
2
5

Python 2.6 版本引入了一个叫做 collections.namedtuple 的功能,让这件事变得简单多了。如果你用的是旧版本的 Python,可以参考这个 命名元组的做法

以下是文档中的直接引用:

>>> Point = namedtuple('Point', 'x y')
>>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
>>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
33
>>> x, y = p                # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y               # fields also accessible by name
33
>>> p                       # readable __repr__ with a name=value style
Point(x=11, y=22)
3

你不能用os.stat()等函数的结果对象的相同实现。不过,Python 2.6引入了一个新的工厂函数,可以创建一种叫做命名元组(named tuple)的类似数据类型。命名元组是一种元组,它的每个位置不仅可以通过位置索引访问,还可以通过名字来访问。根据文档,命名元组的内存占用和普通元组一样,因为它们没有每个实例的字典。这个工厂函数的用法是:

collections.namedtuple(typename, field_names[, verbose])  

第一个参数是新类型的名称,第二个参数是一个字符串(用空格或逗号分隔),包含字段名称,最后,如果verbose设置为真,工厂函数还会打印生成的类。

举个例子

假设你有一个元组,里面包含用户名和密码。要访问用户名,你可以通过位置0来获取,而密码则通过位置1来访问:

credential = ('joeuser', 'secret123')  
print 'Username:', credential[0]  
print 'Password:', credential[1]  

这段代码没有问题,但元组的内容不太容易理解。你需要去查文档,了解元组中字段的位置。这时候,命名元组就能派上用场了。我们可以把之前的例子改写成这样:

import collections  
# Create a new sub-tuple named Credential  
Credential = collections.namedtuple('Credential', 'username, password')  

credential = Credential(username='joeuser', password='secret123')  

print 'Username:', credential.username  
print 'Password:', credential.password  

如果你想看看新创建的Credential类型的代码长什么样,可以在创建类型时把verbose=True加到参数列表中,这样我们就能得到以下输出:

import collections  
Credential = collections.namedtuple('Credential', 'username, password', verbose=True)  

class Credential(tuple):                                       
    'Credential(username, password)'                       

    __slots__ = ()   

    _fields = ('username', 'password')   

    def __new__(_cls, username, password):  
        return _tuple.__new__(_cls, (username, password))   

    @classmethod  
    def _make(cls, iterable, new=tuple.__new__, len=len):  
        'Make a new Credential object from a sequence or iterable'  
        result = new(cls, iterable)                                 
        if len(result) != 2:                                        
            raise TypeError('Expected 2 arguments, got %d' % len(result))  
        return result  

    def __repr__(self):  
        return 'Credential(username=%r, password=%r)' % self  

    def _asdict(t):  
        'Return a new dict which maps field names to their values'  
        return {'username': t[0], 'password': t[1]}  

    def _replace(_self, **kwds):  
        'Return a new Credential object replacing specified fields with new values'  
        result = _self._make(map(kwds.pop, ('username', 'password'), _self))  
        if kwds:  
            raise ValueError('Got unexpected field names: %r' % kwds.keys())  
        return result  

    def __getnewargs__(self):  
        return tuple(self)  

    username = _property(_itemgetter(0))  
    password = _property(_itemgetter(1))  

命名元组不仅可以通过名字访问字段,还包含一些辅助函数,比如_make()函数,可以帮助从一个序列或可迭代对象创建一个Credential实例。例如:

cred_tuple = ('joeuser', 'secret123')  
credential = Credential._make(cred_tuple) 

关于命名元组的Python库文档里有更多信息和代码示例,所以我建议你去看看。

撰写回答