moar!

    
      
diff --git a/pythonetc/README.md b/pythonetc/README.md
index 6617ae0..d054958 100644
--- a/pythonetc/README.md
+++ b/pythonetc/README.md
@@ -17,11 +17,19 @@ Frames:
 1. ./snippets/f-strings.md
 1. ./snippets/sum-frames.md
 
+Classes, metaclasses, and descriptors:
+
+1. ./class-body.md
+1. ./init-subclass.md
+1. ./prepare.md
+1. ./data-descriptors.md
+1. ./cached-property.md
+1. ./set-name.md
+
 More:
 
 1. ./assert.md
 1. ./snippets/final.md
-1. ./cached-property.md
 1. ./snippets/getitem.md
 1. ./fnmatch.md
 1. ./snippets/hamming.md
diff --git a/pythonetc/class-body.md b/pythonetc/class-body.md
new file mode 100644
index 0000000..0ecc8d2
--- /dev/null
+++ b/pythonetc/class-body.md
@@ -0,0 +1,13 @@
+Basically, the class body is the same as, let's say, the function body, [with only a few limitations](https://t.me/pythonetc/438). You can put any statements inside, reuse previous results and so on:
+
+```python
+class A:
+    print('hello')
+    a = 1
+    if a:
+      b = a + 1
+# hello
+
+A.b
+# 2
+```
diff --git a/pythonetc/data-descriptors.md b/pythonetc/data-descriptors.md
new file mode 100644
index 0000000..ad3e8b6
--- /dev/null
+++ b/pythonetc/data-descriptors.md
@@ -0,0 +1,46 @@
+
+[Descriptors](https://docs.python.org/3/howto/descriptor.html) are special class attributes with a custom behavior on atribute get, set or delete. If an object defines `__set__` or `__delete__`, it is considered a data descriptor. Descriptors that only define `__get__` are called non-data descriptors. The difference is that non-data descriptors are called only if the attribute isn't presented in `__dict__` of the instance.
+
+
+Non-data descriptor:
+
+
+```python
+class D:
+  def __get__(self, obj, owner):
+    print('get', obj, owner)
+
+class C:
+    d = D()
+
+c = C()
+c.d
+# get <__main__.C object at 0x7fdcec49ceb8> 
+
+# updating __dict__ shadows the descriptor
+c.__dict__['d'] = 1
+c.d
+# 1
+```
+
+
+```python
+class D:
+  def __get__(self, obj, owner):
+    print('get', obj, owner)
+
+  def __set__(self, obj, owner):
+    print('set', obj, owner)
+
+class C:
+    d = D()
+
+c = C()
+c.d
+# get <__main__.C object at 0x7fdcec49ceb8> 
+
+# updating __dict__ doesn't shadow the descriptor
+c.__dict__['d'] = 1
+c.d
+# get <__main__.C object at 0x7fdcec49ceb8> 
+```
diff --git a/pythonetc/init-subclass.md b/pythonetc/init-subclass.md
new file mode 100644
index 0000000..564a198
--- /dev/null
+++ b/pythonetc/init-subclass.md
@@ -0,0 +1,18 @@
+Python 3.6 introduced a few hooks to simplify things that could be done before only with metaclasses. Thanks to [PEP-487](https://www.python.org/dev/peps/pep-0487/). The most useful such hook is `__init_subclass__`. It is called on subclass creation and accepts the class and keyword arguments passed next to base classes. Let's see an example:
+
+```python
+speakers = {}
+
+class Speaker:
+  # `name` is a custom argument
+  def __init_subclass__(cls, name=None):
+    if name is None:
+      name = cls.__name__
+    speakers[name] = cls
+
+class Guido(Speaker): pass
+class Beazley(Speaker, name='David Beazley'): pass
+
+speakers
+# {'Guido': __main__.Guido, 'David Beazley': __main__.Beazley}
+```
diff --git a/pythonetc/prepare.md b/pythonetc/prepare.md
new file mode 100644
index 0000000..77172b5
--- /dev/null
+++ b/pythonetc/prepare.md
@@ -0,0 +1,23 @@
+Magic method `__prepare__` on metaclass is called on class creation. It must return a dict instance that then will be used as `__dict__` of the class. For example, it can be used to inject variables into the function scope:
+
+```python
+class Meta(type):    
+    def __prepare__(_name, _bases, **kwargs):
+        d = {}
+        for k, v in kwargs.items():
+            d[k] = __import__(v)
+        return d
+
+class Base(metaclass=Meta):
+    def __init_subclass__(cls, **kwargs):
+      pass
+
+class C(Base, m='math'):
+    mypi = m.pi
+
+C.mypi
+# 3.141592653589793
+
+C.m.pi
+# 3.141592653589793
+```
diff --git a/pythonetc/set-name.md b/pythonetc/set-name.md
new file mode 100644
index 0000000..d173d38
--- /dev/null
+++ b/pythonetc/set-name.md
@@ -0,0 +1,51 @@
+If you're going to store a data in the descriptor, the reasonable question is "where".
+
+1. If data stored in the descriptor's attribute, it will be shared between all instances of the class where the descriptor is assigned.
+2. If data is stored in a dict inside of the descriptor, where key is hash of class and value is data, it will lead to a memory leak.
+
+So, the best solution is to store data in the class itself. But how to name the attribute?
+
+`@cached_property` that we implemented above, relies on the passed function name and it is wrong:
+
+```python
+class C:
+    @cached_property
+    def a(self):
+        print('called')
+        return 1
+    b = a
+c = C()
+
+# `a` is cached:
+c.a
+# called
+# 1
+c.a
+# 1
+
+# but `b` isn't:
+c.b
+# called
+# 1
+c.b
+# called
+# 1
+```
+
+[PEP-487](https://www.python.org/dev/peps/pep-0487/) introduced `__set_name__` hook. It is called on descriptor assignment to a class attribute and accepts the class itself at the name of the attribute. Let's use it and fix the implementation:
+
+```python
+class cached_property:
+  def __init__(self, func):
+    self.func = func
+
+  def __set_name__(self, owner, name):
+    self.name = name
+
+  def __get__(self, obj, cls):
+    if obj is None:
+      return self
+    # we've replaced `self.func.__name__` by `self.name` here
+    value = obj.__dict__[self.name] = self.func(obj)
+    return value
+```
diff --git a/pythonetc/snippets/f-strings.md b/pythonetc/snippets/f-strings.md
index 32ec2ca..29e3cda 100644
--- a/pythonetc/snippets/f-strings.md
+++ b/pythonetc/snippets/f-strings.md
@@ -10,7 +10,16 @@ def f(s):
 
 name = '@pythonetc'
 f('Hello, {name}')
-# Hello, @pythonetc
+# 'Hello, @pythonetc'
 ```
 
 [ChainMap](https://t.me/pythonetc/225) merges `locals` and `globals` into one mapping without need to create a new `dict`.
+
+This implementation is a bit more limited, though. While the original f-strings can have any expression inside, our implementation can't:
+
+```python
+f'{2-1}'
+# '1'
+f('{2-1}')
+# KeyError: '2-1'
+```