python - python中可能存在的错误。创建装饰器自动生成属性
问题描述
我创建了一个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
返回 的值b.val_1
)
>>> b = B()
>>> print(b.val_0)
'A: val_1'
>>> print(b.val_1)
'A: val_1'
下面的代码是重现错误的“可执行”简约示例。这是很多代码,所以我将其分为三个部分:
- 装饰器:装饰器
的实现。
内部某处发生错误carry.__call__()
。 - 测试:装饰器
的“打印调试”测试。 重要的是要注意,当我打印时,我得到的是传递给ctor的 last的表示,而不是得到。 对我来说,写作似乎在 python 中的某个地方出现了问题。而不是创建一个我想要它创建的,它会覆盖它们。@carry
b.val_0
val_0
@property
@carry
setattr(<parrent>, <name>, property(<fget, fset, fdel>))
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
“携带”这个名字并没有告诉我会发生什么。除非您有一些问题域,该名称已经很成熟,否则我会敦促您考虑使用不同的名称。照这样说 ...
我相信你的问题是你试图使用一个闭包而不创建一个闭包。用于迭代所有属性的循环不是正确的闭包,因此存在泄漏——所有引用最终都指向迭代变量的最后一个值。
我修改了您的示例代码,如下所示:
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
推荐阅读
- angular - Angular 4项目中的引导类
- go - 我如何知道具有类型接口的参数是否实际上是结构?
- c# - 在通用存储库模式中急切加载多级导航属性的问题
- swift - 在swift中使用switch语句的布尔函数
- php - PHP在新变量中的日期中添加天数会改变另一个
- microsoft-dynamics - Dynamics Navision 2018 - 使用 Web 服务插入销售订单折扣
- c# - Unity 刚体组合对象的不同行为
- ios - Alamofire 返回 nil 作为对 google snap to road api 调用的响应并显示 Alamofire.AFError.invalidURL
- android - ViewModelProviders 在 1.1.0 中已弃用
- asp.net - 如何在控制器 mvc 中声明公共 var 并返回