diff --git a/python-avancé/examples/class-construction-from-scratch.py b/python-avancé/examples/class-construction-from-scratch.py new file mode 100644 index 0000000..ce4980a --- /dev/null +++ b/python-avancé/examples/class-construction-from-scratch.py @@ -0,0 +1,28 @@ +# This is the creation « from scratch » of the following class: + +# class Foo: +# def say(self): +# print("hello") + +# First the type.__prepare__ is called to create a namespace, it +# typically returns a fresh empty dict: + +ns = {} + +# Then the class body is executed in this namespace: + +exec( + """ +def say(self): + print("hello") +""", + globals(), + ns, +) + +# Now, the type __new__ is called to create the class: + +Foo = type.__new__(type, "Foo", (), ns) +type.__init__(Foo, "Foo", (), ns) # Does nothing + +Foo().say() diff --git a/python-avancé/examples/decorator-flask.py b/python-avancé/examples/decorator-flask.py new file mode 100644 index 0000000..e86c116 --- /dev/null +++ b/python-avancé/examples/decorator-flask.py @@ -0,0 +1,39 @@ +from urllib.parse import parse_qsl + + +class Application: + def __init__(self): + self.routes = {} + + def route(self, path): + def deco(fct): + self.routes[path] = fct + return fct + return deco + + def get(self, path): + try: + path, qs = path.split("?", maxsplit=1) + except ValueError: + qs = {} + return self.routes[path](**dict(parse_qsl(qs))) + +app = Application() + + +@app.route("/") +def home(): + return "/api" + +@app.route("/api") +def api(): + return {"version": "1"} + +@app.route("/auth/") +def auth(username, password): + return f"Wrong password for {username}" + + +print(app.get("/")) +print(app.get("/api")) +print(app.get("/auth/?username=mdk&password=test")) diff --git a/python-avancé/examples/decorator-memoize.py b/python-avancé/examples/decorator-memoize.py new file mode 100644 index 0000000..c87d5ad --- /dev/null +++ b/python-avancé/examples/decorator-memoize.py @@ -0,0 +1,19 @@ +def memoize(limit): # Factory + def _memoize(function_fib): + memory = {} + def cached_fib(n): + if n in memory: + return memory[n] + result = function_fib(n) + memory[n] = result + return result + return cached_fib + return _memoize + +@memoize(limit=1024) +def fib(n): + if n < 2: + return 1 + return fib(n-1) + fib(n-2) + +# fib = memoize(limit=1024)(fib) diff --git a/python-avancé/examples/descriptor-positive-integer.py b/python-avancé/examples/descriptor-positive-integer.py new file mode 100644 index 0000000..980062f --- /dev/null +++ b/python-avancé/examples/descriptor-positive-integer.py @@ -0,0 +1,33 @@ +class PositiveInteger: + def __set_name__(self, owner, name): + self.name = name + + def __get__(self, instance, cls=None): + if instance is None: + return self + return instance.__dict__[self.name] + + def __set__(self, instance, value): + if value < 0: + raise ValueError("Negative values are not acceptable.") + instance.__dict__[self.name] = value + + +class Order: + qty = PositiveInteger() + price = PositiveInteger() + + def __init__(self, item, price, qty): + self.item = item + self.price = price + self.qty = qty + + def cost(self): + return self.price * self.qty + + +# Order("un truc", -1, 1) +# o = Order("Un livre", 35, 1) +# o.qty += 1 +# o.qty -= 10 +# print(o.cost()) diff --git a/python-avancé/examples/hypothesis-sort.py b/python-avancé/examples/hypothesis-sort.py new file mode 100644 index 0000000..95036a4 --- /dev/null +++ b/python-avancé/examples/hypothesis-sort.py @@ -0,0 +1,14 @@ +from string import ascii_letters + +from hypothesis import given, settings + +from hypothesis.strategies import text, lists + + +@given(lists(text(alphabet=ascii_letters, max_size=10))) +@settings(max_examples=500) +def test_sort_is_stable(a_list): + print(a_list) + double_sort = sorted(sorted(a_list, key=str.lower), key=len) + single_sort = sorted(a_list, key=lambda s: (len(s), s.lower())) + assert double_sort == single_sort diff --git a/python-avancé/examples/metaclass-autoinit.py b/python-avancé/examples/metaclass-autoinit.py new file mode 100644 index 0000000..e5a11ae --- /dev/null +++ b/python-avancé/examples/metaclass-autoinit.py @@ -0,0 +1,27 @@ +class AutoInit(type): + def __new__(mcs, name, bases, ns, **kwargs): + cls = type.__new__(mcs, name, bases, ns) + cls._attributes = kwargs["attributes"] + return cls + + def __call__(self, *args, **kwargs): + attributes = {} + for name in self._attributes: + attributes[name] = kwargs.pop(name, None) + obj = super().__call__(*args, **kwargs) + for key, value in attributes.items(): + setattr(obj, key, value) + return obj + + +class Point(metaclass=AutoInit, attributes=("x", "y", "z")): + ... + + +origin = Point(x=0, y=0, z=0) +print(origin.x, origin.y, origin.z) # 0 0 0 + +NonePoint = Point() +print(NonePoint.x, NonePoint.y) # None None + +# origin = Point(x=0, y=0, blah=42) # TypeError ! diff --git a/python-avancé/examples/metaclass-introspect.py b/python-avancé/examples/metaclass-introspect.py new file mode 100644 index 0000000..cff7d53 --- /dev/null +++ b/python-avancé/examples/metaclass-introspect.py @@ -0,0 +1,66 @@ +from functools import wraps +from time import perf_counter, sleep +from typing import Self + + +def as_call(args, kwargs): + passed = ['self'] + for arg in args[1:]: + passed.append(repr(arg)) + for key, value in kwargs.items(): + passed.append(f"key={value!r}") + return "(" + ", ".join(passed) + ")" + + +def introspected_method(clsname, ctx, name, method): + def _(*args, **kwargs): + callstr = f"{clsname}.{name}{as_call(args, kwargs)}" + print(f"{'| ' * ctx['depth']}Calling {callstr}...") + ctx["depth"] += 1 + before = perf_counter() + result = method(*args, **kwargs) + after = perf_counter() + ctx["depth"] -= 1 + callstr = f"{clsname}.{name}{as_call(args, kwargs)}" + print( + f"{'| ' * ctx['depth']}Returning from {callstr} after {after-before:.2f}s with {result}" + ) + return result + + return _ + + +class Introspected(type): + def __new__(cls, name, bases, ns, **kwds): + ctx = {"depth": 0} + for key, value in ns.items(): + if callable(value) and key != "__repr__": + ns[key] = introspected_method(name, ctx, key, value) + return super().__new__(cls, name, bases, ns, **kwds) + + +class Point(metaclass=Introspected): + def __init__(self, x, y): + self.x = x + self.y = y + + def dist(self, other: Self) -> float: + return ((other.x - self.x) ** 2 + (other.y - self.y) ** 2) ** 0.5 + + def __repr__(self): + return f"Point(x={self.x!r}, y={self.y!r})" + + def modulus(self): + return self.dist(Point(0, 0)) + + def recursive_call(self, value): + sleep(0.1) + if value: + return self.recursive_call(value - 1) + else: + return self.mean() + + +if __name__ == "__main__": + p = Point(10, 10) + print(p.modulus()) diff --git a/python-avancé/examples/metaclass-typed.py b/python-avancé/examples/metaclass-typed.py new file mode 100644 index 0000000..f661809 --- /dev/null +++ b/python-avancé/examples/metaclass-typed.py @@ -0,0 +1,26 @@ +class StronglyTypedMeta(type): + def __new__(cls, name, bases, ns, **kwds): + if '__annotations__' in ns: + for name, type in ns['__annotations__'].items(): + ns[name] = StronglyTypedDescriptor(name, type) + return super().__new__(cls, name, bases, ns, **kwds) + + +class StronglyTypedDescriptor: + def __init__(self, name, type): + self.name = name + self.type = type + + def __get__(self, instance, cls=None): + return instance.__dict__[self.name] + + def __set__(self, instance, value): + if not isinstance(value, self.type): + raise TypeError(f"{self.name} should be of type {self.type}.") + instance.__dict__[self.name] = value + + + +class Test(metaclass=StronglyTypedMeta): + x: str + y: int diff --git a/python-avancé/examples/metaclass.py b/python-avancé/examples/metaclass.py new file mode 100644 index 0000000..b15a0b2 --- /dev/null +++ b/python-avancé/examples/metaclass.py @@ -0,0 +1,70 @@ +class Meta(type): + @classmethod + def __prepare__(metacls, name, bases, **kwds): + """Prepare the namespace in which the class will be executed.""" + print( + "Meta.__prepare__(", + f"{metacls=},", + f"{name=},", + f"{bases=},", + f"{kwds=}) -> {{}}", + sep="\n ", + ) + return {} + + # __new__() is a static method (special-cased so you need not declare it as such) + def __new__(cls, name, bases, namespace, **kwds): + """The class body is executed in a new namespace and the class name is + bound locally to the result of type(name, bases, namespace). + """ + print( + "\nMeta.__new__(", + f"{cls=},", + f"{name=},", + f"{bases=},", + f"{namespace=}", + f"{kwds=})", + sep="\n ", + end=" ", + ) + returns = type.__new__(cls, name, bases, namespace) + print(f"-> {returns}\n") + return returns + + def __init__(self, name, bases, namespace, **kwds): + """This is the actual creation of the class ! + + As a class is an instance of its type.""" + print( + "Meta.__init__(", + f"{self=},", + f"{name=},", + f"{bases=},", + f"{namespace=},", + f"{kwds=})", + sep="\n ", + end="\n\n" + ) + + +class Classe(metaclass=Meta): + + print("\nAfter Meta.__prepare__ is called, the interpreter executes the class body " + "like:") + print(" exec(body, globals(), ns)") + print("with `ns` the dict returned from __prepare__") + + + def __new__(cls, *args, **kwargs): + print(f"Classe.__new__({cls=}, {args=}, {kwargs=})") + returns = object.__new__(cls) + print(f" returns {returns}") + return returns + + def __init__(self, *args, **kwargs): + print(f"Classe.__init__({self=}, {args=}, {kwargs=})") + + +print("Will now instantiate a Classe()") + +Classe() diff --git a/python-avancé/examples/with-inplace-editor.py b/python-avancé/examples/with-inplace-editor.py new file mode 100644 index 0000000..658c4f0 --- /dev/null +++ b/python-avancé/examples/with-inplace-editor.py @@ -0,0 +1,44 @@ +from pathlib import Path + +class Context: + def __init__(self): + print("Constructing context") + + def __enter__(self): + print("Entering context") + return "Coucou" + + def __exit__(self, exc_type, exc_value, traceback): + print("Exiting context", exc_type) + +# with Context() as ctx: +# print("In context", ctx) + + +from tempfile import NamedTemporaryFile +import os + +class InplaceEdit: + def __init__(self, filename, encoding="UTF-8"): + self.filename = filename + self.ifile = open(filename, encoding=encoding) + self.encoding = encoding + self.tmpfile = NamedTemporaryFile(mode="w", delete=False, encoding=encoding, dir=Path(filename).parent) + + def __enter__(self): + return self.ifile, self.tmpfile + + def __exit__(self, typ, value, tb): + self.ifile.close() + self.tmpfile.close() + if typ is None: + os.rename(self.tmpfile.name, self.filename) + else: + os.unlink(self.tmpfile.name) + +# with open("/etc/hosts") as hosts, open("/etc/shadow") as shadow: +# ... + +with InplaceEdit("hosts.txt") as (ifile, ofile): + for line in ifile: + ofile.write(line.replace("www.mdk.fr", "mdk.fr")) diff --git a/python-avancé/memory.dot b/python-avancé/memory.dot deleted file mode 100644 index c0eda5d..0000000 --- a/python-avancé/memory.dot +++ /dev/null @@ -1,26 +0,0 @@ -digraph Python { - subgraph cluster_0 { - node [style=filled]; - label = "Names"; - color = lightgrey; - un; - deux; - trois; - liste; - } - subgraph cluster_1 { - node [style=filled]; - label = "Values"; - color = lightgrey; - 1; - 2; - 3; - 4; - "[...]" -> 1; - "[...]" -> 2; - } - un -> 1; - deux -> 2; - trois -> 3; - liste -> "[...]"; -}