Python classmethod 的一个示例用途是什么?

84

我已经阅读了Python中的类方法是什么?,但是这篇文章中的示例比较复杂。我正在寻找一个明确、简单、简洁的Python类方法实际应用案例。

你能给出一个小的、具体的使用场景,说明在这种情况下Python类方法是正确的工具吗?


可能是Python中的类方法有什么用?的重复问题。 - Håvard
1
我已经编辑了问题,以解释两个问题之间的区别。我正在寻找一个类似于Hello World的裸骨示例用例。 - coffee-grinder
7个回答

62

初始化的辅助方法:

class MyStream(object):

    @classmethod
    def from_file(cls, filepath, ignore_comments=False):    
        with open(filepath, 'r') as fileobj:
            for obj in cls(fileobj, ignore_comments):
                yield obj

    @classmethod
    def from_socket(cls, socket, ignore_comments=False):
        raise NotImplemented # Placeholder until implemented

    def __init__(self, iterable, ignore_comments=False):
       ...

8
жҸҗдҫӣеӨҮйҖүжһ„йҖ еҮҪж•°зЎ®е®һжҳҜclassmethodзҡ„е…ёеһӢз”Ёжі•гҖӮдёҺе…¶staticmethodзӣёжҜ”пјҢе®ғ们дёҺеӯҗзұ»зҡ„зӣёдә’дҪңз”ЁиүҜеҘҪгҖӮ - ncoghlan

34

嗯,__new__是一个非常重要的类方法。通常这是实例化对象的地方。

所以,当调用dict()时,当然会调用dict.__new__,但有另一种方便的方法可以创建字典,即使用类方法dict.fromkeys()

例如:

>>> dict.fromkeys("12345")
{'1': None, '3': None, '2': None, '5': None, '4': None}

1
一个好的现实世界的例子加1分。虽然你没有明确提到fromkeys也是一个类方法。 - senderle
1
从技术上讲,__new__ 隐式地转换为静态方法。在实例创建过程中还有其他机制,将适当的类对象作为第一个参数传递进去。dict.fromkeys 是一个真正的类方法的绝佳示例。 - ncoghlan
当然,即使__new__并非实际上是这样实现的,它仍然依赖于与classmethod相同的概念 - ncoghlan

21

我不知道,就像命名构造函数这样的东西?

class UniqueIdentifier(object):

    value = 0

    def __init__(self, name):
        self.name = name

    @classmethod
    def produce(cls):
        instance = cls(cls.value)
        cls.value += 1
        return instance

class FunkyUniqueIdentifier(UniqueIdentifier):

    @classmethod
    def produce(cls):
        instance = super(FunkyUniqueIdentifier, cls).produce()
        instance.name = "Funky %s" % instance.name
        return instance

使用方法:

>>> x = UniqueIdentifier.produce()
>>> y = FunkyUniqueIdentifier.produce()
>>> x.name
0
>>> y.name
Funky 1

20

使用@classmethod的最大原因是为了创建一个旨在被继承的替代构造函数,这在多态中非常有用。例如:

class Shape(object):
    # this is an abstract class that is primarily used for inheritance defaults
    # here is where you would define classmethods that can be overridden by inherited classes
    @classmethod
    def from_square(cls, square):
        # return a default instance of cls
        return cls()

请注意,Shape是一个抽象类,它定义了一个类方法from_square。由于Shape没有真正被定义,所以它不知道如何从Square中派生自己,因此它只返回该类的默认实例。

然后,继承类可以定义自己版本的此方法:

class Square(Shape):
    def __init__(self, side=10):
        self.side = side

    @classmethod
    def from_square(cls, square):
        return cls(side=square.side)


class Rectangle(Shape):
    def __init__(self, length=10, width=10):
        self.length = length
        self.width = width

    @classmethod
    def from_square(cls, square):
        return cls(length=square.side, width=square.side)


class RightTriangle(Shape):
    def __init__(self, a=10, b=10):
        self.a = a
        self.b = b
        self.c = ((a*a) + (b*b))**(.5)

    @classmethod
    def from_square(cls, square):
        return cls(a=square.length, b=square.width)


class Circle(Shape):
    def __init__(self, radius=10):
        self.radius = radius

    @classmethod
    def from_square(cls, square):
        return cls(radius=square.length/2)

使用此方法可以使您以多态方式处理所有这些未实例化的类

square = Square(3)
for polymorphic_class in (Square, Rectangle, RightTriangle, Circle):
    this_shape = polymorphic_class.from_square(square)

你可能会觉得这一切都很顺利,但是为什么我不能只使用@staticmethod来实现相同的多态行为呢:

class Circle(Shape):
    def __init__(self, radius=10):
        self.radius = radius

    @staticmethod
    def from_square(square):
        return Circle(radius=square.length/2)
答案:你可以这样做,但是由于在方法中必须明确调用Circle,所以你不会获得继承的好处。这意味着如果我从一个继承的类中调用它而没有进行覆盖,每次都将得到Circle
请注意,当我定义另一个形状类时,这个类并没有任何来自正方形的定制逻辑,我们可以获得什么。
class Hexagon(Shape):
    def __init__(self, side=10):
        self.side = side

    # note the absence of classmethod here, this will use from_square it inherits from shape

在这里,您可以不定义@classmethod,它将使用Shape.from_square的逻辑,同时保留cls并返回相应的形状。

square = Square(3)
for polymorphic_class in (Square, Rectangle, RightTriangle, Circle, Hexagon):
    this_shape = polymorphic_class.from_square(square)

你在 RightTriangle 类中有一个拼写错误。init 方法应该是 "init",但你写成了 "__init"。 - greendino
难道初始化不应该像这样吗:rect = Rectangle(15,5) rt = RightTriangle(5,15) cir = Circle(50) for polymorphic_class in (rect,square,cir,rt): this_shape = polymorphic_class.from_square(polymorphic_class) print("初始化类 ",type(this_shape).__name__)``` - Alferd Nobel

10

我发现我最常使用@classmethod将一段代码与类关联起来,以避免创建全局函数。这种情况通常是在我不需要使用类的实例就能使用该代码的情况下。

例如,我可能有一个数据结构,只有符合某些模式的键才被视为有效。我可能希望从类内部和外部使用它。但是,我不想再创建另一个全局函数:

def foo_key_is_valid(key):
    # code for determining validity here
    return valid

我更愿意将这段代码与其相关的类分组:

class Foo(object):

    @classmethod
    def is_valid(cls, key):
        # code for determining validity here
        return valid

    def add_key(self, key, val):
        if not Foo.is_valid(key):
            raise ValueError()
        ..

# lets me reuse that method without an instance, and signals that
# the code is closely-associated with the Foo class
Foo.is_valid('my key')

1
类方法可以通过类或实例进行调用,所以例如在add_key中,Foo.is_valid(key)也可以是self.is_valid(key)。更多信息请参考:http://docs.python.org/library/functions.html#classmethod - cerberos
24
这个使用案例更像是一个静态方法而不是类方法。如果你的方法没有使用cls参数,那么你应该使用@staticmethod。 - Jim Dennis

0
另一个使用classmethod的有用示例是扩展枚举类型。经典的Enum提供了可以在代码中稍后用于可读性、分组、类型安全等的符号名称。这可以通过使用classmethod来扩展以添加有用的功能。在下面的示例中,Weekday是一种表示一周中每天的枚举类型。它已经使用classmethod进行了扩展,以便不必自己跟踪工作日,而是可以从枚举类型中提取日期并返回相关的枚举成员。
from enum import Enum
from datetime import date


class Weekday(Enum):
    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 3
    THURSDAY = 4
    FRIDAY = 5
    SATURDAY = 6
    SUNDAY = 7
    #
    @classmethod
    def from_date(cls, date):
        return cls(date.isoweekday())

Weekday.from_date(date.today())     
<Weekday.TUESDAY: 2>

来源: https://docs.python.org/3/howto/enum.html


-5
in class MyClass(object):
    '''
    classdocs
    '''
    obj=0
    x=classmethod
    def __init__(self):
        '''
        Constructor
        '''
        self.nom='lamaizi'
        self.prenom='anas'
        self.age=21
        self.ville='Casablanca'
if __name__:
    ob=MyClass()
    print(ob.nom)
    print(ob.prenom)
    print(ob.age)
    print(ob.ville)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接