了解Python数据类

204 阅读4分钟

在Python 3.7版本中,引入了一个新特性:dataclasses

作为参考,一个类基本上是一个创建对象的蓝图。一个类的例子可以是一个国家,我们可以使用Country 类来创建各种实例,比如摩纳哥和冈比亚。

在初始化数值时,提供给构造函数的属性(如人口、语言等)被复制到每个对象实例中。

class Country:
    def __init__(self, name: str, population: int, continent: str, official_lang: str):
        self.name = name
        self.population = population
        self.continent = continent
        self.official_lang = official_lang


smallestEurope = Country("Monaco", 37623, "Europe")
smallestAsia= Country("Maldives", 552595, "Asia")
smallestAfrica= Country("Gambia", 2521126, "Africa") 

如果你曾经在Java和Python等编程语言中使用过面向对象的编程(OOP),那么你应该已经熟悉类了。

然而,一个dataclass ,已经实现了基本的类功能,减少了编写代码的时间。

在这篇文章中,我们将进一步深入了解什么是Python中的dataclasses ,如何操作对象字段,如何排序和比较dataclasses ,等等。

注意,由于这是在Python 3.7中发布的,你必须在本地机器上安装最新版本的Python才能使用它。

什么是Pythondataclass

如前所述,Pythondataclasses 与普通的类非常相似,但是实现了类的功能,大大减少了需要编写的模板代码的数量。

这种模板的一个例子是__init__ 方法。

Country 类的例子中,你可以看到我们不得不手动定义__init__ 方法,这个方法在你初始化类的时候被调用。现在,对于你定义的每一个普通类,你都需要提供这个函数,这意味着你必须写很多重复的代码。

Pythondataclass 中已经定义了这个方法。所以,你可以编写同样的Country 类,而不用手动定义构造函数。

在引擎盖下,当你用新属性初始化对象时,@dataclass 会调用这个方法。

请注意,__init__ 不是默认提供的唯一方法。其他的实用方法如__repr__ (表示),__lt__ (小于),__gt__ (大于),__eq__ (等于), 以及其他许多方法也是默认实现的。

使用普通的 Python 类

在 Python 中使用普通类时,我们有更长的代码来实现基本方法。

再考虑一下Country 这个类。在下面的代码块中,你可以看到几个方法,首先是__innit__ 方法。这个方法初始化了国家名称、人口数量、大陆和官方语言等属性,在Country 实例上。

__repr__ 返回一个类实例的字符串表示。这将每个类实例的属性以字符串的形式打印出来。

_lt_ True 比较两个 实例的人口数,如果现在的实例的人口数较少,则返回 ,如果它们都有相同的人口数,则返回 。Country True _eq_

class Country:
    def __init__(self, name: str, population: int, continent: str, official_lang: str="English" ):
        self.name = name
        self.population = population
        self.continent = continent
        self.official_lang= official_lang

   def __repr__(self):
        return(f"Country(name={self.name},
            population={self.population}, continent={self.continent},
            official_lang={self.official_lang})")

   def __lt__(self, other):
        return self.population < other.population

   def __eq__(self, other):
        return self.population == other.population


smallestAfrica= Country("Gambia", 2521126, "Africa", "English")
smallestEurope = Country("Monaco", 37623, "Europe", "French")
smallestAsia1= Country("Maldives", 552595, "Asia", "Dhivehi")
smallestAsia2= Country("Maldives", 552595, "Asia", "Dhivehi")


print(smallestAfrica) 
# Country(name='Gambia', population=2521126, continent='Africa', #official_lang='English')

print(smallestAsia < smallestAfrica) # True
print(smallestAsia > smallestAfrica) # False

使用Pythondataclass

要在你的代码中使用Python的dataclass ,只需导入该模块并在类的顶部注册@dataclass 装饰器。这就把基类的功能自动注入到我们的类中。

在下面的例子中,我们将创建同样的Country 类,但代码要少得多。

from dataclasses import dataclass

@dataclass(order=True)
class Country:
     name: str
     population: int
     continent: str
     official_lang: str

smallestAfrica= Country("Gambia", 2521126, "Africa", "English")
smallestEurope = Country("Monaco", 37623, "Europe", "French")
smallestAsia1= Country("Maldives", 552595, "Asia", "Dhivehi")
smallestAsia2= Country("Maldives", 552595, "Asia", "Dhivehi")

# Country(name='Gambia', population=2521126, continent='Africa', #official_lang='English')

print(smallestAsia1 == smallestAsia2) # True
print(smallestAsia < smallestAfrica) # False

请注意,我们没有在dataclass 上定义一个构造方法;我们只是定义了字段。

我们还省略了像repr__eq__ 这样的辅助工具。尽管省略了这些方法,这个类仍然可以正常运行。

注意,对于小于(<),dataclass 使用默认的方法来比较对象。在本文的后面,我们将学习如何定制对象比较以获得更好的结果。

使用field() 函数操纵对象字段

dataclass 模块还提供了一个名为field() 的函数。这个函数让你对类的字段有根深蒂固的控制,允许你按照自己的意愿来操纵和定制它们。

例如,我们可以在调用表示方法时排除continent 字段,方法是给它传递一个repr 参数并将其值设置为false

from dataclasses import dataclass, field

@dataclass
class Country:
     name: str
     population: int
     continent: str = field(repr=False) # omits the field
     official_lang: str

smallestEurope = Country("Monaco", 37623, "Europe", "French")

print(smallestEurope)

# Country(name='Monaco', population=37623, official_lang='French') 

然后这段代码在CLI中输出。

Output In CLI That Shows All Country Details But Omits The Continent Field

在默认情况下,repr 总是被设置为True

下面是一些可以被field() 的其他参数。

init 参数

init 参数通过指定一个属性是否应该在初始化过程中作为参数包括在构造器中。如果你把一个字段设置为innit=False ,那么你必须在初始化期间省略该属性。否则,将抛出一个TypeError

from dataclasses import dataclass, field

@dataclass
class Country:
     name: str
     population: int  
     continent: str
     official_lang: str = field(init=False) #Do not pass in this attribute in the constructor argument  


smallestEurope = Country("Monaco", 37623, "Europe", "English") #But you did, so error!

print(smallestEurope)

然后这段代码在CLI中输出。

init Parameter Rendering In The CLI

default 参数

default 参数是用来指定一个字段的默认值,以防在初始化过程中没有提供一个值。

from dataclasses import dataclass, field

@dataclass
class Country:
     name: str
     population: int  
     continent: str
     official_lang: str = field(default="English") # If you ommit value, English will be used


smallestEurope = Country("Monaco", 37623, "Europe") #Omitted, so English is used

print(smallestEurope)

这段代码随后在CLI中输出。

default Parameter Rendered in CLI To Specify A Default Value

repr 参数

repr 参数用来指定该字段是否应该包括(repr=True)或排除(repr=False)在字符串表示中,由__repr__ 方法生成。

from dataclasses import dataclass, field

@dataclass
class Country:
     name: str
     population: int  
     continent: str
     official_lang: str = field(repr=False) # This field will be excluded from string representation


smallestEurope = Country("Monaco", 37623, "Europe", "French") 

print(smallestEurope)

这段代码然后在CLI中输出。

repr Parameter Rendered In CLI To Included (repr=True) Or Excluded (repr=False) From The String Representation

在初始化后修改字段,用__post_init__

__post_init__ 方法是在初始化之后才被调用的。换句话说,它是在对象收到其字段的值后被调用的,比如name,continent,population, 和official_lang

例如,我们将使用该方法来确定我们是否要移民到一个国家,根据这个国家的官方语言。

from dataclasses import dataclass, field

@dataclass
class Country:
     name: str
     population: int
     continent: str = field(repr=False) # Excludes the continent field from string representation
     will_migrate: bool = field(init=False) # Initialize without will_migrate attribute
     official_lang: str = field(default="English") # Sets default language. Attributes with default values must appear last


     def __post_init__(self):
           if self.official_lang == "English":
                 self.will_migrate == True
           else:
                 self.will_migrate == False 

在对象初始化数值后,我们进行检查,看official_lang 字段是否从post_init 内设置为English 。如果是,我们必须将will_migrate 属性设置为true 。否则,我们将其设置为false

排序和比较dataclassessort_index

dataclasses 的另一个功能是为比较对象和对对象的列表进行排序创建一个自定义的顺序。

例如,我们可以根据两个国家的人口数量进行比较。换句话说,我们想说一个国家比另一个国家大,当且仅当它的人口数大于另一个国家。

from dataclasses import dataclass, field

@dataclass(order=True)
class Country:
     sort_index: int = field(init=False)
     name: str
     population: int = field(repr=True)
     continent: str 
     official_lang: str = field(default="English") #Sets default value for official language



     def __post_init__(self):
           self.sort_index = self.population

smallestEurope = Country("Monaco", 37623, "Europe")
smallestAsia= Country("Maldives", 552595, "Asia")
smallestAfrica= Country("Gambia", 2521126, "Africa") 

print(smallestAsia < smallestAfrica) # True
print(smallestAsia > smallestAfrica) # False

要在Pythondataclass 中启用比较和排序,你必须将order 属性与true 的值一起传递给@dataclass 。这样就可以启用默认的比较功能。

由于我们想通过人口数进行比较,我们必须在初始化后从__post_innit__ 方法中把population 字段传给sort_index 属性。

你也可以用一个特定的字段作为sort_index ,对一个对象的列表进行排序。例如,我们必须按照人口数量对一个国家的列表进行排序。

from dataclasses import dataclass, field

@dataclass(order=True)
class Country:
     sort_index: int = field(init=False)
     name: str
     population: int = field(repr=True)
     continent: str 
     official_lang: str = field(default="English")



     def __post_init__(self):
           self.sort_index = self.population



europe = Country("Monaco", 37623, "Europe", "French")
asia = Country("Maldives", 552595, "Asia", "Dhivehi")
africa = Country("Gambia", 2521126, "Africa", "English")
sAmerica = Country("Suriname", 539000, "South America", "Dutch")
nAmerica = Country("St Kits and Nevis", 55345, "North America", "English")
oceania = Country("Nauru", 11000, "Oceania", "Nauruan")  

mylist = [europe, asia, africa, sAmerica, nAmerica, oceania]
mylist.sort()

print(mylist) # This will return a list of countries sorted by population count, as shown below

然后这段代码在CLI中输出。

Using sort_index To Sort List Of Objects

不希望dataclass 被篡改?你可以通过简单地传递一个frozen=True 值给装饰器来冻结这个类。

from dataclasses import dataclass, field

@dataclass(order=True, frozen=True)
class Country:
     sort_index: int = field(init=False)
     name: str
     population: int = field(repr=True)
     continent: str 
     official_lang: str = field(default="English")



     def __post_init__(self):
           self.sort_index = self.population

结束语

Pythondataclass 是一个非常强大的功能,它极大地减少了类定义中的代码量。该模块提供了大部分已经实现的基本类方法。你可以自定义dataclass 中的字段并限制某些动作。

The postUnderstanding Python dataclassesappeared first onLogRocket Blog.