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)?
Advertisement
Answer
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).