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