0

I'm trying to emulate private variables in Python by using function closures. My idea was to define a factory function that returns multiple getters. In Javascript, I would write the following:

function Student(name, age) {
   let _name = name;
   let _age = age;

   return {
     getName() { return _name; },
     getAge() { return _age; }
   };
}

const john = Student("John", 22);

console.log(john.getName()); // prints John
console.log(john.getAge()); // prints 22

Is there any way to do the same in Python(i.e., returning multiple function objects from a closure)?

beep
  • 1,057
  • 2
  • 11
  • 23
  • Have you tried `return lambda: _name, lambda: _age`? – Mechanic Pig Apr 25 '22 at 15:08
  • Have you seen https://stackoverflow.com/a/1641236/924827? – Katu Apr 25 '22 at 15:09
  • @MechanicPig: That gets the hiding/closure, but it doesn't make a virtual object with the functions as named "methods". – ShadowRanger Apr 25 '22 at 15:09
  • @MechanicPig Unfortunately, this approach returns a tuple, I need to be able to access these variables through the getters – beep Apr 25 '22 at 15:13
  • @ShadowRanger Then use namedtuple `Student = namedtuple('Student', ['get_name', 'get_age'])` and `return Student(lambda: _name, lambda: _age)` – Mechanic Pig Apr 25 '22 at 15:13
  • @MechanicPig: Well, it would have to have a different name from the function/pseudo-class (unless you redefine the `namedtuple` on each call, which is not a good idea), but yes, that works. My secondary, bad-idea solution was the more JS-like use of `types.SimpleNamespace` to get the same result without a named `tuple` subclass getting involved, but it's the same basic end result (`namedtuple` would be cheaper per-instance, at the expense of needing predefintion and being much more heavyweight for the class itself). – ShadowRanger Apr 25 '22 at 15:18

3 Answers3

7

So, one, this is horribly unPythonic, so I strongly recommend against it. Just use double underscore prefixed variables if you really need this, e.g.:

class Student:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    @property
    def name(self):
        return self.__name

    @property
    def age(self):
        return self.__age

john = Student("John", 22)
print(john.name)
print(john.age)
john.name = "Steven"  # Dies; the property did not define a setter, so name is read-only

When prefixed with __ (and not suffixed with any _), name-mangling prevents accidental use of private variables by the user, and any subclasses of the class in question (meaning a subclass could also define a __name instance attribute and it would be separate from the parent's definition, which gets the primary guarantee required of private variables). The @property decorated methods are the Pythonic way to make "interface read-only accessors to private/protected attributes" (they're read with obj.name/obj.age, but can't be assigned to since you didn't define an setter), and it's what you should be relying on.

If that's too verbose for you, typing.NamedTuple and dataclasses.dataclass are two nice options for making extremely low-boilerplate classes:

# Functional version (harder to add custom features to)
from collections import namedtuple

Student = namedtuple('Student', ('name', 'age'))

# Typed version (that's also easier to add features to)
from typing import NamedTuple

class Student(NamedTuple):
    name: str
    age: int

both of which are used the same as you used the original version, john = Student("John", 22), and, being derived from tuple, are immutable (so you don't even need to bother making separate private attributes with public read-only accessors; the public attributes are already read-only).

Alternatively, with dataclasses:

from dataclasses import dataclass

@dataclass(frozen=True, slots=True)  # Omit slots=True pre-3.10
class Student:
    __slots__ = 'name', 'age'  # Omit on 3.10+ where slots=True does the work for you
    name: str
    age: int

which again lets Python do the work for you (with slots=True or manually defined __slots__, the instances end up even more memory-efficient than namedtuple/NamedTuple, as they don't have to have a per-instance length, which inheriting from tuple requires).

Using any namedtuple/NamedTuple/dataclass solution adds the additional benefits of giving you useful definitions for a default __repr__ (a more useful stringified form of an instance than the default "class name plus memory address"), equality comparison, and hashing (making instances legal members of a dict/set).


All that said, if you must do this terrible thing, the most direct equivalent would be:

import types

def Student(name, age):
    def getName():
        return name
    def getAge():
        return age
    # A simple dict wrapper that lets you use dotted attribute-style lookup instead
    # of dict-style bracketed lookup
    return types.SimpleNamespace(getName=getName, getAge=getAge)

# Even less Pythonic alternative using unnamed lambdas (so printing the methods
# themselves will tell you only that they are closures defined in Student
# but not the name of the methods themselves, in exchange for shorter code:
def Student(name, age):
    return types.SimpleNamespace(getName=lambda: name, getAge=lambda: age)


john = Student("John", 22)
print(john.getName())
print(john.getAge())

But to be clear, while it's more work to get at them (using the inspect module and the attributes it documents), those closure scoped variables are still accessible, they're not truly private. Python hides very little from users, following the "we're all adults here" principle; "privacy" is not intended as a security measure in any language, it's for interface definition and separation, and Python just drops the pretense.

Note that, per PEP8, the functions should be named with lowercase or lowercase_with_underscores naming, not mixedCase. Python almost never uses mixedCase (and the few places they still do are deprecated and being removed; e.g. I believe threading removed the last of their snakeCase aliases in 3.10; classes are the only place they use CapWords, and otherwise everything is either fully upper or fully lower case). So if you're trying to make it even a little Pythonic, you'd use the names get_name/get_age, not getName/getAge (and as noted further up, in real Python code, you basically never see get prefixed methods at all, because using property removes the need for verb-names associated with methods, in favor of attribute-like access that keeps the plain noun-name).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • The OOP approach looks cleaner indeed, but I have to stick to functional one. Thanks for both examples! – beep Apr 25 '22 at 15:16
1

According to your idea, it can be written as follows:

>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['get_name', 'get_age'])
>>> def student(name, age):
...     return Student(lambda: name, lambda: age)
...
>>> i = student('hello', 19)
>>> i
Student(get_name=<function student.<locals>.<lambda> at 0x000001797FA62A70>, get_age=<function student.<locals>.<lambda> at 0x000001797FA62DD0>)
>>> i.get_name()
'hello'
>>> i.get_age()
19

Of course, this is not a popular way.

If you want to hide the actual tuple subclass, you can refer to the way of friend in the comment area.

Mechanic Pig
  • 6,756
  • 3
  • 10
  • 31
  • To be clear, you don't even need to reassign the arguments; `name`/`age` could be used directly without bothering to alias them to `_name`/`_age`. Downside to using `namedtuple` is that each redefintion of the `namedtuple` consumes a KB or so of memory (usually okay, since it reduces the per-instance memory usage, but not so great when you make a new one for each instance). – ShadowRanger Apr 25 '22 at 15:22
  • @ShadowRanger Yes, so I consider defining `Student` only once, but then I can't hide the actual subclass of tuple. – Mechanic Pig Apr 25 '22 at 15:32
  • 1
    You can, but it's ugly in a different way: `def Student(name, age, *, _cls=namedtuple('Student', ['get_name', 'get_age'])): return _cls(lambda: name, lambda: age)`. You hide the definition of the `namedtuple` in the arguments (and make it a keyword-only argument, to prevent accidentally passing it, and prefixing the name with `_`, indicating it's not part of the public API so users shouldn't pass it themselves). Either that, or make another layer of wrapping, with a `make_Student` method that defines the `namedtuple` and `def Student`, returning `Student` that finds the class from closure. :-) – ShadowRanger Apr 25 '22 at 15:38
  • 1
    Personally, I don't think it's worth the bother here (the per-method closure scope itself adds non-trivial overhead, to the point where this will never be a cheap solution, memory-wise, thus why I stuck to `types.SimpleNamespace`, which avoids the need for a dedicated class at all), but I *do* like `namedtuple`, and will never say it's a bad idea (I did up-vote). – ShadowRanger Apr 25 '22 at 15:40
-1

The closure isn't necessary. Instead you can declare Student as a class.

You have used the correct convention (single underscore prefix) for the naming of the variables, and adding the getter functions is perfectly valid.

Don
  • 164
  • 3