[英]Python - Returning multiple functions from closures
我试图通过使用 function 闭包来模拟 Python 中的私有变量。 我的想法是定义一个返回多个 getter 的工厂 function。 在 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 中做同样的事情(即从闭包中返回多个 function 对象)?
所以,第一,这非常不符合 Pythonic,因此我强烈建议不要使用它。 如果你真的需要这个,只需使用双下划线前缀变量,例如:
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
当以__
为前缀(而不以任何_
为后缀)时,名称重整可防止用户意外使用私有变量,以及所讨论的 class 的任何子类(这意味着子类也可以定义一个__name
实例属性,它将是单独的来自父级的定义,它获得了私有变量所需的主要保证)。 @property
装饰方法是使“私有/受保护属性的接口只读访问器”的 Pythonic 方式(它们是用obj.name
/ obj.age
读取的,但不能分配给因为你没有定义一个setter
),这是你应该依赖的。
如果这对您来说太冗长, typing.NamedTuple
和dataclasses.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
,是不可变的(因此您甚至不需要费心使用 public 创建单独的私有属性只读访问器;公共属性已经是只读的)。
或者,使用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__
的有用定义(比默认的“类名加上 memory 地址”更有用的实例字符串化形式)、相等比较和散列(创建实例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 遵循“我们都是成年人”的原则,对用户几乎没有隐藏; “隐私”并不是任何语言的安全措施,它是为了接口定义和分离,而 Python 只是放弃了伪装。
请注意,根据 PEP8,函数应以lowercase
或lowercase_with_underscores
命名,而不是mixedCase
命名。 Python 几乎从不使用mixedCase
(他们仍然使用的少数地方已被弃用并被删除;例如,我相信threading
删除了他们在 3.10 中的最后一个snakeCase
别名;类是他们唯一使用CapWords
的地方,否则一切都是完全上层或完全小写)。 所以如果你想让它有点Pythonic,你会使用名称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 声明为 class。
您使用了正确的约定(单下划线前缀)来命名变量,并且添加 getter 函数是完全有效的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.