python 使用另一个类的方法而不使用装饰器和继承

7z5jn7bk  于 2023-03-21  发布在  Python
关注(0)|答案(2)|浏览(123)

我有一个类,它有自己的几个方法,为了简单起见,这里没有显示:

class Foo:
  def __init__(self, arg: str):
    self.bar = arg

比如说,除了它自己的方法,我希望Foo的示例使用str的方法来代替bar属性(确保是字符串)。这可以通过__getattr__ dunder方法实现:

class Foo:
  def __getattr__(self, item):
    return getattr(self.foo, item)

调用的结果应该是.bar的新值。然而,由于Python字符串是不可变的,因此方法调用(例如str.strip())产生的字符串需要重新赋值,这看起来不太好。此外,如果调用没有返回字符串,则需要if

result = instance_of_Foo.strip()
if isinstance(result, str):
  instance_of_Foo.bar = result
else:
  ...

我和一个装潢师解决了这个问题:

def decorator(function, *, self):
  def wrapper(*args, **kwargs):
    result = function(*args, **kwargs)
        
    if isinstance(result, str):
      self.bar = result
    else:
      return result

  return wrapper

class Foo:
  def __init__(self, arg: str):
    self.bar = arg

  def __getattr__(self, item):
    method = decorator(getattr(self.bar, item), self = self)
    return method

foo = Foo(' foo ')
print(foo.bar) # ' foo '

foo.strip()
print(foo.bar) # 'foo'

...但肯定有一种更“Python”的方式,最好使用dunder方法而不是装饰器来拦截调用,不是吗?请注意,我的类不能替换字符串(违反Liskov原则),所以继承是不可能的。

s8vozzvw

s8vozzvw1#

你不仅仅是把对字符串方法的调用委托给一个底层的字符串属性。你是在尝试使用字符串方法调用的结果来修改属性。这本身应该由你的 Package 类上的一个单独的方法来处理。

from operator import methodcaller

class Foo:
    def __init__(self, some_text):
        self.bar = some_text

    def update_with(self, method, *args, **kwargs):
        self.bar = methodcaller(method, *args, **kwargs)(self.bar)

f = Foo("--hello, world--")
# not f.strip('-'), but
f.update_with('strip', '-')

传递一个字符串,而不是一个显式的未绑定方法,在处理str的可能子类时效果更好。

class Foo:
    ...

    def update_with_unbound_method(self, f, *args, **kwargs):
        self.bar = f(self.bar, *args, **kwargs)

f = Foo(MyStrSubclass("--hello, world--"))

# The following might work differently, and require you to know
# what subclass was used to create f to get the correct call.
f.update_with_unbound_method(str.strip, '-')
f.update_with_unbound_method(MyStrSubclass.strip, '-')
iqih9akk

iqih9akk2#

回答我自己的问题:
您(或我)可以使用 Package 器缓存__getattr__ dunder方法,但是chepner's answer应该是首选,因为它可以处理任意给定的函数,而且设计得更好。

from functools import cache

class Foo:
  def __init__(self, arg: str):
    self.bar = arg

  @cache
  def __getattr__(self, item):
    method = getattr(self.bar, item)
        
    def wrapper(*args, **kwargs):
      result = method(*args, **kwargs)
            
      if isinstance(result, str):
        self.bar = result
      else:
        return result
    
    print(f'{id(wrapper)}')
    
    return wrapper

试试看:

foo = Foo(' foo ')
print(foo.bar)  # ' foo '

foo.strip()     # id(wrapper) = 2345672443040
print(foo.bar)  # 'foo'

foo.center(7)   # id(wrapper) = 2345681396384
print(foo.bar)  # '  foo  '

foo.center(9)   # Nothing, cached.
print(foo.bar)  # '   foo   '

foo.strip(' ')  # With an argument, also nothing.
print(foo.bar)  # 'foo'

相关问题