简体   繁体   English

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

[英]Python - Returning multiple functions from closures

I'm trying to emulate private variables in Python by using function closures.我试图通过使用 function 闭包来模拟 Python 中的私有变量。 My idea was to define a factory function that returns multiple getters.我的想法是定义一个返回多个 getter 的工厂 function。 In Javascript, I would write the following:在 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

Is there any way to do the same in Python(ie, returning multiple function objects from a closure)?有什么方法可以在 Python 中做同样的事情(即从闭包中返回多个 function 对象)?

So, one, this is horribly unPythonic, so I strongly recommend against it.所以,第一,这非常不符合 Pythonic,因此我强烈建议不要使用它。 Just use double underscore prefixed variables if you really need this, eg:如果你真的需要这个,只需使用双下划线前缀变量,例如:

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).当以__为前缀(而不以任何_为后缀)时,名称重整可防止用户意外使用私有变量,以及所讨论的 class 的任何子类(这意味着子类也可以定义一个__name实例属性,它将是单独的来自父级的定义,它获得了私有变量所需的主要保证)。 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. @property装饰方法是使“私有/受保护属性的接口只读访问器”的 Pythonic 方式(它们是用obj.name / obj.age读取的,但不能分配给因为你没有定义一个setter ),这是你应该依赖的。

If that's too verbose for you, typing.NamedTuple and dataclasses.dataclass are two nice options for making extremely low-boilerplate classes:如果这对您来说太冗长, 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

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).两者的使用方式与您使用的原始版本相同, john = Student("John", 22) ,并且派生自tuple ,是不可变的(因此您甚至不需要费心使用 public 创建单独的私有属性只读访问器;公共属性已经是只读的)。

Alternatively, with dataclasses :或者,使用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).这再次让 Python 为您完成工作(使用slots=True或手动定义的__slots__ ,实例最终比namedtuple / NamedTuple更节省内存,因为它们不必具有每个实例的长度,它继承自tuple需要)。

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 ).使用任何namedtuple / NamedTuple / dataclass解决方案都会增加额外的好处,即为您提供默认__repr__的有用定义(比默认的“类名加上 memory 地址”更有用的实例字符串化形式)、相等比较和散列(创建实例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.但需要明确的是,虽然获取它们需要更多工作(使用inspect模块及其记录的属性),但这些闭包作用域变量仍然可以访问,它们并不是真正私有的。 Python hides very little from users, following the "we're all adults here" principle; Python 遵循“我们都是成年人”的原则,对用户几乎没有隐藏; "privacy" is not intended as a security measure in any language, it's for interface definition and separation, and Python just drops the pretense. “隐私”并不是任何语言的安全措施,它是为了接口定义和分离,而 Python 只是放弃了伪装。

Note that, per PEP8, the functions should be named with lowercase or lowercase_with_underscores naming, not mixedCase .请注意,根据 PEP8,函数lowercaselowercase_with_underscores命名,而不是mixedCase命名 Python almost never uses mixedCase (and the few places they still do are deprecated and being removed; eg 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). Python 几乎从不使用mixedCase (他们仍然使用的少数地方已被弃用并被删除;例如,我相信threading删除了他们在 3.10 中的最后一个snakeCase别名;类是他们唯一使用CapWords的地方,否则一切都是完全上层或完全小写)。 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).所以如果你想让它有点Pythonic,你会使用名称get_name / get_age ,而不是getName / getAge (正如进一步指出的那样,在真正的 Python 代码中,你基本上根本看不到get前缀方法,因为使用property消除了对与方法关联的动词名称的需要,有利于保留普通名词名称的类似属性的访问)。

According to your idea, it can be written as follows:按照你的想法,可以这样写:

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

Of course, this is not a popular way.当然,这不是一种流行的方式。

If you want to hide the actual tuple subclass, you can refer to the way of friend in the comment area.如果想隐藏实际的元组子类,可以参考评论区朋友的方式。

The closure isn't necessary.关闭是没有必要的。 Instead you can declare Student as a class.相反,您可以将 Student 声明为 class。

You have used the correct convention (single underscore prefix) for the naming of the variables, and adding the getter functions is perfectly valid.您使用了正确的约定(单下划线前缀)来命名变量,并且添加 getter 函数是完全有效的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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