【问题标题】:Make a method that is both a class method and an instance method创建一个既是类方法又是实例方法的方法
【发布时间】:2018-07-26 07:13:47
【问题描述】:

有没有办法在 Python 中定义一个既是类方法又是实例方法的方法,这样 cls 和 self 都是接收者。特别是,我希望创建一个方法,(1)知道在类上调用它时调用了哪个类(Foo.method(value)),并且(2)知道在实例上调用它时调用了哪个实例(@987654324 @)。

例如,我会设想这样的工作:

class Foo:
    def __str__(self): return 'Foo()'

    @classinstancemethod
    def method(cls, self):
        print(cls, self)

class Bar(Foo):
    def __str__(self): return 'Bar()'

Foo().method()     # <class '__main__.Foo'> Foo()
Bar().method()     # <class '__main__.Bar'> Bar()
Foo.method(Foo())  # <class '__main__.Foo'> Foo()
Foo.method(Bar())  # <class '__main__.Foo'> Bar()
Bar.method(Foo())  # <class '__main__.Bar'> Foo()

请注意,我知道可以像Foo.foo(value) 一样调用未修饰的方法,但这不是我想要的,因为它没有得到cls 变量。如果没有cls 变量,该方法不知道它刚刚调用了哪个类。它可能被称为Bar.method(value),现在可以知道value 是否是Foo 的一个实例。未修饰的方法更像是静态方法和实例方法,而不是类方法和实例方法。

【问题讨论】:

    标签: python decorator class-method


    【解决方案1】:

    你不需要装饰器。这就是方法已经起作用的方式;实例作为第一个参数传递——隐式地从实例调用方法并从类显式调用——在实例可用的情况下,您可以通过在实例上调用 type 来检索类:

    class Foo(object):
        def __repr__(self): return 'Foo()'
    
        def method(self):
            print((type(self), self)) 
    
    class Bar(Foo):
        def __repr__(self): return 'Bar()'
    

    另一方面,在__str__(或__repr__)特殊方法中,您应该返回一个字符串,而不是打印。

    我使用了__repr__,因为在容器对象(此处为元组)中不调用__str__ 来打印实例。

    更新:

    考虑到上述方法中类/实例打印的问题,您可以改用描述符来正确管理方法中正确的类和实例选择:

    class classinstancemethod():
    
      def __get__(self, obj, cls):
         def method(inst=None):
            print(cls, inst if inst else obj)
         return method
    
    class Foo(object):
        method = classinstancemethod()
    
        def __str__(self): return 'Foo()'
    
    
    class Bar(Foo):
        def __str__(self): return 'Bar()'
    

    【讨论】:

    • 不,那是错误的。 Foo.method(Bar()) 应该输出 (&lt;class '__main__.Foo'&gt;, 'Bar()'),但你的输出 (&lt;class '__main__.Bar'&gt;, 'Bar()')。
    • @Aran-Fey 感谢您的关注。他们可以使用 Python3 中隐藏的 __class__ 变量而不是 type(self)。
    • 这也不能按预期工作。如果您使用__class__,那么它将始终返回Foo 作为类。
    • 我在最后一段中预料到了这个解决方案。这不是我要找的,因为当它被称为Bar.method(Foo()) 时,method 不知道它的接收者是Bar 类。该信息已丢失。
    • @Aran-Fey 已经在研究描述符答案,试图解决您的评论,感谢您的关注。
    【解决方案2】:

    这可以通过将classinstancemethod 实现为自定义descriptor 来解决。

    简而言之,描述符必须定义一个__get__ 方法,该方法将在作为属性访问时调用(如Foo.method 或Foo().method)。此方法将作为参数传递实例和类并返回 bound 方法(即,它返回一个包含 cls 和 self 参数的方法)。当调用此绑定方法时,它会将烘焙的cls 和self 参数转发给实际方法。

    class classinstancemethod:
        def __init__(self, method, instance=None, owner=None):
            self.method = method
            self.instance = instance
            self.owner = owner
    
        def __get__(self, instance, owner=None):
            return type(self)(self.method, instance, owner)
    
        def __call__(self, *args, **kwargs):
            instance = self.instance
            if instance is None:
                if not args:
                    raise TypeError('missing required parameter "self"')
                instance, args = args[0], args[1:]
    
            cls = self.owner
            return self.method(cls, instance, *args, **kwargs)
    

    结果:

    class Foo:
        def __repr__(self): return 'Foo()'
    
        @classinstancemethod
        def method(cls, self):
            print((cls, self))
    
    
    class Bar(Foo):
        def __repr__(self): return 'Bar()'
    
    
    Foo().method()     # (<class '__main__.Foo'>, 'Foo()')
    Bar().method()     # (<class '__main__.Bar'>, 'Bar()')
    Foo.method(Foo())  # (<class '__main__.Foo'>, 'Foo()')
    Foo.method(Bar())  # (<class '__main__.Foo'>, 'Bar()')
    Bar.method(Foo())  # (<class '__main__.Bar'>, 'Foo()')
    

    【讨论】:

    • 完美。我怀疑__get__ 是某种解决方案。
    猜你喜欢
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-01
    • 2016-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多