我有一個簡單的元類,它將以“get_”開頭的類的方法轉換為屬性:
class PropertyConvertMetaclass(type):
def __new__(mcs, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val)
else:
new_attr[name] = val
return type.__new__(mcs, future_class_name, future_class_parents, new_attr)
假設我有 TestClass:
class TestClass():
def __init__(self, x: int):
self._x = x
def get_x(self):
print("this is property")
return self._x
我希望它像這樣作業:我創建了一些有點繼承自它們的新類
class NewTestClass(TestClass, PropertyConvertMetaclass):
pass
我可以像這樣重用他們的兩種方法:
obj = NewTestClass(8)
obj.get_x() # 8
obj.x # 8
當我接受它時,我應該創建一個新類,將其命名為 PropertyConvert 并使 NewTestClass 繼承自它:
class PropertyConvert(metaclass=PropertyConvertMetaclass):
pass
class NewTestClass(TestClass, PropertyConvert):
pass
但這無濟于事,我仍然無法在 NewClassTest 中使用新的屬性方法。如何讓 PropertyConvert 繼承其兄弟的所有方法,而不在 NewClassTest 中做任何事情,只更改 PropertyConverterMetaclass 或 PropertyConverter?我是元類的新手,所以很抱歉,如果這個問題看起來很愚蠢。
更新:因為有人告訴我這是不可能的,所以我將在這里留下預期的類的確切行為(這是我的編程課程中的一項任務)。
class OldAndNasty:
def __init__(self, temperature):
self._temperature = temperature
def get_temperature(self):
return self._temperature
def set_temperature(self, temperature):
if temperature <= 0:
raise ValueError("Temperature below zero is not allowed")
self._temperature = temperature
class NewAndShiny(OldAndNasty, PropertyConverter):
pass
# everything works well here
new_obj = NewAndShiny(100)
new_obj.set_temperature(10)
print(new_obj.get_temperature())
new_obj.temperature = 50
print(new_obj.temperature)
uj5u.com熱心網友回復:
沒有什么“不可能”的。這是一個可以用元類解決的問題,無論多么不尋常。
您的方法很好 - 您遇到的問題是,當您查看“future_class_attr”(也稱為類體中的命名空間)時,它只包含當前定義的類的方法和屬性。在您的示例中,NewTestClass是空的,“future_class_attr”也是空的。
解決這個問題的方法是檢查所有基類,尋找與您正在尋找的模式匹配的方法,然后創建適當的屬性。
在創建目標類之前正確執行此操作會很棘手 - 因為必須以所有超類的正確 mro(方法決議順序)進行屬性搜索 - 并且可能存在很多極端情況。(但請注意,這并非“不可能”)
但是沒有什么可以阻止您在創建新類后這樣做。為此,您可以將回傳值分配super().__new__(mcls, ...) 給一個變數(順便說一句,更喜歡 usingsuper().__new__而不是硬編碼type.__new__:這允許您的元類協作并與 collections.ABC 或 enum.Enum 組合)。該變數是您完成的類,您可以使用dir它來檢查所有屬性和方法名稱,已經合并了所有超類 - 然后,只需創建新屬性并使用setattr(cls_variable, property_name, property_object).
更好的是,撰寫元類__init__而不是它的__new__方法:您檢索已經創建的新類,然后可以繼續內省它dir并立即添加屬性。(super().__init__(...)即使您的班級不需要,也不要忘記打電話。)
另外,請注意,自 Python 3.6 起,如果只在__init_subclass__基類的方法中實作所需的邏輯,則完全沒有元類也可以獲得相同的結果。
uj5u.com熱心網友回復:
當您這樣做時TestClass():,類的主體在成為類的命名空間中運行__dict__。元類只是通過__new__和通知該命名空間的構造__init__。在這種情況下,您已將 的元類設定TestClass為type。
當您從 繼承時TestClass,例如 with class NewTestClass(TestClass, PropertyConverter):,PropertyConvertMetaclass您撰寫的版本僅在__dict__of上運行NewTestClass。TestClass那時已經創建,沒有屬性,因為它的元類方式type,子類是空的,所以你看不到屬性。
這里有幾個可能的解決方案。更簡單但由于您的任務而無法實作的方法是 do class TestClass(metaclass=PropertyConvertMetaclass):。TestClasswill 的所有子級都將具有PropertyConvertMetaclass,因此所有 getter 都將轉換為屬性。
另一種方法是仔細查看 的引數PropertyConvertMetaclass.__new__。一般情況下,你只對future_class_attr屬性進行操作。但是,您也可以訪問future_class_bases。如果你想升級 的直接兄弟PropertyConverter,這就是你所需要的:
class PropertyConvertMetaclass(type):
def __new__(mcs, future_class_name, future_class_parents, future_class_attr):
# The loop is the same for each base __dict__ as for future_class_attr,
# so factor it out into a function
def update(d):
for name, value in d.items():
# Don't check for dunders: dunder can't start with `get_`
if name.startswith('get_') and callable(value):
prop = name[4:]
# Getter and setter can't be defined in separate classes
if 'set_' prop in d and callable(d['set_' prop]):
setter = d['set_' prop]
else:
setter = None
if 'del_' prop in d and callable(d['del_' prop]):
deleter = d['del_' prop]
else:
deleter = None
future_class_attr[prop] = property(getter, setter, deleter)
update(future_class_dict)
for base in future_class_parents:
# Won't work well with __slots__ or custom __getattr__
update(base.__dict__)
return super().__new__(mcs, future_class_name, future_class_parents, future_class_attr)
這可能足以滿足您的任務,但缺乏一定的技巧。具體來說,我可以看到兩個不足:
- 除了直接基類之外沒有查找。
- 你不能在一個類中定義一個 getter,而在另一個類中定義一個 setter。
要解決第一個問題,您必須遍歷類的 MRO。正如@jsbueno 所建議的那樣,在完全構造的類上使用__init__而不是類前字典更容易做到這一點。我將通過在創建任何屬性之前制作一個可用的 getter 和 setter 表來解決第二個問題。您還可以通過這樣做使屬性尊重 MRO。using 唯一的復雜之__init__處在于您必須呼叫setattr該類而不是簡單地更新它的 future __dict__。
class PropertyConvertMetaclass(type):
def __init__(cls, class_name, class_parents, class_attr):
getters = set()
setters = set()
deleters = set()
for base in cls.__mro__:
for name, value in base.__dict__.items():
if name.startswith('get_') and callable(value):
getters.add(name[4:])
if name.startswith('set_') and callable(value):
setters.add(name[4:])
if name.startswith('del_') and callable(value):
deleters.add(name[4:])
for name in getters:
def getter(self, *args, **kwargs):
return getattr(super(cls, self), 'get_' name)(*args, **kwargs)
if name in setters:
def setter(self, *args, **kwargs):
return getattr(super(cls, self), 'set_' name)(*args, **kwargs)
else:
setter = None
if name in deleters:
def deleter(self, *args, **kwargs):
return getattr(super(cls, self), 'del_' name)(*args, **kwargs)
else:
deleter = None
setattr(cls, name, property(getter, setter, deleter)
您在__init__元類中所做的任何事情都可以使用類裝飾器輕松完成。主要區別在于元類將適用于所有子類,而裝飾器僅適用于使用它的地方。
uj5u.com熱心網友回復:
我的問題的解決方案之一是在 PropertyConvertMetaclass 中決議父母的字典:
class PropertyConvertMetaclass(type):
def __new__(mcs, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
for parent in future_class_parents:
for name, val in parent.__dict__.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val, parent.__dict__['set_' name[4:]])
new_attr[name] = val
for name, val in future_class_attr.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val, future_class_attr['set_' name[4:]])
new_attr[name] = val
return type.__new__(mcs, future_class_name, future_class_parents, new_attr)
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/383121.html
