Skip to content
Advertisement

Python – Returning multiple functions from closures

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).

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement