Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Classy Abstractions @ Python Web Conf

Classy Abstractions @ Python Web Conf

Hynek Schlawack

June 18, 2020
Tweet

More Decks by Hynek Schlawack

Other Decks in Programming

Transcript

  1. Classy
    Abstractions
    Hynek Schlawack

    View Slide

  2. [({},),({},)]

    View Slide

  3. Primitive
    Obsession

    View Slide

  4. Premature
    Optimization

    View Slide

  5. View Slide

  6. –me, today
    “You’re wasting precious resources if
    you use a high-level language but only
    use low-level primitives.”

    View Slide

  7. View Slide

  8. PATHLIB

    View Slide

  9. PATHLIB
    >>> p = Path() / "foo" / "bar"

    View Slide

  10. PATHLIB
    >>> p = Path() / "foo" / "bar"
    >>> p.absolute()
    PosixPath('/Users/hynek/foo/bar')
    >>> p.absolute()
    WindowsPath('C:/Users/Hynek Schlawack/foo/bar')

    View Slide

  11. PATHLIB
    >>> p = Path() / "foo" / "bar"
    >>> p.absolute()
    PosixPath('/Users/hynek/foo/bar')
    >>> p.absolute()
    WindowsPath('C:/Users/Hynek Schlawack/foo/bar')
    >>> p.name
    'bar'

    View Slide

  12. PATHLIB
    >>> p = Path() / "foo" / "bar"
    >>> p.absolute()
    PosixPath('/Users/hynek/foo/bar')
    >>> p.absolute()
    WindowsPath('C:/Users/Hynek Schlawack/foo/bar')
    >>> p.name
    'bar'
    >>> Path("README.md").read_text()
    ...

    View Slide

  13. IPADDRESS

    View Slide

  14. IPADDRESS
    >>> i = ipaddress.ip_address("::1")
    >>> i.exploded
    '0000:0000:0000:0000:0000:0000:0000:0001'

    View Slide

  15. IPADDRESS
    >>> i = ipaddress.ip_address("::1")
    >>> i.exploded
    '0000:0000:0000:0000:0000:0000:0000:0001'
    >>> ipaddress.ip_address("10.23.45.56") \
    in ipaddress.ip_network("10.0.0.0/8")
    True

    View Slide

  16. • /?a=fetch&content=die(@md5(HelloThinkCMF))
    • /wp-content/plugins/google-document-embedder/libs/pdf.php?
    fn=lol.pdf&file=../../../../wp-config.php
    • /wp-admin/admin-ajax.php?action=kbslider_show_image&img=../
    wp-config.php

    View Slide

  17. YARL

    View Slide

  18. YARL
    >>> u = yarl.URL("https://hynek.me/articles/")

    View Slide

  19. YARL
    >>> u = yarl.URL("https://hynek.me/articles/")
    >>> u.host
    'hynek.me'
    >>> u.path
    '/articles/'

    View Slide

  20. YARL
    >>> u = yarl.URL("https://hynek.me/articles/")
    >>> u.host
    'hynek.me'
    >>> u.path
    '/articles/'
    >>> u / "speaking/"
    URL('https://hynek.me/articles/speaking/')

    View Slide

  21. YARL
    >>> u = yarl.URL("https://hynek.me/articles/")
    >>> u.host
    'hynek.me'
    >>> u.path
    '/articles/'
    >>> u / "speaking/"
    URL('https://hynek.me/articles/speaking/')
    >>> u.with_path("about/")
    URL('https://hynek.me/about/')

    View Slide

  22. >>> u = yarl.URL.build(
    scheme="redis", user="scott", password="tiger",
    host="redis.local", port=369, path="/1",
    query={"socket_timeout": 42})
    >>> u
    URL('redis://scott:[email protected]:369/1?socket_timeout=42')

    View Slide

  23. >>> u = yarl.URL.build(
    scheme="redis", user="scott", password="tiger",
    host="redis.local", port=369, path="/1",
    query={"socket_timeout": 42})
    >>> u
    URL('redis://scott:[email protected]:369/1?socket_timeout=42')
    >>> redis.ConnectionPool.from_url(str(u))

    View Slide

  24. View Slide

  25. def f(a=False, b=False, c=False):
    ...
    f(b=True)

    View Slide

  26. def f(a=False, b=False, c=False):
    ...
    f(b=True)
    def f(what):
    ...
    f("b")

    View Slide

  27. View Slide

  28. ENUM
    from enum import Enum
    class What(Enum):
    A = "a"
    B = "b"
    C = "c"

    View Slide

  29. ENUM
    from enum import Enum
    class What(Enum):
    A = "a"
    B = "b"
    C = "c"
    f(What.A)

    View Slide

  30. ENUM
    from enum import Enum
    class What(Enum):
    A = "a"
    B = "b"
    C = "c"
    def f(what: What):
    ...
    f(What.D)
    f(What.A)

    View Slide

  31. ENUM
    from enum import Enum
    class What(Enum):
    A = "a"
    B = "b"
    C = "c"
    def f(what: What):
    ...
    f(What.D)
    f(What.A) error: "Type[What]" has no attribute "D"

    View Slide

  32. @app.route("/")
    def view():
    return {
    "foo": "bar"
    }

    View Slide

  33. @app.route("/")
    def view():
    return {
    "data": {
    "id": "some-id",
    "type": "example",
    "attributes": {
    "foo": "bar"
    }
    }
    "links": "http://localhost/",
    }

    View Slide

  34. @bp.route("/", methods=["GET"])
    def get_dns_record(id: int) -> APIResult:
    try:
    return APIResult(
    get_uow().dns_records.get(id)
    )
    except UnknownDNSRecordError:
    raise_not_found("DNS record", id)

    View Slide

  35. @bp.route("/", methods=["GET"])
    def get_dns_record(id: int) -> APIResult:
    try:
    return APIResult(
    get_uow().dns_records.get(id)
    )
    except UnknownDNSRecordError:
    raise_not_found("DNS record", id)

    View Slide

  36. @bp.route("/", methods=["POST"])
    @validated_body
    def create_dns_record(ndr: CreateDNSRecord) -> APIResult:
    try:
    dr_id = svc.create_record(
    get_uow(), ndr.domain, # ...
    )
    except PlanError as e:
    raise APIError(
    id=error_ids.PLAN_NOT_SUFFICIENT,
    status="402",
    title=e.args[0],
    source={"pointer": "/data/attributes/domain"},
    )
    # ...

    View Slide

  37. @bp.route("/", methods=["POST"])
    @validated_body
    def create_dns_record(ndr: CreateDNSRecord) -> APIResult:
    try:
    dr_id = svc.create_record(
    get_uow(), ndr.domain, # ...
    )
    except PlanError as e:
    raise APIError(
    id=error_ids.PLAN_NOT_SUFFICIENT,
    status="402",
    title=e.args[0],
    source={"pointer": "/data/attributes/domain"},
    )
    # ...

    View Slide

  38. @bp.route("/", methods=["POST"])
    @validated_body
    def create_dns_record(ndr: CreateDNSRecord) -> APIResult:
    try:
    dr_id = svc.create_record(
    get_uow(), ndr.domain, # ...
    )
    except PlanError as e:
    raise APIError(
    id=error_ids.PLAN_NOT_SUFFICIENT,
    status="402",
    title=e.args[0],
    source={"pointer": "/data/attributes/domain"},
    )
    # ...

    View Slide

  39. @bp.route("/", methods=["POST"])
    @validated_body
    def create_dns_record(ndr: CreateDNSRecord) -> APIResult:
    try:
    dr_id = svc.create_record(
    get_uow(), ndr.domain, # ...
    )
    except PlanError as e:
    raise APIError(
    id=error_ids.PLAN_NOT_SUFFICIENT,
    status="402",
    title=e.args[0],
    source={"pointer": "/data/attributes/domain"},
    )
    # ...

    View Slide

  40. Locked
    Un-
    locked

    View Slide

  41. Locked
    Un-
    locked
    Start

    View Slide

  42. Locked
    Un-
    locked
    Insert Coin
    Start

    View Slide

  43. Locked
    Un-
    locked
    Insert Coin
    Push
    Start

    View Slide

  44. Locked
    Un-
    locked
    Insert Coin
    Push Insert Coin
    Start

    View Slide

  45. Locked
    Un-
    locked
    Push
    Insert Coin
    Push Insert Coin
    Start

    View Slide

  46. Classes

    View Slide

  47. View Slide

  48. View Slide

  49. View Slide

  50. View Slide

  51. “Dunder”
    double underscores:
    •__init__
    •__eq__
    •...

    View Slide

  52. class Point(namedtuple('Point', ['x', 'y'])):
    __slots__ = ()
    @property
    def hypot(self):
    return (self.x ** 2 + self.y ** 2) ** 0.5
    def __str__(self):
    return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (
    self.x, self.y, self.hypot)

    View Slide

  53. class Point(namedtuple('Point', ['x', 'y'])):
    __slots__ = ()
    @property
    def hypot(self):
    return (self.x ** 2 + self.y ** 2) ** 0.5
    def __str__(self):
    return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (
    self.x, self.y, self.hypot)
    >>> Point.__mro__
    (,
    ,
    ,
    )

    View Slide

  54. CHARACTERISTIC
    @attributes(
    ["a"],
    defaults={"a": 42}
    )
    class C: pass

    View Slide

  55. @attr.s
    class Point:
    x = attr.ib()
    y = attr.ib()
    @property
    def hypot(self):
    return (self.x ** 2 + self.y ** 2) ** 0.5
    def __str__(self):
    return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (
    self.x, self.y, self.hypot)

    View Slide

  56. View Slide

  57. Dataclasses

    View Slide

  58. @dataclasses.dataclass
    class Point:
    x: int
    y: int
    @attr.dataclass
    class Point:
    x: int
    y: int

    View Slide

  59. View Slide

  60. • 2.7

    View Slide

  61. • 2.7
    • __slots__

    View Slide

  62. • 2.7
    • __slots__
    • types optional

    View Slide

  63. • 2.7
    • __slots__
    • types optional
    • validators/converters

    View Slide

  64. • 2.7
    • __slots__
    • types optional
    • validators/converters
    • free to innovate

    View Slide

  65. from attrs import define, field
    @define
    class Point3D:
    x: int
    y: int
    z: int = field(
    validator=positive
    )

    View Slide

  66. View Slide

  67. • embrace abstractions

    View Slide

  68. • embrace abstractions
    • don’t be afraid of classes

    View Slide

  69. • embrace abstractions
    • don’t be afraid of classes
    • wear your masks

    View Slide

  70. ox.cx/a
    @hynek
    hynek.me
    vrmd.de

    View Slide