Python -从闭包返回多个函数



我试图通过使用函数闭包来模拟Python中的私有变量。我的想法是定义一个返回多个getter的工厂函数。在Javascript中,我会这样写:

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

是否有办法在Python中做同样的事情?,从闭包返回多个函数对象)?

所以,首先,这是非常不符合python的,所以我强烈建议不要这样做。如果你真的需要,可以使用双下划线前缀的变量,例如:

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

当以__为前缀(而不以任何_为后缀)时,名称混淆可以防止意外地被用户使用私有变量,以及相关类的任何子类(意味着子类可以定义__name实例属性,并且它将与父类的定义分开,这将获得私有变量所需的主要保证)。@property修饰方法是使"接口只读访问私有/受保护属性"的python方式。(它们与obj.name/obj.age一起读取,但由于您没有定义setter,因此无法分配),这是您应该依赖的。

如果这对你来说太啰嗦了,typing.NamedTupledataclasses.dataclass是两个很好的选择,可以用来制作非常低样板的类:

# 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

与原始版本john = Student("John", 22)的使用方式相同,并且从tuple派生而来,是不可变的(因此您甚至不需要为公共只读访问器创建单独的私有属性;公共属性已经是只读的)。

或者使用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

再次让Python为您完成工作(使用slots=True或手动定义的__slots__,实例最终比namedtuple/NamedTuple更节省内存,因为它们不必具有每个实例的长度,而继承tuple需要)。

使用任何namedtuple/NamedTuple/dataclass解决方案都增加了额外的好处,可以为默认__repr__(比默认"类名+内存地址"更有用的字符串化实例形式)、相等比较和哈希(使实例成为dict/set的合法成员)提供有用的定义。


话虽如此,如果你必须做这个可怕的事情,最直接的等价是:

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())
但要明确的是,虽然获得它们需要更多的工作(使用inspect模块和它所记录的属性),但这些闭包作用域的变量仍然可以访问,它们不是真正的私有的。Python很少向用户隐藏什么,遵循"我们这里都是成年人"的原则。原则;"privacy"在任何语言中都不打算作为一种安全措施,它是用于接口定义和分离的,Python只是放弃了这种伪装。

注意,根据PEP8,函数应该用lowercaselowercase_with_underscores命名,而不是mixedCase。Python几乎从不使用mixedCase(少数仍在使用的地方已被弃用并被删除;例如,我相信threading在3.10中删除了snakeCase的最后一个别名;类是唯一使用CapWords的地方,否则一切都是完全大写或完全小写)。因此,如果您试图使其成为一个小的Python,您将使用名称get_name/get_age,而不是getName/getAge(正如前面所指出的,在实际的Python代码中,您基本上根本不会看到以get为前缀的方法,因为使用property消除了与方法相关的动词名称的需要,而支持类似属性的访问,保留了普通的名词名称)。

根据你的想法,可以这样写:

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

当然,这不是一种流行的方式。

如果你想隐藏实际的元组子类,你可以参考朋友的方法在评论区

不需要闭包。相反,您可以将Student声明为一个类。

您已经使用了正确的约定(单个下划线前缀)来命名变量,并且添加getter函数是完全有效的。

最新更新