pytorch 如何用Python生成方法来在类示例化时设置和获取属性?

xpcnnkqh  于 2023-03-18  发布在  Python
关注(0)|答案(2)|浏览(138)

我有一个包含Tensor的类,Tensor有一系列不可预测的性质。
我想让它根据示例化时给出的列表生成一个带有getter和setter的属性列表。
你知道怎么做吗?
谢谢

class Items:
     
    def __init___(
        self,
        names: List[str]
    ):
     #: Property names to be generated 
     self.names: List[str] = names
     #: Tensors in use
     self.tensors: Dict[str,Tensor] = {}
     
     def get_tensor(self, name:str) -> Tensor | None:
         if name in self.tensors.keys():
             return self.tensors[name]
         return None
         
     def set_tensor(self, name:str, tensor: Tensor):
         self.tensors[name] = tensor

     # The exact same setter and getter functions have to be generated for several properties:
     # entries, classes, scores, target classes, target scores, embeddings, heatmaps, losses...
     # The list is provided by self.names at instantiation 
     
     @property
     def entries(self) -> Tensor | None:
         return self.get_tensor("entries")
         
      @entries.setter
      def entries(self, tensor: Tensor):
           self.set_tensor("entries", tensor)
wfypjpf4

wfypjpf41#

我想到了两种方法。这两种方法都很混乱,而且不总是能很好地处理linters、mypy等。基本上,复制和粘贴或者仅仅使用你已经拥有的get和set方法可能是更干净、更清晰、更容易的解决方案。但是如果你要处理很多属性或者不同的类,那么这些方法应该可以工作。
选项1:使用getattrsetattr代替属性

from typing import Dict, Any

class Tensor:  # just a test class for debugging
    def __init__(self, name: str) -> None:
        self.name = name

    def __repr__(self) -> str:
        return self.name
    
    
class Items:
    def __init__(self, *names: str) -> None:
        self.names = set(names)
        self.tensors: Dict[str, Tensor] = {}

    def __getattr__(self, name: str) -> Any:
        if (names := self.__dict__.get('names')) and name in names:
            return self.tensors.get(name)
        try:
            return self.__dict__[name]
        except KeyError:
            raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

    def __setattr__(self, name: str, value: Tensor) -> None:
        if (names := self.__dict__.get('names')) and name in names:
            self.tensors[name] = value
        super().__setattr__(name, value)

items = Items('entries', 'classes', 'scores', 'target_classes',
              'target_scores', 'embeddings', 'heatmaps', 'losses')
items.entries = Tensor('X')
print(items.tensors)
print(items.entries)
print(items.classes)

选项2:在创建类时,使用元类创建所有你想要的属性,然后你可以为每一组属性名创建一个不同的类(或者继承一个新类)。

from __future__ import annotations

from typing import List, Dict, Any, Tuple, Callable, Optional, Type

class Tensor:  # just a test class for debugging
    def __init__(self, name: str) -> None:
        self.name = name

    def __repr__(self) -> str:
        return self.name

class ItemsMetaClass(type):
    def __new__(mcs: type, name: str, bases: Tuple[type, ...], 
                attributes: Dict[str, Any], tensor_names: List[str]) -> Type[ItemsMetaClass]:
        for tensor_name in tensor_names:
            attributes[tensor_name] = property(
                create_tensor_fget(tensor_name),
                create_tensor_fset(tensor_name))
        return super().__new__(mcs, name, bases, attributes)

def create_tensor_fget(tensor_name: str) -> Callable[[Items], Optional[Tensor]]:
    def fget(self: Items) -> Optional[Tensor]:
        if tensor_name in self.tensors:
            return self.tensors[tensor_name]
        return None
    return fget

def create_tensor_fset(tensor_name: str) -> Callable[[Items, Tensor], None]:
    def fset(self: Items, tensor: Tensor) -> None:
        self.tensors[tensor_name] = tensor
    return fset

class Items(metaclass=ItemsMetaClass, tensor_names=[
            'entries', 'classes', 'scores', 'target_classes',
            'target_scores', 'embeddings', 'heatmaps', 'losses']):
    def __init__(self) -> None:
        self.tensors: Dict[str, Tensor] = {}

items = Items()
items.entries = Tensor('X')
print(items.tensors)
print(items.entries)
print(items.classes)

所以我不确定我是否真的会推荐这两种,但它们都是选择。

uurv41yg

uurv41yg2#

有一个比上面详细的答案更简单的方法来做到这一点。

class Items:
    def __init__(self, names):
        self.names = names
        self.tensors = {}

        old_type = type(self)
        new_type = type(old_type.__name__, old_type.__bases__, dict(old_type.__dict__))

        for name in names:
            getter = lambda self, name=name: self.get_tensor(name)
            setter = lambda self, tensor, name=name: self.set_tensor(name, tensor)
            setattr(new_type, name, property(getter,setter))

        self.__class__ = new_type

    def get_tensor(self, name):
        return self.tensors.get(name)

    def set_tensor(self, name, tensor):
        self.tensors[name] = tensor

关于这段代码,只有几点需要注意。
首先,如果Items在将来被重构为通过元类接受关键字参数,那么您需要更改第7行,使用copy.deepcopyold_type创建new_type
另一个捕获是name=name部分,如果name没有以这种方式绑定到<lambda>作用域,则由于绑定到Items.__init__的作用域,它将引用names的最后一个元素。
最后,在get_tensor中有一些冗余代码,这只是dict.get的重新实现,我用后者替换了这些代码。

它是如何工作的?

当Python在获取或设置属性的过程中查找一个属性时,它会检查type(object)对应的属性是否实现了描述符协议。property实现了descriptor protocol,因此当Python查找属性时,我们可以使用getter和setter。
那么type是如何工作的呢?在单参数type(object)的情况下,它所做的只是检查对象的__class__属性。__class__本身只是对对象类的引用。因此,如果我们将__class__属性更改为自己创建的类,我们就可以创建仅特定于示例的属性。
最后,type的三参数版本可以用来创建新的类,传入名称、基和其他参数。如果您将来将此代码更改为使用元类,请将此行替换为new_type = copy.deepcopy(old_type)
从这里开始,代码就变得相对简单了,我们使用type为示例创建一个新类,迭代名称并创建属性,最后将新类赋给__class__属性。

相关问题