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'}

我的一些疑惑:

  1. 操作属性的最佳位置是什么?例如,我 return 调用 self.created 时的时间戳。我根据另一个属性操作数据属性。 getter 这种东西的正确位置是什么?
  2. 我是否在 init 中正确地将私有变量分配给它们自己?
  3. 我需要 json 中的所有变量供以后处理,如何在 json 中输出它们而没有 它们有下划线?
  4. 如何使用数据classes 完成同样的事情?

关于您的问题 1. 是的,简单来说 python,在读取或写入属性时放置要执行的代码的最佳位置是 属性。或者,您可以使用所谓的 descriptor protocol(高级)创建 'property-like' 对象。

关于你的问题2。你的init是不对的,因为你使用的一些变量在你使用的时候没有定义。 createdenvironmentid 属性也没有正确写入,因为如果您使用 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.ibany 一起使用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)

最后,如果你需要它们,你可以添加一个漂亮的repreqhashdict 行为等​​,使用 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)

注:我是 pyfieldsautoclass 的作者 ;)