Skip to main content

classx Logo

PyPI version License: MIT PyPI Downloads

Python does not provide truly private attributes and methods. Its __name convention only uses name mangling, meaning private members can still be accessed from outside the class.

This library introduces a new kind of class designed for genuine encapsulation. It allows you to define truly private attributes and methods that cannot be directly accessed or bypassed from outside the class, providing a stronger form of privacy than Python's built-in classes.

Installation

You can install classx via pip:

pip install classx

Operations on classes

import classx


# classx type constructor and methods must be separated as functions.

# The best practice in this case when each method and the constructor are
# separated out of one body is to name each method and the constructor
# with the name of the class for example Person and after it the name of
# method or constructor: Person_constructor, Person_some_method_name.


# ----------------- Controllers -----------------


# Private Method
def Person_name_controller(new_name: str) -> str:

    if not isinstance(new_name, str):
        raise TypeError("The name must be text")

    new_name = new_name.strip()

    if not new_name:
        raise ValueError("The name must not be empty")

    if not new_name.isalpha():
        raise ValueError("The name must be a valid text")

    return new_name


# Private Method
def Person_age_controller(new_age: int) -> int:

    if not isinstance(new_age, int):
        raise TypeError("Age must be an integer")

    if new_age < 0 or new_age > 150:
        raise ValueError("Age is irrational")

    return new_age


# --------- Constructor ---------


# Always Public
def Person_constructor(name: str, age: int) -> None:

    # If arguments are the same private attributes don't rewrite them
    # inside the constructor body (auto declared) but if the private
    # attribute is not one of the args you need to declare it inside.

    # Every attribute inside the constructor is private in default
    # and no way to make it public.

    # Also you can write any logic inside the constructor.
    Person_name_controller(name)
    Person_age_controller(age)

    # If no logic needed inside here just write pass not return
    # it will raise a TypeError


# --------- Setters ---------


# Public Method
def Person_set_name(new_name: str) -> None:

    # Here we name the argument 'new_name' not 'name' like the private
    # attribute 'name' declared in the constructor because if we name it
    # 'name' we confuse the compiler and it will raise a SyntaxError
    # because no self here like in traditional classes self.name the
    # attribute and the argument 'name' not the same but here 'name' the
    # private attribute and 'name' the argument are the same in naming and
    # the best practice in all cases here and traditional classes is to
    # write 'new_name' / self.new_name because is a true new name to set
    # from the user even if it's the same old name as value but we set it
    # again that mean new.

    name = Person_name_controller(new_name)


# Public Method
def Person_set_age(new_age: int) -> None:

    # Here we name the argument new_age not age like the private attribute
    # age declared in the constructor because if we name it age we confuse
    # the compiler and it will raise a SyntaxError because no self here
    # like in traditional classes self.age the attribute and the argument
    # age not the same but here age the private attribute and age the
    # argument are the same in naming and the best practice in all cases
    # here and traditional classes is to write new_age / self.new_age
    # because is a true new age to set from the user even if it's the same
    # old age as value but we set it again that mean new.

    age = Person_age_controller(new_age)


# ----- Getters -----


# Public Method
def Person_get_name() -> str:
    return name


# Public Method
def Person_get_age() -> int:
    return age


# ----- Display -----


# Public Method
def Person_display() -> None:
    print(f"Your name is {name} and your age is {age}")


# All classes it will be created inside classx namespaces not here.

# Here we create a new namespace in classx using his engine and point
# to them using a pointer named ns

ns = classx.create_namespace("ns")

# Here we create the Person class inside the same namespace we point
# to them like this you can create many classes with the same name
# in different namespaces without any collision and without removing
# it from the current namespace.

Person = ns.create_class(
    name="Person",
    constructor=Person_constructor,
    public_methods={
        "set_name": Person_set_name,
        "set_age": Person_set_age,
        "get_name": Person_get_name,
        "get_age": Person_get_age,
        "display": Person_display,
    },
    private_methods=[
        Person_name_controller,
        Person_age_controller,
    ],
)


# Entry Point
def main() -> None:

    p1 = Person("ahmed", 24)  # Create a new Person object
    p1.display()

    print("------")

    p1.set_name("mohamed")  # Set a new name.
    p1.set_age(23)  # Set a new age.
    p1.display()

    print("------")

    try:
        p1.set_name("")
    except ValueError as e0:
        print("ValueError handled")  # Because invalid input.

    try:
        p1.set_age(-7)
    except ValueError as e1:
        print("ValueError handled")  # Because invalid input.

    try:
        print(p1.name)
    except AttributeError as e2:
        print("AttributeError handled")  # Because attribute name is private.

    try:
        print(p1.age)
    except AttributeError as e3:
        print("AttributeError handled")  # Because attribute age is private.

    try:
        p1.name = "unknown"
    except AttributeError as e4:
        print("AttributeError handled")  # Because attribute name is private.

    try:
        p1.age = 22
    except AttributeError as e5:
        print("AttributeError handled")  # Because attribute age is private.

    try:
        p1.set_name = None
    except AttributeError as e6:  # Because methods are just attributes.
        print("AttributeError handled")  # You can't assign to public methods

    try:
        p1.Person_name_controller("user")
    except AttributeError as e7:  # Because methods are just attributes.
        print("AttributeError handled")  # Because this method is private.

    try:
        p1.Person_age_controller(7)
    except AttributeError as e8:  # Because methods are just attributes.
        print("AttributeError handled")  # Because this method is private.

    try:
        p1.gender = "male"
    except AttributeError as e9:
        print("AttributeError handled")  # You can't create attributes from outside.


if __name__ == "__main__":
    main()
import classx


# Custom Error
class BankError(Exception):
    pass


# Private Method
def BankAccount_owner_name_controller(name: str) -> str:

    if not isinstance(name, str):
        raise TypeError("The name must be text")

    name = name.strip()

    if not name:
        raise ValueError("The name must not be empty")

    if not name.isalpha():
        raise ValueError("The name must be a valid text")

    return name


# Private Method
def BankAccount_amount_controller(amount: int) -> int:

    if not isinstance(amount, (int, float)):
        raise TypeError("The amount must be a number")

    if amount <= 0:
        raise ValueError("The amount must be greater than 0")

    return amount


# Constructor (Always Public)
def BankAccount_constructor(owner: str, balance: int) -> None:

    BankAccount_owner_name_controller(owner)
    BankAccount_amount_controller(balance)

    if balance < 10000:

        raise BankError("You need amount of 10000 or greater to open a new account")


# Public Method
def BankAccount_deposit(amount: int) -> None:

    balance += BankAccount_amount_controller(amount)

    print(f"Deposited {amount}. Current Balance: {balance}")


# Public Method
def BankAccount_withdraw(amount: int) -> None:

    BankAccount_amount_controller(amount)

    if amount > balance:
        raise BankError("Not enough balance")

    balance -= amount
    print(f"Withdrawn {amount}. Current Balance: {balance}")


# Public Method
def BankAccount_display_balance() -> None:

    print(f"Account Holder: {owner}")
    print(f"Balance: {balance}")


ns = classx.create_namespace("ns")

BankAccount = ns.create_class(
    name="BankAccount",
    constructor=BankAccount_constructor,
    public_methods={
        "deposit": BankAccount_deposit,
        "withdraw": BankAccount_withdraw,
        "display_balance": BankAccount_display_balance,
    },
    private_methods=[
        BankAccount_owner_name_controller,
        BankAccount_amount_controller,
    ],
    # You need to mention all used components inside methods and constructor.
    components={BankError.__name__: BankError},
)


# Entry Point
def main() -> None:

    account = BankAccount("mohamed", 10000)

    account.display_balance()

    print("------")

    account.deposit(5000)
    account.withdraw(3000)

    print("------")

    account.display_balance()

    print("------")

    try:
        account.withdraw(50000)
    except BankError as e0:
        print("BankError handled")

    try:
        account.deposit(-10)
    except ValueError as e1:
        print("ValueError handled")


if __name__ == "__main__":
    main()
import classx


ns = classx.create_namespace("ns")

Database = ns.create_class(
    name="Database",
    singleton=True,  # To make a singleton class
)


def main() -> None:

    db1 = Database()
    db2 = Database()

    print(id(db1))  # Same id as db2 id.
    print(id(db2))  # Same id as db1 id.

    print(db1 is db2)  # True

    # To check if a class singleton or not.
    print(ns.is_singleton(Database))  # True


if __name__ == "__main__":
    main()
import classx


ns = classx.create_namespace("ns")

User = ns.create_class(
    name="User",
    overwrite=True,  # To make the class overwritable
)


def main() -> None:

    global User

    User = ns.create_class(name="User")  # overwrite is False in default.

    # Now you can't overwrite again directly you need to get
    # the permissions from the last overwritable class version.

    try:
        User = ns.create_class(name="User")
    except TypeError as e0:
        # Because the last overwritable class version is not overwritable
        print("TypeError handled")

    # To check if a class is overwritable or not
    print(ns.is_overwritable(User))  # False


if __name__ == "__main__":
    main()
import classx


ns = classx.create_namespace("ns")

Image = ns.create_class(
    name="Image",
    public_constants={"format": "PNG"},
)


def main() -> None:

    img = Image()
    print(img.format)  # read only public attribute.


if __name__ == "__main__":
    main()
import classx


# In this case we don't write Circle_square or Square_square because is
# used in the two classes is common between them with neutral name and
# also you can use square directly as a normal function.


def square(value: float) -> float:
    return value * value


def Circle_constructor(radius: float) -> None:
    pass


def Circle_area() -> float:
    return 3.14159 * square(radius)


def Square_constructor(side: float) -> None:
    pass


def Square_area() -> float:
    return square(side)


ns = classx.create_namespace("ns")

Circle = ns.create_class(
    name="Circle",
    constructor=Circle_constructor,
    public_methods={
        "area": Circle_area,
    },
    private_methods=[square],
)

Square = ns.create_class(
    name="Square",
    constructor=Square_constructor,
    public_methods={
        "area": Square_area,
    },
    private_methods=[square],
)


def main() -> None:

    ci = Circle(3)
    print(ci.area())

    print("------")

    sq = Square(3)
    print(sq.area())


if __name__ == "__main__":
    main()
import classx



ns1 = classx.create_namespace("ns1")
ns2 = classx.create_namespace("ns2")


def main() -> None:

    ns1.set_class(name="User")
    ns2.set_class(name="User")

    print(ns1.contains(ns1.get_class("User")))  # True
    print(ns2.contains(ns2.get_class("User")))  # True

    print(ns1.contains(ns2.get_class("User")))  # False
    print(ns2.contains(ns1.get_class("User")))  # False


if __name__ == "__main__":
    main()
import classx


ns = classx.create_namespace("ns")

User = ns.create_class(name="User")


def main() -> None:

    global User

    print(User)  # <function User at @ddress>
    print(User.__name__)  # User

    # Just like JavaScript typeof AnyCls === "function"
    print(type(User))  # User class is function object (meta class function).

    print(type(User).__name__)  # function

    print(type(type(User)))  # function class is type object (super meta class type).
    print(type(type(User)).__name__)  # type

    print(ns.methods())  # Show all namespace methods

    # To check if a class exist inside a namespace or not
    print(ns.exists("User"))  # True

    # or

    # print(ns.exists(User.__name__))  # True

    print(ns.length())  # Show how many classes in this namespace.

    print(ns.classes())  # Show all classes in this namespace.

    # Check if ns contains this User class version or not
    print(ns.contains(User))  # True

    user = User()
    print(user)  # User() object with no attributes/methods (empty).

    print(type(user))  # <class 'classx.User'>
    print(type(user).__name__)  # User

    print(ns.type(user))  # <function User at @ddress>
    print(ns.type(user).__name__)  # User

    print(ns.isinstance(user, User))  # True

    ns.remove_class("User")  # Remove exact class User with name.

    # or

    # ns.remove_class(User.__name__)

    print(ns.classes())  # () zero class

    ns.set_class(name="User")

    print(
        ns.contains(User)
    )  # False (Bceause this class version with this pointer are deleted from ns)

    # Now global User pointer point to the new User class
    User = ns.get_class("User")

    print(ns.contains(User))  # True

    ns.clear_classes()
    print(ns.classes())  # () zero class


if __name__ == "__main__":
    main()
import classx
import typing



ns = classx.create_namespace("ns")

User1 = ns.create_class(name="User1")
User2 = ns.create_class(name="User2")


def main() -> None:

    user1 = User1()
    user2 = User2()

    print(ns.isinstance(user1, User1))  # True
    print(ns.isinstance(user2, User2))  # True

    print(ns.isinstance(user1, (User1, user2)))  # True
    print(ns.isinstance(user2, (User1, User2)))  # True

    print(ns.isinstance(user1, typing.Union[User1, User2]))  # True
    print(ns.isinstance(user2, typing.Union[User1, User2]))  # True

    print(ns.isinstance(user1, User2))  # False
    print(ns.isinstance(user2, User1))  # False


if __name__ == "__main__":
    main()
import classx


class Node:

    __slots__: tuple[str, str] = ("data", "next")

    def __init__(self, data: object) -> None:

        self.data = data
        self.next: None | Node = None


def LinkedList_constructor() -> None:

    head: None | Node = None
    tail: None | Node = None
    length: int = 0


def LinkedList_add(data: object) -> None:

    node = Node(data)

    if head is None:
        head = node
    else:
        tail.next = node

    tail = node
    length += 1


def LinkedList_length() -> None:
    return length


def LinkedList_show() -> None:

    current: Node = head
    string: list = []

    while current:

        string.append(f"{current.data} -> ")
        current = current.next

    string.append("None")
    print("".join(string))


ns = classx.create_namespace("ns")

LinkedList = ns.create_class(
    name="LinkedList",
    constructor=LinkedList_constructor,
    public_methods={
        "add": LinkedList_add,
        "length": LinkedList_length,
        "show": LinkedList_show,
    },
    # This library intentionally omits inheritance in favor of composition.
    components={Node.__name__: Node},
)


def main() -> None:

    obj = LinkedList()

    obj.show()
    print(f"length: {obj.length()}")

    print("------")

    obj.add(1)
    obj.show()
    print(f"length: {obj.length()}")

    print("------")

    obj.add(2)
    obj.show()
    print(f"length: {obj.length()}")

    print("------")

    obj.add(3)
    obj.show()
    print(f"length: {obj.length()}")


if __name__ == "__main__":
    main()
import classx


# Here we named the constructor with the same class name just like c++
# and java constructor style but when we create another pointer with the
# same class name point to the class it's self after creating it we loose
# the original constructor function pointer but it doesn't matter because
# also when we create a normal class we don't have pointers to the methods
# inside it this is the default because we don't need them.

# If you create the class inside the main function you need to use global
# keyword with the constructor function pointer to avoid UnboundLocalError


def Point(x: int, y: int) -> None:
    pass


# We can use private attributes as public attributes if we make setters
# without any validation and the good thing in this case we can make this
# public attributes any time private with adding a validation layer.


def Point_get_x() -> int:
    return x


def Point_set_x(new_x: int) -> None:
    x = new_x


def Point_get_y() -> int:
    return y


def Point_set_y(new_y: int) -> None:
    y = new_y


ns = classx.create_namespace("ns")

Point = ns.create_class(
    name="Point",
    constructor=Point,
    public_methods={
        "get_x": Point_get_x,
        "set_x": Point_set_x,
        "get_y": Point_get_y,
        "set_y": Point_set_y,
    },
)


def main() -> None:

    point = Point(1, 3)

    print(point.get_x())
    print(point.get_y())

    point.set_x(5)
    point.set_y(7)

    print(point.get_x())
    print(point.get_y())


if __name__ == "__main__":
    main()
import classx


# You can create public static attributes/methods from outside the class


def greet(name: str) -> str:
    return f"Hello, {name}!"


# You can access public static attributes/methods inside the class methods


def User_get_name() -> str:
    return User.name


def User_set_name(name: str) -> None:
    User.name = name
    print(User.greet(name))


ns = classx.create_namespace("ns")

User = ns.create_class(
    name="User",
    public_methods={
        "get_name": User_get_name,
        "set_name": User_set_name,
    },
)


# Public Static Attribute
User.name = "ahmed"

# Chain the greet function with the User class as public static method.
User.greet = greet


def main() -> None:

    print(User.name)
    print(User.greet(User.name))

    user = User()

    print(user.get_name())
    user.set_name("mohamed")
    print(user.get_name())

    print(User.name)


if __name__ == "__main__":
    main()

Operations on namespaces

import classx as clsx


ns = clsx.create_namespace("ns", overwrite=True)  # set and get a new namespace.


def main() -> None:

    global ns

    print(type(clsx))  # <class 'classx.Engine'>
    print(type(ns))  # <class 'classx.Namespace'>

    print(clsx.is_overwritable(ns))  # True

    clsx.set_namespace("ns")  # set a new namespace (overwrite is False in default).

    ns = clsx.get_namespace("ns")  # get the namespace ns

    print(clsx.is_overwritable(ns))  # False

    print(ns.name())  # "ns"

    print(clsx.exists("ns"))  # True

    print(clsx.is_namespace(ns))  # True

    print(clsx.length())  # 1 namespace

    print(clsx.methods())  # Show all namespaces methods

    print(clsx.namespaces())  # show all current namespaces

    clsx.remove_namespace("ns")  # Remove exact namespace ns with name

    # or

    # clsx.remove_namespace(ns.name())

    print(clsx.length())  # 0 namespace

    clsx.set_namespace("ns")

    print(clsx.length())  # 1 namespace

    clsx.clear_namespaces()  # Remove all namespaces

    print(clsx.length())  # 0 namespace


if __name__ == "__main__":
    main()

Modules level

You can use classx to secure your modules like this

# mymodule.py


import sys
import classx


def hello() -> None:
    print("Hello!")


def add(a: int, b: int) -> int:
    return a + b


class Person:
    def __init__(self, name: str) -> None:
        self.name = name

    def greet(self) -> None:
        print(f"Hi, I'm {self.name}")


def Person_wrapper(name: str) -> Person:
    return Person(name)


ns = classx.create_namespace("ns")

mymodule = ns.create_class(
    name="mymodule",
    public_methods={
        hello.__name__: hello,
        add.__name__: add,
        "Person": Person_wrapper,
    },
    components={Person.__name__: Person},
)


sys.modules[__name__] = mymodule()


def main() -> None:
    print("mymodule")


if __name__ == "__main__":
    main()
import mymodule


def main() -> None:

    print(type(mymodule))  # <class 'classx.mymodule'>

    mymodule.hello()

    print(mymodule.add(1, 2))

    person = mymodule.Person("mohamed")
    person.greet()

    try:
        person.greet = "something"
    except AttributeError as e0:
        print("AttributeError handled")  # When you use a classx type

    try:
        person.greet()
    except TypeError as e1:
        print("TypeError handled")  # When you use a normal class

    try:
        mymodule.hello = "something"
    except AttributeError as e2:
        print("AttributeError handled")

    try:
        mymodule.add = "something"
    except AttributeError as e3:
        print("AttributeError handled")

    try:
        mymodule.Person = "something"
    except AttributeError as e4:
        print("AttributeError handled")


if __name__ == "__main__":
    main()

The same module example using classx type Person for full privacy and encapsulation

# mymodule.py


import sys
import classx


def hello() -> None:
    print("Hello!")


def add(a: int, b: int) -> int:
    return a + b


def Person_constructor(name: str) -> None:
    pass


def Person_greet() -> None:
    print(f"Hi, I'm {name}")


ns = classx.create_namespace("ns")

Person = ns.create_class(
    name="Person",
    constructor=Person_constructor,
    public_methods={"greet": Person_greet},
)


def Person_wrapper(name: str) -> Person:
    return Person(name)


mymodule = ns.create_class(
    name="mymodule",
    public_methods={
        hello.__name__: hello,
        add.__name__: add,
        "Person": Person_wrapper,
    },
    components={Person.__name__: Person},
)


sys.modules[__name__] = mymodule()


def main() -> None:
    print("mymodule")


if __name__ == "__main__":
    main()

Don't write from classx import * because is not allowed and it's a bad practice.

License

This project is licensed under the MIT LICENSE - see the LICENSE for more details.

Metadata

Release files for classx 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for classx 1.0.0
File Size Uploaded Provenance Actions
classx-1.0.0.tar.gz 18.8 kB Without provenance Details for classx-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for classx 1.0.0
File Interpreter ABI Platform Actions
classx-1.0.0-py3-none-any.whl Python 3 none any Details for classx-1.0.0-py3-none-any.whl

Total release size: 31.6 kB

Release files / classx-1.0.0.tar.gz

Download URL classx-1.0.0.tar.gz
Size 18.8 kB
Tags Source
SHA-256 checksum
How to use checksums
3d4a095143f8656976dbdeab5b4a42e5f0ac59931434c440a4342b007bf16874
BLAKE2b-256 checksum
How to use checksums
15838f84433564a62893594ca5696e9a326f86100d638798846221471602df70
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.2

Release files / classx-1.0.0-py3-none-any.whl

Download URL classx-1.0.0-py3-none-any.whl
Size 12.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
432d7a8b6deaf0bf6a65c4de5b2c6c4cf6da9f9641c51b299394246b9223fda8
BLAKE2b-256 checksum
How to use checksums
1e4aeb277ed965208f3b55a152e44d91d44452952ba7ecd9716528f549cb4a46
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.2

Security for classx 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page