Instance, Class, and Static Methods Demystified
实例,类和静态方法
In this chapter you’ll see what’s behind class methods, static methods, and regular instance methods in Python.
类方法,静态方法和一般实例方法。
If you develop an intuitive understanding for their differences, you’ll be able to write object-oriented Python that communicates its intent more clearly and will be easier to maintain in the long run.
Let’s begin by writing a (Python 3) class that contains simple examples for all three method types:
class MyClass:
def method(self):
return 'instance method called', self
@classmethod
def classmethod(cls):
return 'class method called', cls
@staticmethod
def staticmethod():
return 'static method called'
Note for Python 2 users: The @staticmethod and @classmethod decorators are available as of Python 2.4 and so this example will work as is. Instead of using a plain class MyClass declaration, you might choose to declare a new-style class inheriting from object with the class MyClass(object) syntax. But other than that, you’re golden!
Instance Methods
The first method on MyClass, called method, is a regular instance method. That’s the basic, no-frills method type you’ll use most of the time. You can see the method takes one parameter, self, which points to an instance of MyClass when the method is called. But of course, instance methods can accept more than just one parameter.
你可以看到这个一般方法取得了一个参数,self,当方法被调用的时候,self指向了MyClass类的一个实例。实例方法可以接受不仅仅一个参数。
Through the self parameter, instance methods can freely access attributes and other methods on the same object. This gives them a lot of power when it comes to modifying an object’s state.
通过self参数,实例方法可以自由的获取同意对象的属性和其他方法。当修改一个对象的状态的时候这给了开发者很多力量。
Not only can they modify object state, instance methods can also access the class itself through the self.__class__
attribute. This means instance methods can also modify class state.
它们不仅仅可以修改对象状态,实例方法还可以通过self.__class__
属性获取类本身,这意味着实例方法可以修改类本身的状态。
Class Methods
Let’s compare that to the second method, MyClass.classmethod. I marked this method with a @classmethod decorator to flag it as a class method.
类方法装饰器
Instead of accepting a self parameter, class methods take a cls parameter that points to the class—and not the object instance—when the method is called.
不用接受一个self参数,类方法获取一个指向类本身的cls参数,而不是对象实例(这里其实就像是self.__class__
),在这个方法被调用的时候。
Since the class method only has access to this cls argument, it can’t modify object instance state. That would require access to self. However, class methods can still modify class state that applies across all instances of the class.
因为类方法只是作用于这个类参数,所以他不能修改对象实例的状态。然而,类方法可以仍然修改应用于所有实例的类状态。
Static Methods
The third method, MyClass.staticmethod was marked with a @staticmethod decorator to flag it as a static method.
This type of method doesn’t take a self or a cls parameter, although, of course, it can be made to accept an arbitrary number of other parameters.
静态方法不获取self或者cls参数,当然,它可以被做的接受任意数量的其他参数。
As a result, a static method cannot modify object state or class state. Static methods are restricted in what data they can access—they’re primarily a way to namespace your methods.
结果是,静态方法不能修改对象状态或者类状态。静态方法被限制在它们可以获取的数据上。它们主要被用于namespace你的方法。
Let’s See Them in Action!
I know this discussion has been fairly theoretical up to this point. I also believe it’s important that you develop an intuitive understanding for how these method types differ in practice. That’s why we’ll go over some concrete examples now.
Let’s take a look at how these methods behave in action when we call them. We’ll start by creating an instance of the class and then calling the three different methods on it.
MyClass was set up in such a way that each method’s implementation returns a tuple containing information we can use to trace what’s going on and which parts of the class or object that method can access.
MyClass是一个如此的类,每一个方法的实现返回一个元组,元组中包含着我们可以追踪接下来发生什么和那个对象可以作用的类或者对象的哪个部分。
Here’s what happens when we call an instance method:
>>> obj = MyClass()
>>> obj.method()
('instance method called', <MyClass instance at 0x11a2>)
This confirms that, in this case, the instance method called method has access to the object instance (printed as <MyClass instance>) via the self argument.
实例方法通过self参数通讯到了对象实例。
When the method is called, Python replaces the self argument with the instance object, obj. We could ignore the syntactic sugar provided by the obj.method() dot-call syntax and pass the instance object manually to get the same result:
>>> MyClass.method(obj)
('instance method called', <MyClass instance at 0x11a2>)
当这个方法被调用的时候,python将实例对象替换了self参数。我们可以忽略obj.method()点调用语法提供的语法甜度,并手动传递实例对象以获得相同的结果。
By the way, instance methods can also access the class itself through the self.__class__
attribute. This makes instance methods powerful in terms of access restrictions—they can freely modify state on the object instance and on the class itself.
Let’s try out the class method next:
>>> obj.classmethod()
('class method called', <class MyClass at 0x11a2>)
Calling classmethod() showed us that it doesn’t have access to the <MyClass instance> object, but only to the <class MyClass> object, representing the class itself (everything in Python is an object, even classes themselves).
classmethod()作用于类,而不是实例对象。
Notice how Python automatically passes the class as the first argument to the function when we call MyClass.classmethod(). Calling a method in Python through the dot syntax triggers this behavior. The self parameter on instance methods works the same way.
Please note that naming these parameters self and cls is just a convention. You could just as easily name them the_object and the_class and get the same result. All that matters is that they’re positioned first in the parameter list for that particular method.
最重要的是它们必须放在参数列表的第一位。
Time to call the static method now:
>>> obj.staticmethod()
'static method called'
Did you see how we called staticmethod() on the object and were able to do so successfully? Some developers are surprised when they learn that it’s possible to call a static method on an object instance.
Behind the scenes, Python simply enforces the access restrictions by not passing in the self or the cls argument when a static method gets called using the dot syntax.
运用句号语法调用静态方法不需要传入self或者cls参数来限制接入限制。
This confirms that static methods can neither access the object instance state nor the class state. They work like regular functions but belong to the class’ (and every instance’s) namespace.
静态方法不通信对象实例和类状态。
Now, let’s take a look at what happens when we attempt to call these methods on the class itself, without creating an object instance beforehand:
>>> MyClass.classmethod()
('class method called', <class MyClass at 0x11a2>)
>>> MyClass.staticmethod()
'static method called'
>>> MyClass.method()
TypeError: """unbound method method() must
be called with MyClass instance as first
argument (got nothing instead)"""
We were able to call classmethod() and staticmethod() just fine, but attempting to call the instance method method() failed with a TypeError.
This is to be expected. This time we didn’t create an object instance and tried calling an instance function directly on the class blueprint itself. This means there is no way for Python to populate the self argument and therefore the call fails with a TypeError exception.
这是意料之中的。这次我们没有创建对象实例,而是尝试直接在类蓝图本身上调用实例函数。这意味着Python无法填充自变量,因此调用失败,并出现类型错误异常。
This should make the distinction between these three method types a little more clear. But don’t worry, I’m not going to leave it at that. In the next two sections I’ll go over two slightly more realistic examples of when to use these special method types.
I will base my examples around this bare-bones Pizza class:
class Pizza:
def __init__(self, ingredients):
self.ingredients = ingredients
def __repr__(self):
return f'Pizza({self.ingredients!r})'
>>> Pizza(['cheese', 'tomatoes'])
Pizza(['cheese', 'tomatoes'])
Delicious Pizza Factories With @classmethod
If you’ve had any exposure to pizza in the real world, you’ll know that there are many delicious variations available:
Pizza(['mozzarella', 'tomatoes'])
Pizza(['mozzarella', 'tomatoes', 'ham', 'mushrooms'])
Pizza(['mozzarella'] * 4)
The Italians figured out their pizza taxonomy centuries ago, and so these delicious types of pizza all have their own names. We’d do well to take advantage of that and give the users of our Pizza class a better interface for creating the pizza objects they crave.
意大利人在几个世纪前弄明白了他们的披萨文字学,所以这些美味的披萨有他们自己的名字。我们利用这个通过创造他们请求的披萨对象给我们的用户一个更好的接口。
A nice and clean way to do that is by using class methods as factory functions for the different kinds of pizzas we can create:
一个美好而简洁的方式去实现它就是通过使用类方法工厂函数创造不同种类的披萨
class Pizza:
def __init__(self, ingredients):
self.ingredients = ingredients
def __repr__(self):
return f'Pizza({self.ingredients!r})'
@classmethod
def margherita(cls):
return cls(['mozzarella', 'tomatoes'])
@classmethod
def prosciutto(cls):
return cls(['mozzarella', 'tomatoes', 'ham'])
Note how I’m using the cls argument in the margherita and prosciutto factory methods instead of calling the Pizza constructor directly.
注意我是如何在margherita和prosciutto工厂函数中使用cls参数而不是直接调用披萨构造器。
This is a trick you can use to follow the Don’t Repeat Yourself (DRY)8 principle. If we decide to rename this class at some point, we won’t have to remember to update the constructor name in all of the factory functions.
如果我们决定在某个时刻重命名这个类,我们就不必记住在所有工厂函数中更新构造函数名。
Now, what can we do with these factory methods? Let’s try them out:
>>> Pizza.margherita()
Pizza(['mozzarella', 'tomatoes'])
>>> Pizza.prosciutto()
Pizza(['mozzarella', 'tomatoes', 'ham'])
As you can see, we can use the factory functions to create new Pizza objects that are configured just the way we want them. They all use the same __init__
constructor internally and simply provide a shortcut for remembering all of the various ingredients.
正如我们所看到的,我们可以使用工厂函数去创造新的披萨对象,并且用我们想要的方式去配置这个对象。它们都使用同样的内部初始化构造器并且简单提供记住所有不同成分的片段。
Another way to look at this use of class methods is to realize that they allow you to define alternative constructors for your classes.
另外去看待类方法的使用的视角就是认识到它们允许你去为你的类定义不同的构造器。
Python only allows one __init__
method per class. Using class methods makes it possible to add as many alternative constructors as necessary. This can make the interface for your classes self-documenting (to a certain degree) and simplify their usage.
python只允许每个类有一个初始化方法。使用类方法可以添加任意的构造器。这可以使类的接口自记录(在一定程度上)并简化它们的使用。
When To Use Static Methods
It’s a little more difficult to come up with a good example here, but tell you what—I’ll just keep stretching the pizza analogy thinner and thinner…
(yum!)
Here’s what I came up with:
import math
class Pizza:
def __init__(self, radius, ingredients):
self.radius = radius
self.ingredients = ingredients
def __repr__(self):
return (f'Pizza({self.radius!r}, '
f'{self.ingredients!r})')
def area(self):
return self.circle_area(self.radius)
@staticmethod
def circle_area(r):
return r ** 2 * math.pi
Now what did I change here? First, I modified the constructor and __repr__
to accept an extra radius argument.
I also added an area() instance method that calculates and returns the pizza’s area. This would also be a good candidate for an @property—but hey, this is just a toy example.
Instead of calculating the area directly within area(), by using the well-known circle area formula, I factored that out to a separate circle_area() static method.
不是直接在area()计算面积,通过使用著名的圆面积公式,我将其定义为分离的circle_area()静态方法。
Let’s try it out!
>>> p = Pizza(4, ['mozzarella', 'tomatoes'])
>>> p
Pizza(4, {self.ingredients})
>>> p.area()
50.26548245743669
>>> Pizza.circle_area(4)
50.26548245743669
Sure, this is still a bit of a simplistic example, but it’ll help explain some of the benefits that static methods provide.
As we’ve learned, static methods can’t access class or instance state because they don’t take a cls or self argument. That’s a big limitation—but it’s also a great signal to show that a particular method is independent from everything else around it.
静态方法不是通讯到类或者实例状态但是它有一个好处就是它独立于其他任何东西。
In the above example, it’s clear that circle_area() can’t modify the class or the class instance in any way. (Sure, you could always work around that with a global variable, but that’s not the point here.)
Now, why is that useful?
Flagging a method as a static method is not just a hint that a method won’t modify class or instance state. As you’ve seen, this restriction is also enforced by the Python runtime.
标志一个方法是静态方法不只是这个方法不能修改类或者实例状态的引子。正如你所见,这个限制也是由python运行时强制执行的。
Techniques like that allow you to communicate clearly about parts of your class architecture so that new development work is naturally guided to happen within these boundaries. Of course, it would be easy enough to defy these restrictions. But in practice, they often help avoid accidental modifications that go against the original design.
实际上,静态方法经常宝珠避免偶然的违反原先设计的修改。
Put differently, using static methods and class methods are ways to communicate developer intent while enforcing that intent enough to avoid most “slip of the mind” mistakes and bugs that would break the design.
Applied sparingly and when it makes sense, writing some of your methods that way can provide maintenance benefits and make it less likely that other developers use your classes incorrectly.
Static methods also have benefits when it comes to writing test code. Since the circle_area() method is completely independent from the rest of the class, it’s much easier to test.
静态方法好测试。
We don’t have to worry about setting up a complete class instance before we can test the method in a unit test. We can just fire away like we would if we were testing a regular function. Again, this makes future maintenance easier and provides a link between object-oriented and procedural programming styles.
在单元测试中测试方法之前,我们不必担心设置完整的类实例。如果我们在测试一个正则函数的话,我们就可以像这样发射出去。同样,这使得以后的维护更加容易,并且提供了面向对象和过程编程风格之间的链接。
Key Takeaways
- Instance methods need a class instance and can access the instance
through self. - Class methods don’t need a class instance. They can’t access the
instance (self) but they have access to the class itself via cls. - Static methods don’t have access to cls or self. They work like
regular functions but belong to the class’ namespace. - Static and class methods communicate and (to a certain degree)
enforce developer intent about class design. This can have definite
maintenance benefits.
静态方法属于类命名空间。
静态和类方法交流并且在某种程度上强制开发者专注在类设计。这个可以有明确的可维护好处。
总结一下就是首先三种方法都有不同的作用于:实例方法作用于实例,但是也可以通过self+class魔法方法去进入到类的作用域。类方法就是对基类和在此之上的所有构建的实际方法产生影响。而静态方法,作用空间就在类作用域。但是静态方法独立于其他的方法。实例方法通过self进行操作,指代实例obj本身,而类方法是cls,指代的类本身。静态方法没什么参数进行控制。