zoukankan      html  css  js  c++  java
  • python数据类

    前言

    之前有写过一篇python元类的笔记,元类主要作用就是在要创建的类中使用参数metaclass=YourMetaclass调用自定义的元类,这样就可以为所有调用了这个元类的类添加相同的属性了。

    有需要查看链接:https://mp.weixin.qq.com/s/gmR_pggxeitPWKmnbo1usw

    本篇笔记主要是对dataclass的特性作了解和对参考文章的总结摘要,完整文章地址:https://realpython.com/python-data-classes/

    python数据类初识

    用docker拉个python:3.7的镜像作为实验环境

    1. 使用dataclass装饰器创建数据类
    >>> from dataclasses import dataclass
    >>> @dataclass
    ... class DataClassTest:
    ...     first_name: str
    ...     last_name: str
    ... 
    >>> p = DataClassTest('vickey', 'wu')
    >>> p.first_name
    'vickey'
    >>> p.last_name
    'wu'
    >>> p
    DataClassTest(first_name='vickey', last_name='wu')
    >>> p == DataClassTest('vickey', 'wu')
    True
    >>> p.first_name = 'wiki'
    >>> p
    DataClassTest(first_name='wiki', last_name='wu')
    

    从上面例子可以看到,如果使用dataclass装饰器来定义数据类,则必须声明参数类型,数据类默认可以修改参数的值类型,如果不希望更改则使用@dataclass(frozen=True)即可,这样上面的 参数值就不可更改了,更改会报错dataclasses.FrozenInstanceError: cannot assign to field 'first_name'

    当不确定参数到底用哪种类型,或可以是多种类型时则可以用下面的Any来声明

    >>> from dataclasses import dataclass
    >>> from typing import Any
    >>> @dataclass
    ... class W:
    ...     n:Any
    ...     v: float = 18
    ... 
    >>> w = W('vickey')
    >>> w
    W(n='vickey', v=18)
    >>> w = W(19)
    >>> w
    W(n=19, v=18)
    
    1. 不使用dataclass装饰器的普通类
    >>> class RegularClassTest:
    ...     def __init__(self, first_name, last_name):
    ...         self.first_name = first_name
    ...         self.last_name = last_name
    ... 
    >>> pp = RegularClassTest('vickey', 'wu')
    >>> pp.first_name
    'vickey'
    >>> pp.last_name
    'wu'
    >>> pp
    <__main__.RegularClassTest object at 0x7f5f66a49550>
    >>> pp == RegularClassTest('vickey', 'wu')
    False
    

    从1和2两个例子对比可以看出,使用@dataclass后有几个优势(不限于此):

    • 无需定义__init__函数,只需定义参数及参数类型即可。
    • 打印出来的对象描述信息更清晰了。而未使用dataclass的类需要再添加__repr__函数显示才友好。(看下面的例子)
    • 实例化后的实例可以用==判断出是否与类实例相等,而未使用dataclass的类需要再添加__eq__函数才能判断。(看下面的例子)

    3 不使用dataclass装饰器实现数据类相同功能

    >>> class RegularClassTest2:
    ...     def __init__(self, first_name, last_name):
    ...         self.first_name = first_name
    ...         self.last_name = last_name
    ...     def __repr__(self):
    ...         return (f'{self.__class__.__name__}'
    ...                 f'(first_name={self.first_name!r}, last_name={self.last_name!r})')
    ...     def __eq__(self, other):
    ...         if other.__class__ is not self.__class__:
    ...             return NotImplemented
    ...         return (self.first_name, self.last_name) == (other.first_name, other.last_name)
    ... 
    >>> r = RegularClassTest2('2', '1')
    >>> r
    RegularCard(first_name='2', last_name='1')
    >>> r == RegularClassTest2('2', '1')
    True
    >>> 
    

    通过在普通类中添加__repr____eq__就可以具有上面提到的数据类的第2,3个优势,但还是需要__init__函数。虽然上面提到不使用dataclass也可以达到部分效果,参考文章作者也说明了各自的好处与不足,感兴趣的童鞋查看原文,这里就不记录了。

    数据类参数调用函数赋值

    from dataclasses import dataclass, field
    from typing import List
    
    # 数据类rank参数为牌大小,suit为花色
    @dataclass
    class PlayingCard:
        rank: str
        suit: str
    
    
    # 生成13牌的4种花色
    RANKS = '2 3 4 5 6 7 8 9 10 J Q K A'.split()
    SUITS = '♣ ♢ ♡ ♠'.split()
    
    def make_french_deck():
        print([PlayingCard(r, s) for s in SUITS for r in RANKS])
        print('################## list generated by fuction make_french_deck')
        return [PlayingCard(r, s) for s in SUITS for r in RANKS]
    
    
    
    # 参考源码typing.List
    # List(yourclass):https://docs.python.org/3/library/typing.html#typing.ForwardRef
    # 使用field的default_factory调用参数名为make_french_deck的函数,这个函数会生成一个list,然后赋值参数cards
    @dataclass
    class Deck:
        cards: List[PlayingCard] = field(default_factory=make_french_deck)
    
    
    print('################# called class Deck with para cards')
    print(Deck())
    
    • output
    ################# called class Deck with para cards
    [PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), PlayingCard(rank='4', suit='♣'), PlayingCard(rank='5', suit='♣'), PlayingCard(rank='6', suit='♣'), PlayingCard(rank='7', suit='♣'), PlayingCard(rank='8', suit='♣'), PlayingCard(rank='9', suit='♣'), PlayingCard(rank='10', suit='♣'), PlayingCard(rank='J', suit='♣'), PlayingCard(rank='Q', suit='♣'), PlayingCard(rank='K', suit='♣'), PlayingCard(rank='A', suit='♣'), PlayingCard(rank='2', suit='♢'), PlayingCard(rank='3', suit='♢'), PlayingCard(rank='4', suit='♢'), PlayingCard(rank='5', suit='♢'), PlayingCard(rank='6', suit='♢'), PlayingCard(rank='7', suit='♢'), PlayingCard(rank='8', suit='♢'), PlayingCard(rank='9', suit='♢'), PlayingCard(rank='10', suit='♢'), PlayingCard(rank='J', suit='♢'), PlayingCard(rank='Q', suit='♢'), PlayingCard(rank='K', suit='♢'), PlayingCard(rank='A', suit='♢'), PlayingCard(rank='2', suit='♡'), PlayingCard(rank='3', suit='♡'), PlayingCard(rank='4', suit='♡'), PlayingCard(rank='5', suit='♡'), PlayingCard(rank='6', suit='♡'), PlayingCard(rank='7', suit='♡'), PlayingCard(rank='8', suit='♡'), PlayingCard(rank='9', suit='♡'), PlayingCard(rank='10', suit='♡'), PlayingCard(rank='J', suit='♡'), PlayingCard(rank='Q', suit='♡'), PlayingCard(rank='K', suit='♡'), PlayingCard(rank='A', suit='♡'), PlayingCard(rank='2', suit='♠'), PlayingCard(rank='3', suit='♠'), PlayingCard(rank='4', suit='♠'), PlayingCard(rank='5', suit='♠'), PlayingCard(rank='6', suit='♠'), PlayingCard(rank='7', suit='♠'), PlayingCard(rank='8', suit='♠'), PlayingCard(rank='9', suit='♠'), PlayingCard(rank='10', suit='♠'), PlayingCard(rank='J', suit='♠'), PlayingCard(rank='Q', suit='♠'), PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')]
    ################## list generated by fuction make_french_deck
    Deck(cards=[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), PlayingCard(rank='4', suit='♣'), PlayingCard(rank='5', suit='♣'), PlayingCard(rank='6', suit='♣'), PlayingCard(rank='7', suit='♣'), PlayingCard(rank='8', suit='♣'), PlayingCard(rank='9', suit='♣'), PlayingCard(rank='10', suit='♣'), PlayingCard(rank='J', suit='♣'), PlayingCard(rank='Q', suit='♣'), PlayingCard(rank='K', suit='♣'), PlayingCard(rank='A', suit='♣'), PlayingCard(rank='2', suit='♢'), PlayingCard(rank='3', suit='♢'), PlayingCard(rank='4', suit='♢'), PlayingCard(rank='5', suit='♢'), PlayingCard(rank='6', suit='♢'), PlayingCard(rank='7', suit='♢'), PlayingCard(rank='8', suit='♢'), PlayingCard(rank='9', suit='♢'), PlayingCard(rank='10', suit='♢'), PlayingCard(rank='J', suit='♢'), PlayingCard(rank='Q', suit='♢'), PlayingCard(rank='K', suit='♢'), PlayingCard(rank='A', suit='♢'), PlayingCard(rank='2', suit='♡'), PlayingCard(rank='3', suit='♡'), PlayingCard(rank='4', suit='♡'), PlayingCard(rank='5', suit='♡'), PlayingCard(rank='6', suit='♡'), PlayingCard(rank='7', suit='♡'), PlayingCard(rank='8', suit='♡'), PlayingCard(rank='9', suit='♡'), PlayingCard(rank='10', suit='♡'), PlayingCard(rank='J', suit='♡'), PlayingCard(rank='Q', suit='♡'), PlayingCard(rank='K', suit='♡'), PlayingCard(rank='A', suit='♡'), PlayingCard(rank='2', suit='♠'), PlayingCard(rank='3', suit='♠'), PlayingCard(rank='4', suit='♠'), PlayingCard(rank='5', suit='♠'), PlayingCard(rank='6', suit='♠'), PlayingCard(rank='7', suit='♠'), PlayingCard(rank='8', suit='♠'), PlayingCard(rank='9', suit='♠'), PlayingCard(rank='10', suit='♠'), PlayingCard(rank='J', suit='♠'), PlayingCard(rank='Q', suit='♠'), PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')])
    

    上面的例子是类Deck调用了类外的一个函数make_french_deck来生成一个类Deck的列表类型参数cards,这个列表由传入类PlayingCard不同参数ranksuit而生成的类PlayingCard调用列表。这样就生成了13牌的4种花色的所有值。

    数据类的继承

    from dataclasses import dataclass
    
    @dataclass
    class Position:
        name: str
        lon: float = 0.0
        lat: float = 0.0
    
    @dataclass
    class Capital(Position):
        # 因为父类参数有默认值,所以子类的参数必须定义默认值,否则报错
        # country: str
        country: str = 'Unknown'
        # 可以在子类重新定义父类的参数默认值
        lat: float = 40.0
    
    • 如果父类参数有默认值,子类的所有参数必须定义默认值,否则报错:TypeError: non-default argument 'country' follows default argument。报错原因相当于在子类初始化时def __init__(name: str, lon: float = 0.0, lat: float = 0.0, country: str):非默认参数没有在默认参数前面,因为python规定非默认参数必须在默认参数前面
    • 参数的顺序按照父类顺序,然后子类参数顺序。

    总结

    • 数据类是Python3.7的新特性之一。使用数据类就不必编写样板代码来为对象获得适当的初始化__init__,表示__repr__,和比较__eq__
    • 数据类参数必须声明参数类型,参数可以使用函数赋值。
    • 在继承时如果父类参数有定义默认值,则子类参数必须也要定义默认值,继承后的参数顺序为父类参数,然后到子类参数。
    • 除此之外,数据类和普通类区别不大,数据类定义参数后像普通类一样定义实例方法,一样调用。

    公众号往期文章

    python内置装饰器

    python装饰器

    scrapy-redis debug视频

    scrapy-redis源码浅析

    scrapy过滤重复数据和增量爬取

    redis基础笔记

    scrapy电影天堂实战(二)创建爬虫项目

    scrapy电影天堂实战(一)创建数据库

    scrapy基础笔记

    在docker镜像中加入环境变量

    笔记 | mongodb 入门操作

    笔记 | python元类

    笔记 | python2和python3使用super()

    那些你在python3中可能没用到但应该用的东西

    superset docker 部署

    开机启动容器里面的程序

    博客 | 三步部署hitchhiker-api

  • 相关阅读:
    截取字符串的值
    Tomcat发布项目方法
    struts标签
    正则表达式范例
    树的操作方法
    树结点动态帮定事件
    I/O 流和对象序列化
    Word中的字体大小
    script实现的日期表示
    JavaScript弹出窗口技巧
  • 原文地址:https://www.cnblogs.com/vickey-wu/p/11627486.html
Copyright © 2011-2022 走看看