从Java的角度去看Python中的classmethod和statismethod

一、浅谈Java和Python中的静态方法和类方法

Python中提供了两种方法装饰器,@classmethod和@staticmethod。classmethod被称为“类方法”,而staticmethod被称为“静态方法”。被这两个装饰器装饰的方法,不需要对实例进行初始化就可以直接调用:

class Demo:
    @classmethod
    def Klassmeth(*args):
        pass
    @staticmethod
    def statmeth(*args):
        pass
Demo.klassmeth()
Demo.statmeth()

classmethod通常是用于定于备选构造方法。对于Python来说,虽然经常把__init__方法认为是构造方法,但真正创建对象的方法是__new__,对象创建完成以后会把参数传递给__init__方法。当一个__init__方法不够用的时候(即想要通过一个类完成多个不同实例的创建),往往会使用到classmethod,比如:

class Draw:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def draw(cls, position):
        return cls(*position)
draw = Draw(x, y)
draw2 = Draw.draw((x, y))

这样,无论传入的参数是x和y的各自的数值还是一个包含坐标值的tuple,Draw都可以创建相应的实例。在Java中似乎并不需要这么做,因为Java可以具有多个构造方法:

public class Draw{
     
    Draw(int x, int y){
     
        this.x = x;
        this.y = y;
          }
    Draw(int[] position){
     
        this.x = position[0];
        this.y = position[1];
}
    public static void main{
     
        int a = 0;
        int b = 0;
        int[] c = {
     0, 0};
        Draw draw = new Draw(a, b);
        Draw draw2 = new Draw(c);
        }
}

如此一来,就不需要使用classmethod去声明多个构造方法了。而Java中的“类方法”则和Python中的“staticmethod"有些许相似之处。虽然被称为”类方法“,但是其本身使用static修饰符进行修饰,被static修饰的方法不能够使用实例的方法和属性。因为Java是纯OOP语言,类中的函数常被称为“方法”,使用static修饰的方法可以看成一个普通的函数,刚好存在于某个类当中。而Python中的staticmethod因为没有传入self参数,本身也可以看作一个普通函数。
staticmethod装饰器要求传入的参数是callable的即可,也就是说,我们可以给class添加该装饰器,只要这个class实现了__call__方法:

class Demo:
    a = 1
    def __init__(self):
        self.b = 2
    @staticmethod
    class Demo1:
        def __init__{
     self}:
            pass
        
        def printf(self):
            print(a)  #可以顺利执行
            print(self.b)  #无法执行
demo1 = Demo.Demo1()
demo1.printf()

由于内部类没有传入外部类的self参数,所以内部类是无法使用外部类的实例属性的,除非在外部类中将内部类实例化。但是内部类可以使用外部类的类属性,这和Java中的静态内部类比较相似:

public class Outer{
     
static int a = 1;
int b = 2;
static class inner{
     
    void printf{
     
         System.out.println(a); //可以使用
         System.out.println(b); //不可以使用
               }
          }
      }

Java中的main方法是放在类中的,每一个类都是一个.Java文件,在测试代码的时候如果在每一个文件下都加上一个main方法就会使原本就臃肿的Java代码显得更加累赘,这时候就可以把测试代码放在静态内部类中。

二、从匿名类与匿名函数的角度看对象排序

Java里面还有一种内部类是匿名内部类。这个很好理解,匿名类就是没有签名的类,类在实例化的时候去实现。这看起来和JavaScript的风格很像,JavaScript常用于浏览器和桌面软件这样具有大量交互式场景的方面,因此会使用大量的回调函数,在调用的时候才去实现具体的细节。而Java中,这样的应用场景多见于安卓开发中,因为安卓也存在大量的交互式行为,常常需要监听事件。
以下是匿名内部类的一个例子:

public abstract Cat{
     
    public void sleep();
}

public class Outer{
     
    public static void main(String[] args){
      
        Cat cat = new Cat(){
     
            public void sleep() throws Exception{
     
                Thread.sleep(1000);
                           }
                 }
        }
}

有的时候,我们会在使用一些API的时候重写它下面的方法,进而改变这些接口的行为方式,这时候它们也可以看作是匿名的。假设我们有两个小孩的实例,他们都有年龄这个属性,那么可以通过重写Comparator接口下的compare方法来用比较年龄的方式来比较这两个实例:

List<Children> children = new ArrayList<>();
children.add(new Children(10));
children.add(new Children(12));
Children olderChild = Collections.max(children, new Comparator<Books>(){
     
    @Override
    public int compare(Children c1, Children c2){
     
        if (c1.getAge() - c2.getAge() > 0){
     
            return 1;
        }
        else if(c1.getAge() - c2.getAge() == 0){
     
            return 0;
        }
        else {
     return -1;}
    }
});

这里,调用max方法将会输出年龄最大的小孩的实例。这么写的话,和JS里的排序写法看起来就比较相似了。但是在python中不需要这么做,只要用一个参数key就搞定了:

import random
import math

class Children:
    __slots__ = ["age", "name"]
    def __init__(self, age, name):
        self.age = math.ceil(age)
        self.name = name
    
    def __repr__(self):
        return "name: {} age: {}".format(self.name, self.age)

childrens = list()
name = "".join([chr(random.choice(range(97, 113))) for _ in range(5)])
for _ in range(100):
    childrens.append(Children(random.random * 10, name))
childrens.sort(key = lambda x: x.age)

在里初始化了100个小孩的实例,并且把他们的年龄设置在1和11之间,然后将其放入一个list中。我们调用了list的sort方法,并使用其自带的key参数,传入一个匿名的lambda函数将每一个实例的年龄属性作为排序的依据,就轻松完成了对100个小孩的实例的排序。在PySpark里面我们可以经常看到利用这种API去处理大量的数据——尽管这样的写法不太利于阅读。
不过在Java8中也新引入了lambda表达式,使用它可以代替大部分的匿名内部类,从而让代码写起来更加精简。于是,在Java8中我们也可以这样去写比较代码:

List<Children> children = new ArrayList<>();
children.add(new Children(10));
children.add(new Children(12));
Children olderchild = Collections.max(children, (first, second)->{
       
                  return first.getAge() - second.getAge();
});

这样同样可以找出年龄最大的小孩,并且还有一个好处是:在重写compare方法时由于其返回值是int类型,因此当我们比较Double类型的值时,还需要调用Double.compare()方法,而如果使用匿名函数的话,一个简单的返回值就可以搞定了。

你可能感兴趣的:(java,python,面向对象编程,类,class)