title
'Summer Skin'
☝️ function ✌️ class
>>> print
<built-in function print>
>>> str
<class 'str'>
>>> sorted
<built-in function sorted>
>>> reversed
<class 'reversed'>
>>> type
<class 'type'>
>>> enumerate
<class 'enumerate'>
>>> list
<class 'list'>
☝️ function ✌️ class
>>> print
<built-in function print>
>>> str
<class 'str'>
>>> sorted
<built-in function sorted>
>>> reversed
<class 'reversed'>
>>> type
<class 'type'>
>>> enumerate
<class 'enumerate'>
>>> list
<class 'list'>
self__init__dunder methodsobjectclass attributeshashabilitysuper()immutabilityimplementing operatorsmultiple inheritanceproperty__dict__name manglingbound methods__new__abstract base classes__slots__typecallable objects__getattribute____getattr__descriptorsgoose typingmetaclasses__mro_entries__
>>> numbers = [2, 1, 3, 4]
>>> numbers.append(7)
>>> list.append(numbers, 11)
>>> numbers
[2, 1, 3, 4, 7, 11]
>>> name = "PyBeach"
>>> name.lower()
'pybeach'
>>> str.lower(name)
'pybeach'
>>> sorted(["Banana", "apple", "Cherry"], key=str.lower)
['apple', 'Banana', 'Cherry']
>>> name = "PyBeach"
>>> name.lower()
'pybeach'
>>> type(name)
<class 'str'>
>>> type(name).lower
<method 'lower' of 'str' objects>
>>> type(name).lower(name)
'pybeach'
class Album:
def __init__(self, title, tracks):
self.title = title
self.tracks = tracks
def track(self, number):
return self.tracks[number - 1]
Album.track = track
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.track(1)
Track('Soul Meets Body', 231)
class Album:
def __init__(self, title, tracks):
self.title = title
self.tracks = tracks
def track(self, number):
return self.tracks[number - 1]
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.track(1)
Track('Soul Meets Body', 231)
>>> Album.track(plans, 1)
Track('Soul Meets Body', 231)
class Album:
def __init__(self, title, tracks):
self.title = title
self.tracks = tracks
def track(self, number):
return self.tracks[number - 1]
class Album:
def __init__(this, title, tracks):
this.title = title
this.tracks = tracks
def track(this, number):
return this.tracks[number - 1]
class Album:
def __init__(self, title, tracks):
self.title = title
self.tracks = tracks
def track(number):
return self.tracks[number - 1]
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.track(1)
TypeError: Album.track() takes 1 positional argument but 2 were given
thing.method(arguments)
⬇
type(thing).method(thing, arguments)
class Album:
def __init__(self, title, tracks):
self.title = title
self.tracks = tracks
def __repr__(self):
return f"Album({self.title!r}, {self.tracks!r})"
🧐 a particularly pedantic Python programmer
__new____init__
>>> plans == Album("Plans", tracks)
True
def __eq__(self, other):
return self.tracks == other.tracks
>>> len(plans)
2
def __len__(self):
return len(self.tracks)
>>> for track in plans:
... print(track.title)
...
Soul Meets Body
Summer Skin
def __iter__(self):
return iter(self.tracks)
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.title
'Plans'
>>> plans.__dict__
{'title': 'Plans', 'tracks': [...]}
>>> plans.__dict__["title"]
'Plans'
>>> plans.__dict__["title"] = "Transatlanticism"
>>> plans.title
'Transatlanticism'
>>> plans.title = "Narrow Stairs"
>>> plans.year = 2005
>>> del plans.year
>>> plans.title = "Plans"
self.title = titleplans.title = "Plans"class Track:
__slots__ = ("title", "seconds")
def __init__(self, title, seconds):
self.title = title
self.seconds = seconds
>>> t = Track("Summer Skin", 194)
>>> t.__dict__
AttributeError: 'Track' object has no attribute '__dict__'.
Did you mean '.__dir__' instead of '.__dict__'?
>>> t.artist = "Death Cab for Cutie"
AttributeError: 'Track' object has no attribute 'artist'
and no __dict__ for setting new attributes
Track with __slots__
128 bytes per Track without
class Album:
genre = "unknown"
def __init__(self, title, tracks):
...
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.genre
'unknown'
>>> "genre" in plans.__dict__
False
>>> "genre" in Album.__dict__
True
__init__ __repr__ __len__ __iter__duration)__dict__ (title)genre, track)class Album:
...
@property
def duration(self):
return sum(t.seconds for t in self.tracks)
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.duration
425
>>> "duration" in plans.__dict__
False
>>> "duration" in Album.__dict__
True
>>> Album.duration
<property object at 0x7ef6b3ecbe50>
__init__ __repr__ __len__ __iter____get__class Album:
...
@property
def duration(self):
return sum(t.seconds for t in self.tracks)
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.duration
425
>>> "duration" in plans.__dict__
False
>>> "duration" in Album.__dict__
True
>>> Album.duration
<property object at 0x7ef6b3ecbe50>
>>> Album.duration.__get__(plans, Album)
425
__init__ __repr__ __len__ __iter__property is a classclass property:
def __init__(self, function):
self.function = function
def __get__(self, instance, owner):
return self.function(instance)
# Plus __set__, setter, and more...
# (we'll see __set__ soon)
class Album:
...
@property
def duration(self):
return sum(t.seconds for t in self.tracks)
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.__dict__["duration"] = 0
__init__ __repr__ __len__ __iter____get__ and __set__class Album:
...
@property
def duration(self):
return sum(t.seconds for t in self.tracks)
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.__dict__["duration"] = 0
>>> plans.duration
425
__init__ __repr__ __len__ __iter__object.__getattribute__
class object:
def __getattribute__(self, name):
cls_attr = MISSING
for cls in type(self).__mro__:
if name in cls.__dict__:
cls_attr = cls.__dict__[name]
break
get = getattr(type(cls_attr), "__get__", None)
if get and hasattr(type(cls_attr), "__set__"):
return get(cls_attr, self, type(self))
if name in self.__dict__:
return self.__dict__[name]
if get:
return get(cls_attr, self, type(self))
if cls_attr is not MISSING:
return cls_attr
raise AttributeError(name)
class Album:
...
def track(self, number):
return self.tracks[number - 1]
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> type(Album.track)
<class 'function'>
>>> hasattr(type(Album.track), "__get__")
True
__init__ __repr__ __len__ __iter____get__ returns the bound methodclass Album:
...
def track(self, number):
return self.tracks[number - 1]
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> type(Album.track)
<class 'function'>
>>> hasattr(type(Album.track), "__get__")
True
>>> plans.track
<bound method Album.track of Album('Plans', [...])>
>>> Album.track.__get__(plans, Album)
<bound method Album.track of Album('Plans', [...])>
__init__ __repr__ __len__ __iter__thing.method(arguments)
⬇
type(thing).method(thing, arguments)
⬇
type(thing).method.__get__(thing, type(thing))(arguments)
propertycached_propertymethods
classmethodstaticmethod__slots__
Just use a property or a cached_property instead


>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> Album.__mro__
(<class '__main__.Album'>, <class 'object'>)
>>> plans.__mro__
AttributeError: 'Album' object has no attribute '__mro__'
>>> "__mro__" in Album.__dict__
False
type == classtype is the type of all types
>>> type
<class 'type'>
>>> type(type)
<class 'type'>
type is a metaclass
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> type(plans)
<class '__main__.Album'>
>>> type(Album)
<class 'type'>
>>> type.__dict__["__mro__"]
<attribute '__mro__' of 'type' objects>
Their class has a __call__ method
Their metaclass has a __call__ method
>>> from functools import partial
>>> partial
<class 'functools.partial'>
>>> to_int = partial(int, base=2)
>>> to_int("101")
5
>>> type(to_int).__call__(to_int, "101")
5
>>> Album("Plans", [])
Album('Plans', [])
>>> type(Album).__call__(Album, "Plans", [])
Album('Plans', [])
class type:
def __call__(cls, *args, **kwargs):
obj = cls.__new__(cls, *args, **kwargs)
if isinstance(obj, cls):
cls.__init__(obj, *args, **kwargs)
return obj
the standard library
enumabccollections.abc
third-party libraries
djangopydanticsqlalchemy
class Album:
def __new__(cls, *args, **kwargs):
...
class Album:
def __init_subclass__(cls, **kwargs):
...
@decorator
class Album:
...
super()class Parent:
def hi(self):
return "parent"
class Child(Parent):
def hi(self):
return "child + " + super().hi()
>>> child = Child()
>>> super
<class 'super'>
>>> super(Child, child).hi()
'parent'
>>> type(child).__mro__
(<class '__main__.Child'>,
<class '__main__.Parent'>,
<class 'object'>)
class Child:
def __init__(self):
print(locals())
super().__init__()
>>> child = Child()
{'self': <__main__.Child object at 0x7994f82a0830>,
'__class__': <class '__main__.Child'>}
class Child:
def __init__(self):
print(locals())
super().__init__()
>>> child = Child()
{'self': <__main__.Child object at 0x7994f82a0830>,
'__class__': <class '__main__.Child'>}
>>> Child.__init__.__code__.co_freevars
('__class__',)
class TrackNotFound(LookupError):
pass
def __init__(self, number):
super().__init__(f"No track number {number}")
self.number = number
TrackNotFound.__init__ = __init__
>>> TrackNotFound(5)
RuntimeError: super(): __class__ cell not found
>>> import typing
>>> class Point(typing.NamedTuple):
... x: int
...
>>> typing.NamedTuple
<function NamedTuple at 0x7ef6b3f0b880>
>>> typing.NamedTuple.__mro_entries__
<function _namedtuple_mro_entries at 0x7ef6b3f0b920>
follow me online
a Python tip every Wednesday
follow me online
a Python tip every Wednesday
Slides cut from the talk. Not part of it.
super()
super() in 2 minutesfor color in Color:
print(color)
class EnumType(type):
def __iter__(cls):
...
Thing()what happens when you call a class?
thing.method(x)no self passed in
def method(self, x):but self is accepted
thing.attributewhere does Python find it?
for color in Color:
print(color)
class EnumType(type):
def __iter__(cls):
...
isinstance(plans, Iterable)
class ABCMeta(type):
def __instancecheck__(cls, obj):
...
x == y
__eq__
x < yx >= y
__lt____le____gt____ge__
with x:
__enter____exit__
len(x)x[i]for v in x
__len____getitem____setitem____delitem____iter____contains__
x + yx * y
__add____sub____mul____truediv____floordiv____mod____pow____neg__
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> Album.__mro__
(<class '__main__.Album'>, <class 'object'>)
>>> plans.__mro__
AttributeError: 'Album' object has no attribute
'__mro__'
>>> type.__dict__["__mro__"]
<attribute '__mro__' of 'type' objects>
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> Album.__mro__
(<class '__main__.Album'>, <class 'object'>)
>>> plans.__mro__
AttributeError: 'Album' object has no attribute
'__mro__'
>>> type.__dict__["__mro__"]
<attribute '__mro__' of 'type' objects>
>>> type(Album).__call__(Album, "Plans", [])
Album('Plans', [])
>>> import enum
>>> class Color(enum.Enum):
... RED = 1
... BLUE = 2
...
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>]
>>> type(Color)
<class 'enum.EnumType'>
>>> "__iter__" in type(Color).__dict__
True
using type checks for duck typing
>>> from collections.abc import Iterable
>>> type(Iterable)
<class 'abc.ABCMeta'>
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> isinstance(plans, Iterable)
True
>>> Album.__mro__
(<class '__main__.Album'>, <class 'object'>)
>>> "__iter__" in Album.__dict__
True
ABCMeta implements
__instancecheck____subclasscheck__
>>> class Meta(type):
... @classmethod
... def __prepare__(metacls, name, bases):
... return {"surprise": "I was never defined!"}
...
>>> class Weird(metaclass=Meta):
... print(surprise)
...
I was never defined!
class Album:
...
def track(self, number):
return self.tracks[number - 1]
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> Album.track
<function Album.track at 0x7ef6b3ee5b20>
>>> plans.track
<bound method Album.track of Album('Plans', [...])>
__init__ __repr__ __len__ __iter__class Album:
...
def track(self, number):
return self.tracks[number - 1]
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> Album.track
<function Album.track at 0x7ef6b3ee5b20>
>>> plans.track
<bound method Album.track of Album('Plans', [...])>
>>> hasattr(type(Album.track), "__get__")
True
>>> Album.track.__get__(plans)
<bound method Album.track of Album('Plans', [...])>
__init__ __repr__ __len__ __iter__class Album:
...
@cached_property
def longest(self):
return max(self.tracks, key=lambda t: t.seconds)
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.longest
Track('Soul Meets Body', 231)
>>> plans.longest
Track('Soul Meets Body', 231)
__init__ __repr__ __len__ __iter__duration)__dict__ (title, longest)genre, track)class Album:
...
@property
def duration(self):
return sum(t.seconds for t in self.tracks)
@cached_property
def longest(self):
return max(self.tracks, key=lambda t: t.seconds)
@classmethod
def single(cls, track):
return cls(track.title, [track])
__init__ __repr__ __len__ __iter__class cached_property:
...
def __get__(self, instance, owner=None):
value = self.func(instance)
instance.__dict__[self.name] = value
return value
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.longest
Track('Soul Meets Body', 231)
>>> plans.longest
Track('Soul Meets Body', 231)
__init__ __repr__ __len__ __iter__property
functools.cached_property
a virtual attribute
a lazy attribute
from functools import cached_property
class Album:
...
@cached_property
def longest(self):
return max(self.tracks, key=lambda t: t.seconds)
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> plans.longest
Track('Soul Meets Body', 231)
>>> plans.longest
Track('Soul Meets Body', 231)
__init__ __repr__ __len__ __iter__from functools import cached_property
class Album:
...
@cached_property
def longest(self):
return max(self.tracks, key=lambda t: t.seconds)
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> cached_property
<class 'functools.cached_property'>
>>> Album.__dict__["longest"]
<functools.cached_property object at 0x7f3c1d2b8e90>
__init__ __repr__ __len__ __iter____get__from functools import cached_property
class Album:
...
@cached_property
def longest(self):
return max(self.tracks, key=lambda t: t.seconds)
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> cached_property
<class 'functools.cached_property'>
>>> Album.__dict__["longest"]
<functools.cached_property object at 0x7f3c1d2b8e90>
>>> plans.longest
Track('Soul Meets Body', 231)
>>> Album.__dict__["longest"].__get__(plans)
Track('Soul Meets Body', 231)
>>> "longest" in plans.__dict__
True
__init__ __repr__ __len__ __iter__duration)__set____dict__ (title, tracks)genre, track, longest)__get__ if it has one__init__ __repr__ __len__ __iter__MISSING = sentinel("MISSING")
def __getattribute__(self, name):
attr = MISSING
for cls in type(self).__mro__: # the class, then its parents
if name in cls.__dict__:
attr = cls.__dict__[name]
break
get = getattr(type(attr), "__get__", None)
if get and hasattr(type(attr), "__set__"): # ① a data descriptor
return get(attr, self, type(self))
if name in self.__dict__: # ② the instance's __dict__
return self.__dict__[name]
if get: # ③ any other descriptor
return get(attr, self, type(self))
if attr is not MISSING: # ③ or a plain class attribute
return attr
raise AttributeError(name)
class Album:
...
def track(self, number):
return self.tracks[number - 1]
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> type(Album.track)
<class 'function'>
>>> hasattr(type(Album.track), "__get__")
True
__init__ __repr__ __len__ __iter__class Album:
...
def track(self, number):
return self.tracks[number - 1]
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> type(Album.track)
<class 'function'>
>>> hasattr(type(Album.track), "__get__")
True
>>> Album.track
<function Album.track at 0x7ef6b3ee5b20>
>>> plans.track
<bound method Album.track of Album('Plans', [...])>
>>> Album.track.__get__(plans)
<bound method Album.track of Album('Plans', [...])>
__init__ __repr__ __len__ __iter__self whether we like it or not"class Album:
...
@classmethod
def single(cls, track):
return cls(track.title, [track])
>>> tracks = [Track("Soul Meets Body", 231),
... Track("Summer Skin", 194)]
>>> plans = Album("Plans", tracks)
>>> classmethod, staticmethod
(<class 'classmethod'>, <class 'staticmethod'>)
>>> hasattr(classmethod, "__get__")
True
>>> hasattr(staticmethod, "__get__")
True
__init__ __repr__ __len__ __iter____slots__?class Track:
__slots__ = ("title", "seconds")
...
>>> t = Track("Summer Skin", 194)
>>> Track.__dict__["title"]
<member 'title' of 'Track' objects>
>>> hasattr(Track.__dict__["title"], "__set__")
True