文章的Python版本為3.5
namedtuple使用
這個函式用來建立tuple子類,可以用名稱欄位。例如下面的程式碼演示它的特效
- 從
collections
中匯入,函式第一個引數為類名,第二個為要定義的名稱欄位,它們用逗號分割,放入一個字串中。之後就能通過這個類來建立物件,程式碼演示中新建的類叫Someone
>>> from collections import namedtuple >>> Person = namedtuple('Person', 'name, age') # 此時Person這個類就有name,age兩個字元 >>> Someone = Person('ss', 29) # 新建一個name為ss,Someone >>> Someone = Person(name='ss', age=29) # 引數可以用key-value來表示 複製程式碼
__doc__
為新類的描述文件,由namedtuple新建>>> Person.__doc__ Person(name, age) 複製程式碼
- 因為是tuple的子類,可以直接通過索引來訪問屬性,也可以通過欄位名稱來訪問。同時能夠像tuple那樣解包賦值
>>> Someone[0] # 通過下標訪問 ss >>> Someone.name # 通過欄位名來訪問 ss >>> name, age = Someone ss 29 複製程式碼
- 可以轉換為
dict
,得到的是OrderedDict
。同樣通過一個dict
來構造Someone
這個我們定義的類>>> d = Someone._asdict() >>> d = { 'name': 'ss', 'age': 33 } >>> anothor = Someone(**d) # 將d解包後傳入建構函式 複製程式碼
- 使用類方法
_make
來生成一個Someone
物件>>> ss = Someone._make(['myname', 28]) >>> ss Person(name='myname', age=28) 複製程式碼
- 使用
_replace
來通過替換特定的欄位和值,返回一個新的物件>>> older = ss._replace(age=50) # 上一個函式_make建立的ss >>> older Person(name='myname', age=50) 複製程式碼
namedtuple實現
namedtuple函式使用的是超程式設計技術,通過傳入namedtuple(cls_name, field_names)
引數來定製類。
-
首先看
namedtuple
中對引數field_names
的處理。如果引數是字串,則將其分割成陣列,比如'name, age'
變為['name', 'age']
。同時引數可以是可迭代物件,也將其轉為字串陣列if isinstance(field_names, str): field_names = field_names.replace(',', ' ').split() field_names = list(map(str, field_names)) typename = str(typename) 複製程式碼
-
檢查傳入
field_names
是否符合要求,不能是Python關鍵詞,也不能是運算子。for name in [typename] + field_names: if type(name) != str: # 必須是字串 raise TypeError('Type names and field names must be strings') if not name.isidentifier(): # 不能是運算子 raise ValueError('Type names and field names must be valid ' 'identifiers: %r' % name) if _iskeyword(name): # 不能是Python關鍵詞 raise ValueError('Type names and field names cannot be a ' 'keyword: %r' % name) seen = set() for name in field_names: if name.startswith('_') and not rename: # 不能以'_'開頭 raise ValueError('Field names cannot start with an underscore: ' '%r' % name) if name in seen: # 不能重複定義識別符號 raise ValueError('Encountered duplicate field name: %r' % name) seen.add(name) 複製程式碼
-
在
namedtuple
定義了一個模板_class_template
,裡面包括前面提到的各種方法,下面是模板的程式碼:from builtins import property as _property, tuple as _tuple from operator import itemgetter as _itemgetter from collections import OrderedDict class {typename}(tuple): '{typename}({arg_list})' __slots__ = () _fields = {field_names!r} def __new__(_cls, {arg_list}): 'Create new instance of {typename}({arg_list})' return _tuple.__new__(_cls, ({arg_list})) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new {typename} object from a sequence or iterable' result = new(cls, iterable) if len(result) != {num_fields:d}: raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result)) return result def _replace(_self, **kwds): 'Return a new {typename} object replacing specified fields with new values' result = _self._make(map(kwds.pop, {field_names!r}, _self)) if kwds: raise ValueError('Got unexpected field names: %r' % list(kwds)) return result def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + '({repr_fmt})' % self def _asdict(self): 'Return a new OrderedDict which maps field names to their values.' return OrderedDict(zip(self._fields, self)) def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return tuple(self) {field_defs} 複製程式碼
舉一個例子:比如
Person = namedtuple('Person', 'name, age')
這個程式碼建立了Person類,其中包括name、age欄位,則上面的模板對應的欄位如下- typename: 'Person'
- arg_list: name, age
- field_names: 其中
!r
表示呼叫repr()
。相當於repr(("name", "age"))得到('name', 'age') - num_fields:
len(field_names)
=len(['name', 'age'])
為2 - repr_fmt: (name, age)
- field_defs: 後面介紹
可以看到
namedtuple
繼承於tuple
,這樣就能像tuple那樣作為可迭代物件。 在__new__
函式中呼叫tuple.__new__
返回tuple例項,在__new__
函式中第一個引數是類本身,餘下的引數與__init__
方法一樣,返回的例項作為__init__
方法的第一個引數self
-
那像
ss.name
這樣的取值操作是怎麼實現的呢,還是使用模板,不過是另一個模板_field_template
,定義了property,其中_property從from builtins import property as _property
匯入{name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}') 複製程式碼
上面這個在模板被填充後變為:
name = _property(_itemgetter(0), doc='Alias for field number 0') age = _property(_itemgetter(1), doc='Alias for field number 1') 複製程式碼
property
類原型為property(fget=None, fset=None, fdel=None, doc=None)
,通過itemgetter
定義了fget
,其他的方法都沒定義,這樣就只能是一個只讀欄位。itemgetter(0)
返回一個獲取第1個元素的選擇器。比如下面:>>> from operator import itemgetter >>> get = itemgetter(0) >>> get([1,2,3]) 1 複製程式碼
因此當我們呼叫
ss.name
時,實際是執行為ss[0]
-
需要將填充好的模板執行獲得真實的類
exec(class_definition, namespace) 複製程式碼
之後將其從namespace中得到
result = namespace[typename] result._source = class_definition 複製程式碼
最終得到result就是我們定製得到的類