《Python面向对象编程指南》——2.2 

    xiaoxiao2024-01-30  150

    本节书摘来自异步社区《Python面向对象编程指南》一书中的第2章,第2.2节,作者[美]Steven F. Lott, 张心韬 兰亮 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。

    2.2 __format__()方法

    string.format()和内置的format()函数都使用了__format__()方法。它们都是为了获得给定对象的一个符合要求的字符串表示。

    下面是给__format__()传参的两种方式。

    someobject.__format__(""):当应用程序中出现format(someobject)或者"{0}".format(someobject)时,会默认以这种方式调用__format__()。在这些情况下,会传递一个空字符串,__format__()的返回值会以默认格式表示。 someobject.__format__(specification):当应用程序中出现format (someobject, specification)或者"{0:specification}".format (someobject)"时,会默认以这种方式调用__format__()。

    注意,"{0!r}".format()和"{0!s}".format()并不会调用__format__()方法。它们会直接调用__repr__()或者__str__()。

    当specification是""时,一种合理的返回值是return str(self),这为各种对象的字符串表示形式提供了明确的一致性。

    在一个格式化字符串中,":"之后的文本都属于格式规范。当我们写"{0:06.4f}"时,06.4f是应用在项目0上的格式规范。

    Python标准库的6.1.3.1节定义了一个复杂的数值规范,它是一个包括9个部分的字符串。这就是格式规范的基本语法,它的语法如下。

    [[fill]align][sign][#][0][width][,][.precision][type]

    这些规范的正则表示如下。

    re.compile( r"(?P<fill_align>.?[\<\>=\^])?" "(?P<sign>[-+ ])?" "(?P<alt>#)?" "(?P<padding>0)?" "(?P<width>\d*)" "(?P<comma>,)?" "(?P<precision>\.\d*)?" "(?P<type>[bcdeEfFgGnosxX%])?" )

    这个正则表达式将规范分解为8个部分。第1部分同时包括了原本规范中的fill和alignment字段。我们可以利用它们定义我们的类中的数值类型的格式。

    但是,Python格式规范的语法有可能不能很好地应用到我们之前定义的类上。所以,我们可能需要定义我们自己的规范化语法,并且使用我们自己的__format__()方法来处理它。如果我们定义的是数值类型,那么我们应该使用Python中内建的语法。但是,对于其他类型,没有理由坚持使用预定义的语法。

    例如,下面是我们自定义的一个微型语言,用%r来表示rank,用%s来表示suit,用%代替%%,所有其他的文本保持不变。

    我们可以用下面的格式化方法扩展Card类。

    def __format__( self, format_spec ):   if format_spec == "":     return str(self)   rs= format_spec.replace("%r",self.rank).replace("%s",self.suit)   rs= rs.replace("%%","%")   return rs

    方法签名中,需要一个format_spec作为格式规范参数。如果没有提供这个参数,那么就会使用str()函数来返回结果。如果提供了格式规范参数,就会用rank、suit和%字符替换规范中对应的部分,来生成最后的结果。

    这允许我们使用下面的方法来格式化牌。

    print( "Dealer Has {0:%r of %s}".format( hand.dealer_card) )

    其中,("%r of %s")作为格式化参数传入__format__()方法。通过这种方式,我们能够为描述自定义对象提供统一的接口。

    或者,我们可以用下面的方法:

    default_format= "some specification" def __str__( self ):   return self.__format__( self.default_format ) def __format__( self, format_spec ):   if format_spec == "": format_spec = self.default_format   # process the format specification.

    这种方法的优点是把所有与字符串表示相关的逻辑放在__format__()方法中,而不是分别写在__format__()和__str__()里。但是,这样做有一个缺点,因为并非每次都需要实现__format__()方法,但是我们总是需要实现__str__()。

    2.2.1 内嵌格式规范

    string.format()方法可以处理{}中内嵌的实例,替换其中的关键字,生成新的格式规范。这种替换是为了生成最后传入__format__()中的格式化字符串。通过使用这种内嵌的替换,我们可以使用一种更加简单的带参数的更加通用的格式规范,而不是使用相对复杂的数值格式。

    下面是使用内嵌格式规范的一个例子,它让format参数中的width更容易改变:

    width=6 for hand,count in statistics.items():   print( "{hand}{count:{width}d}".format(hand=hand,count=count,width= width) )

    我们定义了一个通用的格式,"{hand}{count:{width}d}",它需要一个width参数,才算是一个正确的格式规范。

    通过width=参数提供的值会被用来替换{width}。替换完成后,完整的格式化字符串会作为__format__()方法的参数使用。

    2.2.2 集合和委托格式规范

    当格式化一个包含集合的对象时,我们有两个难题:如何格式化整个对象和如何格式化集合中的对象。以Hand为例,其中包含了Cards类的集合。我们会更希望可以将Hand中一部分格式化的逻辑委托给Card实例完成。

    下面是Hand中的format()方法。

    def __format__( self, format_specification ):   if format_specification == "":     return str(self)   return ", ".join( "{0:{fs}}".format(c, fs=format_specification)     for c in self.cards )

    Hand集合中的每个Card实例都会使用format_specification参数。对于每一个Card对象,都会使用内嵌格式规范的方法,用format_specification创建基于"{0:{fs}}"的格式。通过这样的方法,一个Hand对象,player_hand,可以以下面的方法格式化:

    "Player: {hand:%r%s}".format(hand=player_hand)

    这会将%r%s格式规范应用在Hand对象中的每个Card实例上。

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)