Python Forum
C++-type static members for python classes - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: General (https://python-forum.io/forum-1.html)
+--- Forum: Code sharing (https://python-forum.io/forum-5.html)
+--- Thread: C++-type static members for python classes (/thread-13811.html)



C++-type static members for python classes - Gribouillis - Nov-01-2018

In C++, if a class A has a static string field A::host initialized to "localhost", the value of this field can be changed through the class by writing for example A::host = "127.0.0.1" or through an instance a of the class, by writing a.host = '127.0.0.1'. Both statements have the same net result.

In python if class A has a member A.host and a is an instance of the class, the statement a.host = '127.0.0.1' doesn't change the value of A.host, instead, it updates a member of this particular instance.

The following snippet allows one to define fields in a python class having the same behavior as C++ static fields. This is achieved by creating a metaclass on the fly for the target class with a call to function metaclass_with_cpp_fields() below. The descriptor protocol and the metaclass mechanism make this possible. Let us remark that this doesn't prevent class A from using another metaclass by using the parent_metaclass argument of metaclass_with_cpp_fields.

class Field:
    def __init__(self, initializer):
        self.value = initializer
    def __get__(self, instance, cls):
        return self.value
    def __set__(self, instance, value):
        self.value = value

class MetaField:
    def __init__(self, desc):
        self.desc = desc
    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return self.desc.__get__(instance, cls)
    def __set__(self, instance, value):
        self.desc.__set__(instance, value)

def metaclass_with_cpp_fields(fields, parent_metaclass=type):
    class CppFieldsMeta(parent_metaclass):
        def __new__(cls, name, parents, attrs):
            attrs.update(ifield)
            instance = parent_metaclass.__new__(cls, name, parents, attrs)
            return instance
    ifield = {name: Field(value) for name, value in fields.items()} 
    for name, value in ifield.items():
        setattr(CppFieldsMeta, name, MetaField(value))
    return CppFieldsMeta


if __name__ == "__main__":
    metaA = metaclass_with_cpp_fields(dict(
        host='localhost',
        port=80,))

    class A(metaclass=metaA):
        pass

    class B(A):
        pass

    a = A()
    b = B()
    print(A.host, '-', a.host, '-', B.host)
    A.host = '127.0.0.1'
    print(A.host, '-', a.host, '-', B.host)
    a.host = '0.0.0.0'
    print(A.host, '-', a.host, '-', B.host)
    B.host = 'HOST-B'
    print(A.host, ' - ', a.host, '-', B.host)
    b.host = 'After-b'
    print(A.host, '-', a.host, '-', B.host)
The output of this code proves that the fields behave like C++ static fields
Output:
localhost - localhost - localhost 127.0.0.1 - 127.0.0.1 - 127.0.0.1 0.0.0.0 - 0.0.0.0 - 0.0.0.0 HOST-B - HOST-B - HOST-B After-b - After-b - After-b
For comparison, here is the equivalent C++ code with the same output
#include <iostream>
#include <string>
using namespace std;

class A {
public:
    static string host;
    static int port;
};

class B : public A{
};


string A::host = string("localhost");
int A::port = 80;


int main(){
    A a;
    B b;
    cout << A::host << " - " << a.host << " - " << B::host << endl;
    A::host = "127.0.0.1";
    cout << A::host << " - " << a.host << " - " << B::host << endl;
    a.host = "0.0.0.0";
    cout << A::host << " - " << a.host << " - " << B::host << endl;
    B::host = "HOST-B";
    cout << A::host << " - " << a.host << " - " << B::host << endl;
    b.host = "After-b";
    cout << A::host << " - " << a.host << " - " << B::host << endl;
}