繁体   English   中英

Python - 从闭包中返回多个函数

[英]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.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 ,是不可变的(因此您甚至不需要费心使用 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,函数lowercaselowercase_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM