【问题标题】:Can I pass the name of an attribute when it itself is being initialised?我可以在初始化属性时传递属性的名称吗?
【发布时间】:2020-07-28 12:08:21
【问题描述】:

我有一个类,我在其中将几个类属性定义为对象。属性本身的名称是需要传递给对象的参数之一。目前它们是手动编写的,如下例所示:

class Beatles:
    john = Guitar(name='john')
    paul = Bass(name='paul')
    george = Guitar(name='george')
    ringo = Drums(name='ringo')

class Musician:
    def __init__(self)

class Guitar(Musician):
    def __init__(self, name)

class Bass(Musician):
    def __init__(self, name)

class Drums(Musician):
    def __init__(self, name) 

我发现了一种解决方法,它使用元类使用 Foo 对象的命名空间字典来构建对象:

class Meta(type):
    def __new__(cls, name, bases, dict_):
        for k, v in dict_.items():
            if isinstance(v, Musician):
                dict_[k] = type(v)(k)
        return super().__new__(cls, name, bases, dict_)

仅当“名称”参数是唯一不会出现这种情况的参数时才有效。有没有更好的办法?

【问题讨论】:

  • 虽然问题本身具有技术价值,但这种设计存在固有问题。例如,当一个乐队有 2 个同名的音乐家时会发生什么?当然,您现在可以添加另一个检查,确保名称是唯一的并使用中间名进行更改。但是... a) 另一个很大的复杂性和 b) “Johnny B”不是一个有效的属性名称。在更大的意义上,是的,这是 Python 的一个问题。例如说一个表格。每个字段可能有一个名称,并且可能希望将它们分配给表单,但是 field.name 和 form.. 之间没有链接
  • 我想我在 Ramalho 的 Fluent Python 中看到了类似的东西,而且他没有明显的简单解决方案来桥接赋值 = 运算符的左右两边,所以重复了这个名字。后来他确实提出了一些技巧,但是,IIRC 既不优雅,也不简单。除非你有压倒一切的理由去做这样的事情(比如成为框架内易用性的核心部分,将疣安全地隐藏在里面)然后,在你的位置上,我会去有了这个限制并解决它们。我怀疑这个问题也非常特定于 Python。

标签: python python-3.x oop metaclass


【解决方案1】:

在 Python 3.6 中,有一个新功能可以解决这种模式,并减少对元类的需求,因为这些几乎总是会导致混乱。

所需要的只是要成为属性的对象的类具有__set_name__ 方法(在您的情况下,这些都是Musician 的子类,因此,您所要做的就是添加一个def __set_name__(self, owner, name): ... 方法)。

所以,在您的情况下,您只需要:


class Musician:
    def __init__(self, ...):
        # no need to get a 'name' parameter here
        ...
    # This is called automatically by Python:
    def __set_name__(self, owner, name):
        # 'owner' is the class object where the attributes are defined in. 
        # in this case, "Beatles". It is usually not needed, but available.

        self.name = name

class Guitar(Musician):
    pass

class Bass(Musician):
    pass

class Drums(Musician):
    pass

class Beatles(metaclass=Meta):
    john = Guitar()
    paul = Bass()
    george = Guitar()
    ringo = Drums()

现在,如果出于某种原因您想通过使用元类来实现这一点(假设您必须使用 Python 3.5,或者无法更改 Musician 类上的代码) - 您可以使用 functools.partial存储其他属性并在上面的相同元类代码中传递缺少的name 属性:

from functools import partial 

class Beatles:
    john = partial(Guitar, other_attribute='')
    paul = partial(Bass, wearing_shoes=False)
    george = partial(Guitar)
    ringo = partial()

(请记住,如果需要,您可以缩短 partial 以提高可读性,就像 from functools import partial as P 一样简单)

【讨论】:

    【解决方案2】:

    对象本身还不存在,但构造函数最终是一个函数,因此您将所需的内容作为函数参数传递。

    在这种情况下,示例可能无法传达您想问的问题。你没有名字是因为你是鼓手,你有名字是因为你是一个人。作为层次结构的顶层,Musician 将具有该属性,而不是 Drummer,它应该是 Musician 构造函数中的参数:

    class Musician:
        def __init__(self, name):
            self.name = name
    
    class Guitar(Musician):
        def __init__(self, name):
            super().__init__(name)
    
    class Bass(Musician):
        def __init__(self, name):
            super().__init__(name)
    
    class Drums(Musician):
        def __init__(self, name):
            super().__init__(name)
    
    Beatles = {
        Guitar(name='john'),
        Bass(name='paul'),
        Guitar(name='george'),
        Drums(name='ringo'),
    }        
    
    for m in Beatles:
        print (m.name)
    

    【讨论】:

    • 我知道这行得通,但它并不能真正回答我的问题。也许这个例子不清楚,但我认为我的问题的意图是。该示例仅使用占位符名称。我需要一个具有自身属性的类,对象,而不是对象字典。
    猜你喜欢
    • 1970-01-01
    • 2021-11-30
    • 1970-01-01
    • 1970-01-01
    • 2011-05-30
    • 1970-01-01
    • 1970-01-01
    • 2017-12-02
    • 2010-11-24
    相关资源
    最近更新 更多