Python getter、setter 和操作字段
Python getters, setters and manipulating fields
我正在构建一个简单的 class,其中包含一些仅属于实例的变量,例如带有纪元时间戳的“已创建”字段,以及在创建实例时设置的其他一些变量。作为练习,希望以 most pythonic 方式尽可能做到这一点。此外,我想要一个打印属性的函数,如json,以便稍后使用。到目前为止,我有这个:
import os
import uuid
import time
class Example:
def __init__(self, data=None):
self._data = data
self._id = self.id
self._environment = self.environment
self._created = self.created
@property
def data(self):
if self.environment == 'prd':
self._data
else:
return None
@data.setter
def data(self, value):
self._data = value
@property
def id(self):
return str(uuid.uuid4())
@id.setter
def id(self, value):
self._id = value
@property
def request(self):
return self._request
@request.setter
def request(self, value):
self._request = value
@property
def environment(self):
return os.getenv('environment', 'dev')
@environment.setter
def environment(self, value):
self._environment = value
@property
def created(self):
return str(int(time.time()))
@created.setter
def created(self, value):
self._created = value
def to_json(self):
return self.__dict__
输出:
from example import Example
e = Example()
print(e.to_json())
# {'_data': None, '_id': '5f946b10-0e89-4d65-9f76-b9f15a81197d', '_environment': 'dev', '_created': '1592835056'}
我的一些疑惑:
- 操作属性的最佳位置是什么?例如,我 return 调用 self.created 时的时间戳。我根据另一个属性操作数据属性。 getter 这种东西的正确位置是什么?
- 我是否在 init 中正确地将私有变量分配给它们自己?
- 我需要 json 中的所有变量供以后处理,如何在 json 中输出它们而没有 它们有下划线?
- 如何使用数据classes 完成同样的事情?
关于您的问题 1. 是的,简单来说 python,在读取或写入属性时放置要执行的代码的最佳位置是 属性。或者,您可以使用所谓的 descriptor protocol(高级)创建 'property-like' 对象。
关于你的问题2。你的init是不对的,因为你使用的一些变量在你使用的时候没有定义。 created
、environment
和 id
属性也没有正确写入,因为如果您使用 setter,将写入一个新的私有属性(例如 _id
)但你不在 getter 中使用它。使用 属性 的“好”方法是这样的:
class Example:
def __init__(self):
self._a = None
@property
def a(self):
if self._a is None:
return "my_default_val"
else:
return self._a
@a.setter
def a(self, value):
self._a = value
e = Example()
print(e.a)
e.a = 2
print(e.a)
产量
my_default_val
2
对于你的问题3,最适合你的是学习如何使用list comprehensions,dict comprehensions等,并用它来动态过滤你的vars(o)
(或o.__dict__
,即一样)
现在提到你的问题4。根据我个人的经验,我建议不要使用dataclasses,它只是attrs
的一个子集。您可以使用 attrs
, or what seems more adapted to your use case where objects are mutable, pyfields
。事实上 attrs
还没有在属性更改时调用 validators/handlers,并且强加了相当严格的开发理念(即你不能将 attr.ib
与 any 一起使用class 因为 class __init__
和 __setattr__
被 attrs
修改了。
这里是你如何使用 pyfields
:
import os
import uuid
import time
from pyfields import field, get_field_values, make_init
class Example:
# the various fields
_data = field(default=None)
environment = field(default_factory=lambda o: os.getenv('environment', 'dev'))
id = field(default_factory=lambda o: str(uuid.uuid4()))
created = field(default_factory=lambda o: str(int(time.time())))
request = field()
# create a default constructor if you do not need a custom one
__init__ = make_init()
# 'data' is a dynamic view that depends on 'environment': still need a property
@property
def data(self):
if self.environment == 'prd':
return self._data
else:
return None
@data.setter
def data(self, value):
self._data = value
def to_json(self):
dct = get_field_values(self, public_only=True)
dct['data'] = self.data
return dct
e = Example(request='hello')
print(e.to_json())
产量
{'environment': 'dev', 'id': '8f3c2f8f-ce36-4e69-bfb9-b044db83be84', 'created': '1592897458', 'request': 'hello', 'data': None}
请注意 get_field_values
不是 return data
属性 的内容。参见 this feature request。
如果需要,您可以进一步简化此示例,使用 autofields
删除字段定义中的一些样板文件并在存在 none 时生成构造函数:
from pyfields import get_field_values, autofields
@autofields
class Example:
# the various fields
_data = None
environment: str = os.getenv('environment', 'dev')
id: str = str(uuid.uuid4())
created: int = str(int(time.time()))
request: str
(... all the same than previously)
最后,如果你需要它们,你可以添加一个漂亮的repr
、eq
、hash
、dict
行为等,使用 autoclass
。它会自动检测到class上有fields
,你可以使用它的autofields
参数自动创建它们:
from autoclass import autoclass
@autoclass(autofields=True)
class Example:
# the various fields
_data = None
environment: str = os.getenv('environment', 'dev')
id: str = str(uuid.uuid4())
created: int = str(int(time.time()))
request: str
# data is a dynamic view that depends on the environment: need a property
@property
def data(self):
if self.environment == 'prd':
return self._data
else:
return None
@data.setter
def data(self, value):
self._data = value
def to_json(self):
dct = dict(self) # <--- note this: autoclass dict behaviour
dct['data'] = self.data
return dct
e = Example(request='hello')
print(repr(e))
产量:
Example(environment='dev', id='ee56cb2f-8a4a-48ac-9789-956f1eaea132', created='1592901475', request='hello', 'data': None)
注:我是 pyfields
和 autoclass
的作者 ;)
我正在构建一个简单的 class,其中包含一些仅属于实例的变量,例如带有纪元时间戳的“已创建”字段,以及在创建实例时设置的其他一些变量。作为练习,希望以 most pythonic 方式尽可能做到这一点。此外,我想要一个打印属性的函数,如json,以便稍后使用。到目前为止,我有这个:
import os
import uuid
import time
class Example:
def __init__(self, data=None):
self._data = data
self._id = self.id
self._environment = self.environment
self._created = self.created
@property
def data(self):
if self.environment == 'prd':
self._data
else:
return None
@data.setter
def data(self, value):
self._data = value
@property
def id(self):
return str(uuid.uuid4())
@id.setter
def id(self, value):
self._id = value
@property
def request(self):
return self._request
@request.setter
def request(self, value):
self._request = value
@property
def environment(self):
return os.getenv('environment', 'dev')
@environment.setter
def environment(self, value):
self._environment = value
@property
def created(self):
return str(int(time.time()))
@created.setter
def created(self, value):
self._created = value
def to_json(self):
return self.__dict__
输出:
from example import Example
e = Example()
print(e.to_json())
# {'_data': None, '_id': '5f946b10-0e89-4d65-9f76-b9f15a81197d', '_environment': 'dev', '_created': '1592835056'}
我的一些疑惑:
- 操作属性的最佳位置是什么?例如,我 return 调用 self.created 时的时间戳。我根据另一个属性操作数据属性。 getter 这种东西的正确位置是什么?
- 我是否在 init 中正确地将私有变量分配给它们自己?
- 我需要 json 中的所有变量供以后处理,如何在 json 中输出它们而没有 它们有下划线?
- 如何使用数据classes 完成同样的事情?
关于您的问题 1. 是的,简单来说 python,在读取或写入属性时放置要执行的代码的最佳位置是 属性。或者,您可以使用所谓的 descriptor protocol(高级)创建 'property-like' 对象。
关于你的问题2。你的init是不对的,因为你使用的一些变量在你使用的时候没有定义。 created
、environment
和 id
属性也没有正确写入,因为如果您使用 setter,将写入一个新的私有属性(例如 _id
)但你不在 getter 中使用它。使用 属性 的“好”方法是这样的:
class Example:
def __init__(self):
self._a = None
@property
def a(self):
if self._a is None:
return "my_default_val"
else:
return self._a
@a.setter
def a(self, value):
self._a = value
e = Example()
print(e.a)
e.a = 2
print(e.a)
产量
my_default_val
2
对于你的问题3,最适合你的是学习如何使用list comprehensions,dict comprehensions等,并用它来动态过滤你的vars(o)
(或o.__dict__
,即一样)
现在提到你的问题4。根据我个人的经验,我建议不要使用dataclasses,它只是attrs
的一个子集。您可以使用 attrs
, or what seems more adapted to your use case where objects are mutable, pyfields
。事实上 attrs
还没有在属性更改时调用 validators/handlers,并且强加了相当严格的开发理念(即你不能将 attr.ib
与 any 一起使用class 因为 class __init__
和 __setattr__
被 attrs
修改了。
这里是你如何使用 pyfields
:
import os
import uuid
import time
from pyfields import field, get_field_values, make_init
class Example:
# the various fields
_data = field(default=None)
environment = field(default_factory=lambda o: os.getenv('environment', 'dev'))
id = field(default_factory=lambda o: str(uuid.uuid4()))
created = field(default_factory=lambda o: str(int(time.time())))
request = field()
# create a default constructor if you do not need a custom one
__init__ = make_init()
# 'data' is a dynamic view that depends on 'environment': still need a property
@property
def data(self):
if self.environment == 'prd':
return self._data
else:
return None
@data.setter
def data(self, value):
self._data = value
def to_json(self):
dct = get_field_values(self, public_only=True)
dct['data'] = self.data
return dct
e = Example(request='hello')
print(e.to_json())
产量
{'environment': 'dev', 'id': '8f3c2f8f-ce36-4e69-bfb9-b044db83be84', 'created': '1592897458', 'request': 'hello', 'data': None}
请注意 get_field_values
不是 return data
属性 的内容。参见 this feature request。
如果需要,您可以进一步简化此示例,使用 autofields
删除字段定义中的一些样板文件并在存在 none 时生成构造函数:
from pyfields import get_field_values, autofields
@autofields
class Example:
# the various fields
_data = None
environment: str = os.getenv('environment', 'dev')
id: str = str(uuid.uuid4())
created: int = str(int(time.time()))
request: str
(... all the same than previously)
最后,如果你需要它们,你可以添加一个漂亮的repr
、eq
、hash
、dict
行为等,使用 autoclass
。它会自动检测到class上有fields
,你可以使用它的autofields
参数自动创建它们:
from autoclass import autoclass
@autoclass(autofields=True)
class Example:
# the various fields
_data = None
environment: str = os.getenv('environment', 'dev')
id: str = str(uuid.uuid4())
created: int = str(int(time.time()))
request: str
# data is a dynamic view that depends on the environment: need a property
@property
def data(self):
if self.environment == 'prd':
return self._data
else:
return None
@data.setter
def data(self, value):
self._data = value
def to_json(self):
dct = dict(self) # <--- note this: autoclass dict behaviour
dct['data'] = self.data
return dct
e = Example(request='hello')
print(repr(e))
产量:
Example(environment='dev', id='ee56cb2f-8a4a-48ac-9789-956f1eaea132', created='1592901475', request='hello', 'data': None)
注:我是 pyfields
和 autoclass
的作者 ;)