我试图通过使用函数闭包来模拟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.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
派生而来,是不可变的(因此您甚至不需要为公共只读访问器创建单独的私有属性;公共属性已经是只读的)。
或者使用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,函数应该用lowercase
或lowercase_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函数是完全有效的。