The Daily Shaarli

All links of one day in a single page.

Today - 06/15/26

mooc-python-w9-s3-closure

In Python, a closure is a function that captures local variables from its lexical environment, even when the function is called outside of that environment.

The closure retains a reference to the parent function's variables, not their values ​​at any given time

If the parent variable is modified after the closure is created, the closure will see the new value

def add_n(n):
    def f(x):
        return x + n
    return f

add_10 = add_n(10)
print(add_10(100))
>>> 110
print('closure len', add_10.__closure__.__len__())
>>> 1
print('closure content', add_10.__closure__[0].cell_contents)
>>> 10

usefulness

Decorators: Closures are often used to create decorators
Function factories: Create functions with predefined behaviors
Context memory: Preserve state between function calls

modified closure value

nonlocal

def counter_call(f):
    counter = 0
    def wrapper(*args, **kargs):
        nonlocal counter # set counter is non local attribute but free variable
        counter += 1
        r = f(*args, **kargs)
        print(f"{f.__name__} called {counter} times")
        return r
    return wrapper

@counter_call
def poly(n):
    return sum(i**5 for i in range(n))

function attribute

def counter_call(f):
    def wrapper(*args, **kargs):
        wrapper.counter += 1
        r = f(*args, **kargs)
        print(f"{f.__name__}() called {wrapper.counter} times")
        return r
    wrapper.counter = 0 # evaluate before wrapper calling
    return wrapper

@counter_call
def poly(n):
    return sum(i**5 for i in range(n))
r = poly(10)
>>> poly() called 1 times
print(poly(10))
>>> poly() called 2 times
print(poly.counter) # poly = wrapper(poly) et wrapper has attribute counter
>>> 2
mooc-python-w9-s6-attribute-attr

getattribute and setattr

'getattribute' and 'setattr' are two special methods (dunder methods) used to control access to a class's attributes. Here's a clear explanation of their role and differences:

getattribute(self, name)

Role: Called every time an attribute is accessed (read), even if the attribute doesn't exist

Default behavior: Searches for the attribute in the instance, then in the class, and then in the parent classes

usefulness

  • Intercept access to an attribute to add logic (e.g., logging, validation)
  • Raise an exception if the attribute is not allowed

Caution

If you override 'getattribute', you must call 'super().getattribute(name)' to avoid blocking access to internal attributes

setattr(self, name, value)

Role: Called whenever an attribute is modified (written)

Default behavior: Stores the value in the instance

usefulness

  • Validate or modify the value before assigning it
  • Prevent modification of certain attributes
  • Synchronize attributes with other data

Caution

If you override 'setattr', you must use 'self.dict[name] = value' to avoid infinite recursion

example

class Temperature:
    def __get__(self, obj, objtype):
        print("desc __get__")
        return obj._temperature

    def __set__(self, obj, value):
        print(f"desc __set__ {value}")
        obj._temperature = value

class Maison:
    def __init__(self, temperature):
        self.temperature = temperature

    def __getattribute__(self, attr):
        print(f"__getattribute__ : {attr}")
        return object.__getattribute__(self, attr)

    def __setattr__(self, attr, value):
        print(f"__setatt__ : {attr} - {value}")
        return object.__setattr__(self, attr, value)

    temperature = Temperature()

m = Maison(18)
__setatt__ : temperature - 18
desc __set__ 18
__setatt__ : _temperature - 18

m.temperature
__getattribute__ : temperature
desc __get__
__getattribute__ : _temperature

m.temperature = 22
__setatt__ : temperature - 22
desc __set__ 22
__setatt__ : _temperature - 22

m.x = 10
__setatt__ : x - 10

m.x
__getattribute__ : x

m.y
__getattribute__ : y
__getattribute__ : __dir__
__getattribute__ : __dict__
__getattribute__ : __class__
AttributeError: 'Maison' object has no attribute 'y'

getattr(self, name)

'getattr' is a special method (or dunder method) that is called when an attribute is not found in a class instance using the standard methods (getattribute, hasattr, etc.)

It allows you to dynamically manage access to missing attributes

usefulness

  • Creating attributes on the fly

  • Implementing default behaviors for undefined attributes

  • Proxies or wrappers: To redirect attribute calls to another object

  • Dynamic attributes: To generate values ​​based on the attribute name

  • Compatibility with external APIs: To manage attributes that do not yet exist in a class

example

class Redirector:

    def __init__(self, id):
        self.id = id

    def __repr__(self):
        return f"Redirector2({self.id})"

    def __getattr__(self, name):
        def forwarder(arg):
            return f"{self.id} -> {name}({arg})"
        return forwarder

R = Redirector(2)
print(R)
print(R.bar(20))
mooc-python-w9-s4-metaclass

metaclass

A metaclass is a class that defines the behavior of other classes. By default, in Python, classes are instances of 'type', which is the default metaclass. However, you can create your own metaclasses to control class creation, add attributes, or modify class behavior.

usefulness

  • Control class creation: Add attributes, validate properties, or modify the class before it is finalized
  • Singleton: Ensure that a class has only one instance
  • Automatic validation: Verify that certain methods or attributes exist in the classes that use them
  • Automatic registration: Register classes in a registry (for example, for a plugin system)

new

  • Role: 'new' is responsible for memory allocation and instance creation
  • Signature: new(cls, name, bases, namespace)
  • Return: It must return a new instance of the class (or another class if necessary)

usefulness

  • To create singletons (a single instance of a class)
  • To modify the type of the returned instance (for example, to return a subclass)
  • To control object creation (e.g., validation before creation)
class LowerAttrType(type):
    def __new__(cls, name, bases, namespace, **kwds):
        namespace = {(n.lower() if not n.startswith('__') else n): obj for n, obj in namespace.items()}
        bases = (BaseOfAll,)
        return type.__new__(cls, name, bases, namespace, **kwds)    

class BaseOfAll:
    def common_func(self):
        return f"in common_func"

class C(metaclass=LowerAttrType):
    def fUnc_bAd_CAP(selfself):
        return f"in fUnc_bAd_CAP"

c = C()
print('func_bad_cap', 'func_bad_cap' in c.__dir__())
>>> True
print('fUnc_bAd_CAP', 'fUnc_bAd_CAP' in c.__dir__())
>>> False
print('c.common_func', c.common_func())
>>> c.common_func in common_func
print('c.func_bad_cap', c.func_bad_cap())
>>> c.func_bad_cap in fUnc_bAd_CAP
mooc-python-w9-s5-property-descriptor

property

'@property' decorator allows you to define a method as a property of a class. This means you can access that method as if it were an attribute, without using parentheses to call a method.

  • No parentheses: object.property (not object.property()).
  • Convention: Use _<name> for the "private" attribute (e.g., _name).
  • Validation: The setter can include checks.
  • Compatibility: Works with inheritance and abstract classes.

example

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("The radius must be positive")
        self._radius = value

    @radius.deleter
    def radius(self):
        print("Deleting the radius")
        del self._radius

c = Circle(5)
print(c.radius)
>>> 5
c.radius = 10
del c.radius

descriptor

A Python descriptor is an object that implements at least one of the following special methods:

__get__(self, obj, objtype=None)
__set__(self, obj, value)
__delete__(self, obj)

These methods allow you to customize access to, modification of, or deletion of an attribute of a class instance

usefulness

  • Data validation: Check or transform values ​​before assigning them
  • Dynamic calculations: Generate a value on the fly (e.g., @property properties)
  • Data sharing: Manage common attributes between multiple instances

examples

class DescriptorTemperature:
    TEMPMAX = 20

    def __get__(self, obj, objtype=None):
        print('__get__')
        return obj._temperature

    def __set__(self, obj, value):
        print('__set__')
        if value > __class__.TEMPMAX:
            raise ValueError(f"Temperature is too hot:{value}, max:{__class__.TEMPMAX} ")
        obj._temperature = value

class Home:
    def __init__(self, temperature):
        self.temperature = temperature
    temperature = DescriptorTemperature()

h = Home(18)
print(h.temperature)
h.temperature = 20
h.temperature = 30