[英]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.