{ "cells": [ { "cell_type": "code", "execution_count": 1, "outputs": [ { "data": { "text/plain": "'21.4.0'" }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import typing as T\n", "import attrs\n", "\n", "attrs.__version__" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "## PyCharm Support\n", "\n", "PyCharm 很早就支持基于 ``attr.s`` API 的自动补全, 但 ``attrs`` 是 2021 年之后的新 API, Pycharm 2022.3 中 ``attrs.define`` 还不被 PyCharm 所接受." ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": 2, "outputs": [ { "ename": "TypeError", "evalue": "__init__() missing 1 required positional argument: 'id'", "output_type": "error", "traceback": [ "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", "\u001B[0;31mTypeError\u001B[0m Traceback (most recent call last)", "Input \u001B[0;32mIn [2]\u001B[0m, in \u001B[0;36m\u001B[0;34m()\u001B[0m\n\u001B[1;32m 16\u001B[0m account: \u001B[38;5;28mstr\u001B[39m \u001B[38;5;241m=\u001B[39m attr\u001B[38;5;241m.\u001B[39mfield()\n\u001B[1;32m 19\u001B[0m \u001B[38;5;66;03m# move cursor in bracket and hit CMD + P to see hint\u001B[39;00m\n\u001B[0;32m---> 20\u001B[0m \u001B[43mBase\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 21\u001B[0m User()\n\u001B[1;32m 22\u001B[0m PaidUser(\u001B[38;5;28mid\u001B[39m\u001B[38;5;241m=\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124minvalid\u001B[39m\u001B[38;5;124m\"\u001B[39m, name\u001B[38;5;241m=\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124malice\u001B[39m\u001B[38;5;124m\"\u001B[39m, account\u001B[38;5;241m=\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m1234\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n", "\u001B[0;31mTypeError\u001B[0m: __init__() missing 1 required positional argument: 'id'" ] } ], "source": [ "import attr\n", "\n", "\n", "@attr.s\n", "class Base:\n", " id: int = attr.field()\n", "\n", "\n", "@attr.s\n", "class User(Base):\n", " name: str = attr.field()\n", "\n", "\n", "@attr.s\n", "class PaidUser(User):\n", " account: str = attr.field()\n", "\n", "\n", "# move cursor in bracket and hit CMD + P to see hint\n", "Base()\n", "User()\n", "PaidUser(id=\"invalid\", name=\"alice\", account=\"1234\")" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [ "@attr.s\n", "class User:\n", " _id: 1 = attr.ib()\n", " name: str = attr.ib()\n", "\n", "\n", "user = User(id=1, name=\"Alice\")\n", "print(user)\n", "print(user._id)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": 3, "outputs": [ { "data": { "text/plain": "{'name': 'alice'}" }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@attr.s\n", "class Base:\n", " _important_attr: T.List[str] = None\n", "\n", " def to_dict(self) -> dict:\n", " return {\n", " k: v\n", " for k, v in attr.asdict(self).items()\n", " if k in self._important_attr\n", " }\n", "\n", "\n", "@attr.s\n", "class Person(Base):\n", " id: int = attr.ib()\n", " name: str = attr.ib()\n", "\n", " _important_attr = [\"name\"]\n", "\n", "\n", "person = Person(id=1, name=\"alice\")\n", "person.to_dict()" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "## Immutable 和 Mutable\n", "\n", "所谓 Immutable 就是对象一旦被创建, 就无法被修改. 而 Mutable 则是对象被创建后, 其属性值是可以被修改的. 这里要注意的是, 如果对象的属性是 Mutable 的对象, 比如有个属性是列表, 你可以对列表本身进行修改, 但是不能给这个属性赋一个新的值.\n", "\n", "Immutable 的好处:\n", "\n", "1. 可以从机制上避免很多因为修改带来的错误.\n", "2. 对象可以被缓存, 可以被哈希, 可以用来当 Dict Key, 可以用来去重." ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": 8, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3, 4, 5]\n" ] } ], "source": [ "def sort_arr(arr: list):\n", " arr.sort()\n", " return arr\n", "\n", "\n", "@attr.s(frozen=True)\n", "class MyImmutableData:\n", " arr: T.List[int] = attr.ib(factory=list, converter=sort_arr)\n", "\n", "\n", "my_immutable_data = MyImmutableData(arr=[3, 1, 4, 2])\n", "my_immutable_data.arr.append(5)\n", "print(my_immutable_data.arr)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "markdown", "source": [ "## Converter\n", "\n", "很多时候我们的一些属性被传入的时候我们希望对其做一些预处理. 例如将字符串 cast type 成整数, 将列表进行排序等. 而一旦我们的对象是 Mutable 的, Converter 机制就不是很好用了. 因为逻辑上你每次对属性进行修改时就应该运行 converter, 但是你如果用 ``self.my_attribute = ...`` 的方式赋值, converter 方法并不会被调用. 所以这时我建议将 Converter 函数都做成这个类的 classmethod, 然后如果你非要调用 ``self.my_attribute = ...`` 的时候," ], "metadata": { "collapsed": false, "pycharm": { "name": "#%% md\n" } } }, { "cell_type": "code", "execution_count": 7, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3, 4]\n" ] } ], "source": [ "def sort_arr(arr: list):\n", " arr.sort()\n", " return arr\n", "\n", "\n", "@attr.s\n", "class MyMutableData:\n", " arr: T.List[int] = attr.ib(factory=list, converter=sort_arr)\n", "\n", " @classmethod\n", " def convert_arr(cls, arr: list) -> list:\n", " return sort_arr(arr)\n", "\n", "\n", "my_mutable_data = MyMutableData(arr=[1, 2])\n", "my_mutable_data.arr = my_mutable_data.convert_arr([3, 1, 4, 2])\n", "print(my_mutable_data.arr)" ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } }, { "cell_type": "code", "execution_count": null, "outputs": [], "source": [], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 0 }