【问题标题】:Python class constructor (static)Python 类构造函数(静态)
【发布时间】:2018-01-12 07:43:13
【问题描述】:

Python 是否有用于类构造函数的机制,即在第一次引用类时调用的函数(而不是在创建该对象的实例时)?我知道这存在于其他一些语言中,但我在 Python 中没有遇到过。

基本上,我想在该函数中初始化一些静态属性。我在下面举了一个我期望的例子。当然,该示例返回 None,但我希望它返回 'foo'。

class T:
    arg = None
    def __class_constructor__():
        T.arg = 'foo'

print(T.arg)  # returns None

为避免混淆:我很清楚对象构造函数,但这不是我想要的,因为它仅在创建第一个对象时调用,而不是在创建之前:

class T:
    arg = None
    def __init__(self):
        type(self).arg = 'foo'

print(T.arg)  # returns None
obj = T()
print(T.arg)  # returns 'foo'

【问题讨论】:

  • 为什么不class T: arg = 'foo'...? class 关键字“类构造函数”。
  • 除非该值取决于您定义类时不可用的信息,否则您没有理由不能这样做。请提供一些细节?
  • @EliasMi 您可以在__class_constructor__ 中编写的任何代码,您也可以在类本身中编写。它的功能丝毫不逊色。
  • 好的,所以:不,没有“在类第一次引用时启动的类构造函数。”该类的第一个引用是class T。唯一的下一个机会是实例创建时间。因此,您必须更详细地了解您的实际问题,以便我们推荐一种 Pythonic 方法来解决它。
  • 实际上,你是对的——这行得通:class T: arg = 0 for i in range(4): arg += i print(T.arg) # returns 6。我只是从没想过在类头中添加实际代码

标签: python class oop constructor


【解决方案1】:

你可以使用类装饰器:

def add_arg(cls):
    if not hasattr(cls, "arg"):
        cls.arg = 'foo'
    return cls

@add_arg
class T(object):
    pass

或自定义元类:

class WithArg(type):
    def __new__(meta, name, bases, attrs):
        cls = type.__new__(meta, name, bases, attrs)
        if not hasattr(cls, "arg"):
            cls.arg = "foo"
        return cls

# python 2
class T(object):
    __metaclass__ = WithArg

# python 3
class T(metaclass=WithArg):
    pass

但正如其他人已经提到的那样,这只会给您带来更多的好处,而不仅仅是在 class 语句中设置 class 属性。

注意:如果你想要类本身的计算属性,你必须将其设置为自定义元类的属性

 class WithProp(type):
     @property
     def arg(cls):
         return "foo"

 class T(object):
     __metaclass__ = WithProp

 T.arg
 => 'foo'

但是arg 只能在类对象本身上可用,而不是在它的实例上:

T().arg
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'T' object has no attribute 'arg'

或编写您自己的自定义描述符:

class ArgDescriptor(object):
    def __get__(self, obj, cls=None):
        return 42

class T(object):
    arg = ArgDescriptor()

T.arg
=> 42
T().arg
=> 42

【讨论】:

  • 这些都没有激活“当第一次引用类时”......
  • 是的,我刚刚编辑了关于这个的答案。问题是“何时首次引用该类”并没有多大意义。
  • 我忘了提到一个重要的细节……属性应该是类本身的一个实例。但是现在我已经解决了......我只是先定义类,然后设置属性(因为否则,类不知道自己)
  • 所以基本上,我想要(现在拥有)是:class Config: //// def __init__(self, foo): //// self._foo = foo,然后是:Config.DEFAULT = Config('default params')
  • “属性应该是类本身的一个实例” => 我发布的所有解决方案都允许这样做——但对于如此简单的事情来说确实是矫枉过正。 XY问题经常......
【解决方案2】:

你只需要在类中声明类变量时初始化它

class T:
    arg = 'foo' #this initialises the class instance

    def __init__(self):
        self.arg = 'bar' #this initialises the object instance

print(T.arg)  # class instance returns 'foo'
obj = T()
print(T.arg)  # class instance still returns 'foo'
print(obj.arg)  # object instance returns 'bar'

【讨论】:

    【解决方案3】:

    我创建了一个 static_init 装饰器,它调用 static_init 类方法(如果存在)。这个 static_init 类方法将在评估类装饰器时运行,即 when the class is defined - 所以在第一次引用该类时并不完全 - 但它类似于 Java 等其他语言中的静态初始化。

    这是装饰器和如何使用它来初始化枚举类上的类变量的示例:

    # pylint: disable=missing-docstring,no-member
    
    import enum
    
    def static_init(cls):
        if getattr(cls, "static_init", None):
            cls.static_init()
        return cls
    
    @static_init
    class SomeEnum(enum.Enum):
        VAL_A = enum.auto()
        VAL_B = enum.auto()
        VAL_C = enum.auto()
        VAL_D = enum.auto()
    
        @classmethod
        def static_init(cls):
            text_dict = {}
            setattr(cls, 'text_dict', text_dict)
            for value in cls:
                text_dict[value.name.lower().replace("_", " ").title()] = value
    
    def test_static_init():
        assert SomeEnum.text_dict["Val A"] == SomeEnum.VAL_A
        assert SomeEnum.text_dict["Val B"] == SomeEnum.VAL_B
        assert SomeEnum.text_dict["Val C"] == SomeEnum.VAL_C
        assert SomeEnum.text_dict["Val D"] == SomeEnum.VAL_D
    

    【讨论】:

      猜你喜欢
      • 2011-10-07
      • 1970-01-01
      • 2010-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-01
      • 1970-01-01
      相关资源
      最近更新 更多