◆ class 定義のブロック内は関数実行と同じで普通に文を書ける
◆ インスタンスを作らなくても定義時に中身が実行される
◆ 関数みたいにスコープが独立していて そのスコープのローカル変数は保持される
  ◆ 「クラス名.変数名」や「インスタンス.変数名」でアクセスできる
◆ 即時実行・独立したスコープ・変数を参照可能 な特徴を生かしてブロックスコープとして使うこともできなくはない

C# や PHP や JavaScript など多くの言語での class 定義では class ブロック内に書けるものはクラス定義専用の構文のみです
メソッド定義やプロパティ定義などクラスの情報を定義する場所なので そこ専用のものだけです
if や for などの普通の文は書けません
書くならメソッドの中の関数部分になります

しかし Python だと class のブロックも 関数のブロックと一緒で普通の文を書けます

class C1:
if True:
a = 1

これはエラーになりません

ブロック内の文は 関数の即時実行のように class 定義文を処理する段階で即時実行されます
定義の時点で実行されるので 最終的にインスタンスをひとつも作らなかったとしても実行されます

関数と同じように文を書けますが 関数ではないので return は使えません
また def で関数定義の中の class 定義だったとしても if などのように return はできません

実行した結果のそのブロック内の変数は保持されて 「クラス名.変数名」 でアクセスできるようになります

class A:
a = 1
b = 2
c = a + b
del a

x = []
for i in range(10):
if i % 2 == 0:
x.append(i)

def foo(x):
return x + 1

y = foo(10) + foo(20)

print("class end")

print("after class")

print("b:", A.b)
print("c:", A.c)
print("i:", A.i)
print("x:", A.x)
print("foo:", A.foo)
print("foo(5):", A.foo(5))
print("y:", A.y)
try:
print("a: ", A.a)
except:
print("a: access error")
class end
after class
b: 2
c: 3
i: 9
x: [0, 2, 4, 6, 8]
foo: <function A.foo at 0x002CA148>
foo(5): 6
y: 112
a: access error

class end が after class より先に出力されています
del で消されるので a はアクセスできず Python はブロックスコープがないのでループ変数の i は残っています
関数もそのまま関数として使えます

これらの変数はインスタンスからもアクセスできて 関数呼び出しの場合には最初の引数にそのインスタンス自身が渡されます

class B:
def m(self, a):
print("self:", self)
print("a:", a)

b = B()
b.m(10)

B.m(1, 2)
self: <__main__.B object at 0x00383358>
a: 10
self: 1
a: 2

この違いがあるので B.m と B().m でアクセスした場合に関数は別オブジェクトになってます

class C:
v = object()
def m():
pass

c = C()

print(C.v is c.v)
print(C.m is c.m)
print(C.m)
print(c.m)
True
False
<function C.m at 0x0076A148>
<bound method C.m of <__main__.C object at 0x00753220>>

ビルトインの staticmethod 関数はインスタンス経由でも self を渡さなくしてくれます

class D:
def foo(x, y):
return x + y

bar = staticmethod(foo)

@staticmethod
def baz(x, y):
return x + y

print(D.foo(3, 4))
print(D.bar(3, 4))
print(D.baz(3, 4))

d = D()

try:
print(d.foo(3, 4))
except Exception as err:
print("Error:", err)

print(d.bar(3, 4))
print(d.baz(3, 4))
7
7
7
Error: foo() takes 2 positional arguments but 3 were given
7
7

ビルトインクラス以外なら 後からクラスオブジェクトを拡張してメソッドの追加もできます

class E:
def __init__(self):
self.value = 10

e = E()

try:
e.foo()
except:
print("failed to call method foo")

E.foo = lambda self: print(self.value)

e.foo()
failed to call foo method
10

おまけ

この class 定義文の仕組みを使えばブロックスコープとして使うこともできたりします

x = foo()
y = bar(x)
z = x + y
print(z)

この処理で z を求める部分はブロックにしたいとします

class _:
x = foo()
y = bar(x)
z = x + y

z = _.z
print(z)

こうすれば スコープが独立したブロックとして使えます
Python では同じスコープ内で同じ名前のクラス定義を何度もできるので 「_」 という名前ならブロック用の一時変数とします
Python だと def を使うと関数の即時実行ができず lambda なら複文を書けないのでブロックスコープを作るためだけの関数実行は逆に読みづらくなることもあり 結局使わないことが多いです
class 定義文にすると 見た目的には凄くシンプルで見やすく書けます

こういう使い方もできる というだけで本来の使い方とは違いますし 特におすすめはしませんけど