python 中可能存在错误。创建装饰器自动生成属性
Possible bug in python. Create decorator to automatically generate properties
我创建了一个 repl.it
到 运行 在线代码:
https://repl.it/repls/ShimmeringQuickwittedHarddrive#main.py
我正在尝试创建一个装饰器以将对象属性中的属性“携带”到对象中。
简单示例(伪代码):
class carry:
''' Decorator. '''
class Prop:
''' Representation of '@property'. '''
# impl
# impl
class A:
def __init__(self):
self._val_0 = 'A: val_0'
self._val_1 = 'A: val_1'
@property
def val_0(self):
return self._val_0
@property
def val_1(self):
return self._val_1
# '_a' - the name of the attribute to get the @property'ies from.
# 'carry.Prop(...)' - a list of @property'ies to carry.
@carry(
'_a',
carry.Prop('val_0'),
carry.Prop('val_1')
)
class B:
def __init__(self):
self._a = A()
我期望得到的:
>>> b = B()
>>> print(b.val_0)
'A: val_0'
>>> print(b.val_1)
'A: val_1'
我得到了什么:
(警告:差异非常细微,容易被忽略;b.val_0
returns b.val_1
的值)
>>> b = B()
>>> print(b.val_0)
'A: val_1'
>>> print(b.val_1)
'A: val_1'
下面的代码是重现错误的“可执行”简约示例。
代码很多所以我把它分成三个部分:
- 装饰者:
装饰器的实现。
carry.__call__()
. 内某处发生错误
- 测试:
'print-debug' 测试 @carry
装饰器。
重要的是要注意,当我打印 b.val_0
时,我得到的不是 val_0
,而是传递给 @carry
ctor 的最后一个 @property
的表示。
对我来说,写 setattr(<parrent>, <name>, property(<fget, fset, fdel>))
似乎在 python 的某个地方被窃听了。我希望它创建的不是创建 property()
,而是覆盖它们。
import pprint
from copy import copy
pp = pprint.PrettyPrinter(
indent = 2,
width = 80,
depth = None,
compact = False,
sort_dicts = False
)
##################################################
# Decorator:
##################################################
class carry:
class Prop:
''' Represents a 'property'. '''
def __init__(self, name:str, fget:bool=True, fset:bool=False, fdel:bool=False):
self._name = name
self._fget = fget
self._fset = fset
self._fdel = fdel
@property
def name(self) -> str:
return self._name
@property
def fget(self) -> bool:
return self._fget
@property
def fset(self) -> bool:
return self._fset
@property
def fdel(self) -> bool:
return self._fdel
def __init__(self, obj, *props):
self._obj = obj
self._props = props
def __call__(self, cls):
class Template(cls):
pass
Template.__name__ = cls.__name__
Template.__doc__ = cls.__doc__
for prop in self._props:
print('prop repr: ... {}'.format(prop.__dict__))
kwargs = {}
if prop.fget:
def fget(self):
print('fget id: ..... {}'.format(id(fget)))
print('fget addr: ... {}'.format(hex(id(fget))))
print('fget.obj: .... {}'.format(fget.obj))
print('fget.name: ... {}'.format(fget.name))
return getattr(getattr(self, fget.obj), fget.name)
fget.obj = copy(self._obj)
fget.name = copy(prop.name)
print('fget id: ..... {}'.format(id(fget)))
print('fget addr: ... {}'.format(hex(id(fget))))
print('fget.obj: .... {}'.format(fget.obj))
print('fget.name: ... {}'.format(fget.name))
kwargs['fget'] = fget
result = property(**kwargs)
print('kwargs: ...... {}'.format(kwargs))
print('prop: ........ {}'.format(result))
print('prop id: ..... {}'.format(id(result)))
print('prop addr: ... {}'.format(hex(id(result))))
setattr(Template, prop.name, result)
print()
# Return modified class:
return Template
##################################################
# Tests:
##################################################
class A:
def __init__(self):
self._val_0 = 'A: val_0: 0'
self._val_1 = 'A: val_1: 1'
self._val_2 = 'A: val_2: 2'
self._val_3 = 'A: val_3: 3'
@property
def val_0(self):
return self._val_0
@property
def val_1(self):
return self._val_1
@property
def val_2(self):
return self._val_2
@property
def val_3(self):
return self._val_3
print()
print('##################################################')
print()
props = [
carry.Prop('val_0'),
carry.Prop('val_1'),
carry.Prop('val_2'),
carry.Prop('val_3'),
]
@carry('_a', *props)
class B:
def __init__(self):
self._a = A()
print('##################################################')
print()
print(B)
print(B.__name__)
pp.pprint(dir(B))
print()
print('##################################################')
print()
b = B()
print(b.val_0)
print()
print(b.val_1)
print()
print(b.val_2)
print()
print(b.val_3)
print()
首先,让我重申一下我的意见:您似乎在实施“委托”模式。下面是一些代码,可以用更少的行数和更大的灵活性来实现:
https://www.michaelcho.me/article/method-delegation-in-python
名称 'carry' 没有告诉我会发生什么。除非您有一些问题域,其中该名称已经是 well-established,否则我建议您考虑一个不同的名称。话虽如此...
我认为您的问题是您试图 使用 闭包而不 创建 闭包。用于迭代所有属性的循环不是正确的闭包,因此存在泄漏——所有引用最终都指向迭代变量的最后一个值。
我像这样修改了您的示例代码:
if prop.fget:
fget = self.make_fget(self._obj, prop.name)
#def fget(self):
# print('fget id: ..... {}'.format(id(fget)))
# print('fget addr: ... {}'.format(hex(id(fget))))
# print('fget.obj: .... {}'.format(fget.obj))
# print('fget.name: ... {}'.format(fget.name))
# return getattr(getattr(self, fget.obj), fget.name)
#fget.obj = copy(self._obj)
#fget.name = copy(prop.name)
并添加了这个方法:
def make_fget(self, attribute_name, property_name):
""" Construct and return an fget closure. """
def fget(self):
print('fget id: ..... {}'.format(id(fget)))
print('fget addr: ... {}'.format(hex(id(fget))))
print('fget.obj: .... {}'.format(fget.obj))
print('fget.name: ... {}'.format(fget.name))
return getattr(getattr(self, fget.obj), fget.name)
fget.obj = attribute_name
fget.name = property_name
return fget
结果更符合我的预期:
# ... many, many lines elided ...
fget id: ..... 139848106927168
fget addr: ... 0x7f30ecbc6440
fget.obj: .... _a
fget.name: ... val_0
A: val_0: 0
fget id: ..... 139848106463104
fget addr: ... 0x7f30ecb54f80
fget.obj: .... _a
fget.name: ... val_1
A: val_1: 1
fget id: ..... 139848106491984
fget addr: ... 0x7f30ecb5c050
fget.obj: .... _a
fget.name: ... val_2
A: val_2: 2
fget id: ..... 139848106492128
fget addr: ... 0x7f30ecb5c0e0
fget.obj: .... _a
fget.name: ... val_3
A: val_3: 3
我创建了一个 repl.it
到 运行 在线代码:
https://repl.it/repls/ShimmeringQuickwittedHarddrive#main.py
我正在尝试创建一个装饰器以将对象属性中的属性“携带”到对象中。
简单示例(伪代码):
class carry:
''' Decorator. '''
class Prop:
''' Representation of '@property'. '''
# impl
# impl
class A:
def __init__(self):
self._val_0 = 'A: val_0'
self._val_1 = 'A: val_1'
@property
def val_0(self):
return self._val_0
@property
def val_1(self):
return self._val_1
# '_a' - the name of the attribute to get the @property'ies from.
# 'carry.Prop(...)' - a list of @property'ies to carry.
@carry(
'_a',
carry.Prop('val_0'),
carry.Prop('val_1')
)
class B:
def __init__(self):
self._a = A()
我期望得到的:
>>> b = B()
>>> print(b.val_0)
'A: val_0'
>>> print(b.val_1)
'A: val_1'
我得到了什么:
(警告:差异非常细微,容易被忽略;b.val_0
returns b.val_1
的值)
>>> b = B()
>>> print(b.val_0)
'A: val_1'
>>> print(b.val_1)
'A: val_1'
下面的代码是重现错误的“可执行”简约示例。 代码很多所以我把它分成三个部分:
- 装饰者:
装饰器的实现。
carry.__call__()
. 内某处发生错误
- 测试:
'print-debug' 测试@carry
装饰器。
重要的是要注意,当我打印b.val_0
时,我得到的不是val_0
,而是传递给@carry
ctor 的最后一个@property
的表示。
对我来说,写setattr(<parrent>, <name>, property(<fget, fset, fdel>))
似乎在 python 的某个地方被窃听了。我希望它创建的不是创建property()
,而是覆盖它们。
import pprint
from copy import copy
pp = pprint.PrettyPrinter(
indent = 2,
width = 80,
depth = None,
compact = False,
sort_dicts = False
)
##################################################
# Decorator:
##################################################
class carry:
class Prop:
''' Represents a 'property'. '''
def __init__(self, name:str, fget:bool=True, fset:bool=False, fdel:bool=False):
self._name = name
self._fget = fget
self._fset = fset
self._fdel = fdel
@property
def name(self) -> str:
return self._name
@property
def fget(self) -> bool:
return self._fget
@property
def fset(self) -> bool:
return self._fset
@property
def fdel(self) -> bool:
return self._fdel
def __init__(self, obj, *props):
self._obj = obj
self._props = props
def __call__(self, cls):
class Template(cls):
pass
Template.__name__ = cls.__name__
Template.__doc__ = cls.__doc__
for prop in self._props:
print('prop repr: ... {}'.format(prop.__dict__))
kwargs = {}
if prop.fget:
def fget(self):
print('fget id: ..... {}'.format(id(fget)))
print('fget addr: ... {}'.format(hex(id(fget))))
print('fget.obj: .... {}'.format(fget.obj))
print('fget.name: ... {}'.format(fget.name))
return getattr(getattr(self, fget.obj), fget.name)
fget.obj = copy(self._obj)
fget.name = copy(prop.name)
print('fget id: ..... {}'.format(id(fget)))
print('fget addr: ... {}'.format(hex(id(fget))))
print('fget.obj: .... {}'.format(fget.obj))
print('fget.name: ... {}'.format(fget.name))
kwargs['fget'] = fget
result = property(**kwargs)
print('kwargs: ...... {}'.format(kwargs))
print('prop: ........ {}'.format(result))
print('prop id: ..... {}'.format(id(result)))
print('prop addr: ... {}'.format(hex(id(result))))
setattr(Template, prop.name, result)
print()
# Return modified class:
return Template
##################################################
# Tests:
##################################################
class A:
def __init__(self):
self._val_0 = 'A: val_0: 0'
self._val_1 = 'A: val_1: 1'
self._val_2 = 'A: val_2: 2'
self._val_3 = 'A: val_3: 3'
@property
def val_0(self):
return self._val_0
@property
def val_1(self):
return self._val_1
@property
def val_2(self):
return self._val_2
@property
def val_3(self):
return self._val_3
print()
print('##################################################')
print()
props = [
carry.Prop('val_0'),
carry.Prop('val_1'),
carry.Prop('val_2'),
carry.Prop('val_3'),
]
@carry('_a', *props)
class B:
def __init__(self):
self._a = A()
print('##################################################')
print()
print(B)
print(B.__name__)
pp.pprint(dir(B))
print()
print('##################################################')
print()
b = B()
print(b.val_0)
print()
print(b.val_1)
print()
print(b.val_2)
print()
print(b.val_3)
print()
首先,让我重申一下我的意见:您似乎在实施“委托”模式。下面是一些代码,可以用更少的行数和更大的灵活性来实现: https://www.michaelcho.me/article/method-delegation-in-python
名称 'carry' 没有告诉我会发生什么。除非您有一些问题域,其中该名称已经是 well-established,否则我建议您考虑一个不同的名称。话虽如此...
我认为您的问题是您试图 使用 闭包而不 创建 闭包。用于迭代所有属性的循环不是正确的闭包,因此存在泄漏——所有引用最终都指向迭代变量的最后一个值。
我像这样修改了您的示例代码:
if prop.fget:
fget = self.make_fget(self._obj, prop.name)
#def fget(self):
# print('fget id: ..... {}'.format(id(fget)))
# print('fget addr: ... {}'.format(hex(id(fget))))
# print('fget.obj: .... {}'.format(fget.obj))
# print('fget.name: ... {}'.format(fget.name))
# return getattr(getattr(self, fget.obj), fget.name)
#fget.obj = copy(self._obj)
#fget.name = copy(prop.name)
并添加了这个方法:
def make_fget(self, attribute_name, property_name):
""" Construct and return an fget closure. """
def fget(self):
print('fget id: ..... {}'.format(id(fget)))
print('fget addr: ... {}'.format(hex(id(fget))))
print('fget.obj: .... {}'.format(fget.obj))
print('fget.name: ... {}'.format(fget.name))
return getattr(getattr(self, fget.obj), fget.name)
fget.obj = attribute_name
fget.name = property_name
return fget
结果更符合我的预期:
# ... many, many lines elided ...
fget id: ..... 139848106927168
fget addr: ... 0x7f30ecbc6440
fget.obj: .... _a
fget.name: ... val_0
A: val_0: 0
fget id: ..... 139848106463104
fget addr: ... 0x7f30ecb54f80
fget.obj: .... _a
fget.name: ... val_1
A: val_1: 1
fget id: ..... 139848106491984
fget addr: ... 0x7f30ecb5c050
fget.obj: .... _a
fget.name: ... val_2
A: val_2: 2
fget id: ..... 139848106492128
fget addr: ... 0x7f30ecb5c0e0
fget.obj: .... _a
fget.name: ... val_3
A: val_3: 3