docs: def attr_func(*attrs, **kwargs):
      """Creates an "attribute function" for given attribute name(s).
  
      Resulting function will retrieve attributes with given names, in order,
      from the object that has been passed to it.
      For example, ``attr_func('a', 'b')(foo)`` yields the same as ``foo.a.b``
  
      :param attrs: Attribute names
      :param default: Optional keyword argument specifying default value
                      that will be returned when some attribute is not present
  
      :return: Unary attribute function
      """
      ensure_argcount(attrs, min_=1)
      ensure_keyword_args(kwargs, optional=('default',))
  
      # preprocess argument list:
      # * allow dots in arguments, interpreting them as multiple attributes,
      #   e.g. ``attr_func('a.b')`` as ``attr_func('a', 'b')``
      # * make sure the attribute names are valid Python identifiers
      attrs = map(ensure_string, attrs)
      attrs = flatten(attr.split('.') if '.' in attr else [attr]
                      for attr in attrs)
      for attr in attrs:
          if not is_identifier(attr):
              raise ValueError("'%s' is not a valid attribute name", attr)
  
      if 'default' in kwargs:
          default = kwargs['default']
          if len(attrs) == 1:
              getattrs = lambda obj: getattr(obj, attrs[0], default)
          else:
              def getattrs(obj):
                  for attr in attrs:
                      try:
                          obj = getattr(obj, attr)
                      except AttributeError:
                          return default
                  return obj
      else:
          if len(attrs) == 1:
              getattrs = operator.attrgetter(attrs[0])
          else:
              def getattrs(obj):
                  for attr in attrs:
                      obj = getattr(obj, attr)
                  return obj
  
      return getattrs