〖Python〗– 脚本目录规范

  也就说假使有个别变量的值在编译期间就可知鲜明。则一直在匿名内部里面创立2个正片。尽管局地变量的值无法在编写翻译时期分明。则经过构造器传參的格局来对拷贝举行开始化赋值。

【脚本目录规范】

设计一个层次分明的目录结构,就是为了落成那两点:

  1. 可读性高:面生项指标人,壹眼就能看懂目录结构。
  2. 可维护性高:随着时光的延迟,代码/配置的局面增添,项目布局不会混杂,还是能够协会优秀。

目录组织方式:

ATM
├── bin
│   └── start.py
├── conf
│   └── settings.py
├── core
│   └── test_main.py
├── db
│   └── db.json
├── docs
├── lib
│   └── common.py
├── log
│   └── access.log
└── README

简单易行解释一下:

  1. bin :存放项目的片段可执行文件,当然你能够起名script/之类的也行。
  2. conf :配置文件目录
  3. core:大旨代码目录
  4. db:数据目录
  5. docs :存放1些表明文书档案。
  6. lib:库文件,存放一些自定义模块和包
  7. log:日志目录
  8. README 安装表明

关于README的内容:

README的坚守是描述该品种的新闻,让读者非常的慢理解那一个种类。
它必要证实以下几个事项:

  1. 软件定位,软件的基本作用。
  2. 运营代码的办法:安装环境、运行命令等。
  3. 大约的采用表达。
  4. 代码目录结构表达,更详细点能够作证软件的基本原理。
  5. 常见难题求证。

关于requirements.txt和setup.py

图片 1图片 2

一般来说,用setup.py来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools来管理这些事情。这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。
这个我是踩过坑的。
我刚开始接触Python写项目的时候,安装环境、部署代码、运行程序这个过程全是手动完成,遇到过以下问题:
1、安装环境时经常忘了最近又添加了一个新的Python包,结果一到线上运行,程序就出错了。
2、Python包的版本依赖问题,有时候我们程序中使用的是一个版本的Python包,但是官方的已经是最新的包了,通过手动安装就可能装错了。
3、如果依赖的包很多的话,一个一个安装这些依赖是很费时的事情。
4、新同学开始写项目的时候,将程序跑起来非常麻烦,因为可能经常忘了要怎么安装各种依赖。
setup.py可以将这些事情自动化起来,提高效率、减少出错的概率。"复杂的东西自动化,能自动化的东西一定要自动化。"是一个非常好的习惯。
setuptools的文档比较庞大,刚接触的话,可能不太好找到切入点。学习技术的方式就是看他人是怎么用的,可以参考一下Python的一个Web框架,flask是如何写的: setup.py
当然,简单点自己写个安装脚本(deploy.sh)替代setup.py也未尝不可。

setup.py说明

图片 3图片 4

这个文件存在的目的是:
1、方便开发者维护软件的包依赖。将开发过程中新增的包添加进这个列表中,避免在setup.py安装依赖时漏掉软件包。
2、方便读者明确项目使用了哪些Python包。
这个文件的格式是每一行包含一个包依赖的说明,通常是flask>=0.10这种格式,要求是这个格式能被pip识别,这样就可以简单的通过 pip install -r requirements.txt来把所有Python包依赖都装好了。

requirements.txt说明

有关配置文件的应用形式:

图片 5图片 6

1、配置文件写在一个或多个python文件中,比如此处的conf.py。
2、项目中哪个模块用到这个配置文件就直接通过import conf这种形式来在代码中使用配置。
这种做法我不太赞同:
1、这让单元测试变得困难(因为模块内部依赖了外部配置)
2、另一方面配置文件作为用户控制程序的接口,应当可以由用户自由指定该文件的路径。
3、程序组件可复用性太差,因为这种贯穿所有模块的代码硬编码方式,使得大部分模块都依赖conf.py这个文件。
所以,我认为配置的使用,更好的方式是,
1、模块的配置都是可以灵活配置的,不受外部配置文件的影响。
2、程序的配置也是可以灵活控制的。
能够佐证这个思想的是,用过nginx和mysql的同学都知道,nginx、mysql这些程序都可以自由的指定用户配置。
所以,不应当在代码中直接import conf来使用配置文件。上面目录结构中的conf.py,是给出的一个配置样例,不是在写死在程序中直接引用的配置文件。可以通过给main.py启动参数指定配置路径的方式来让程序读取配置内容。当然,这里的conf.py你可以换个类似的名字,比如settings.py。或者你也可以使用其他格式的内容来编写配置文件,比如settings.yaml之类的。

布局文件表达

 

这样1来就攻破了前方所说的
生命周期不雷同的标题。可是新的难题又来了,既然在run方法中訪问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会油可是生什么状态?

E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner
Compiled from "Outter.java"
public class com.cxh.test2.Outter$Inner extends java.lang.Object
  SourceFile: "Outter.java"
  InnerClass:
   #24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes
t2/Outter
  minor version: 0
  major version: 50
  Constant pool:
const #1 = class        #2;     //  com/cxh/test2/Outter$Inner
const #2 = Asciz        com/cxh/test2/Outter$Inner;
const #3 = class        #4;     //  java/lang/Object
const #4 = Asciz        java/lang/Object;
const #5 = Asciz        this$0;
const #6 = Asciz        Lcom/cxh/test2/Outter;;
const #7 = Asciz        <init>;
const #8 = Asciz        (Lcom/cxh/test2/Outter;)V;
const #9 = Asciz        Code;
const #10 = Field       #1.#11; //  com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t
est2/Outter;
const #11 = NameAndType #5:#6;//  this$0:Lcom/cxh/test2/Outter;
const #12 = Method      #3.#13; //  java/lang/Object."<init>":()V
const #13 = NameAndType #7:#14;//  "<init>":()V
const #14 = Asciz       ()V;
const #15 = Asciz       LineNumberTable;
const #16 = Asciz       LocalVariableTable;
const #17 = Asciz       this;
const #18 = Asciz       Lcom/cxh/test2/Outter$Inner;;
const #19 = Asciz       SourceFile;
const #20 = Asciz       Outter.java;
const #21 = Asciz       InnerClasses;
const #22 = class       #23;    //  com/cxh/test2/Outter
const #23 = Asciz       com/cxh/test2/Outter;
const #24 = Asciz       Inner;

{
final com.cxh.test2.Outter this$0;

public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   putfield        #10; //Field this$0:Lcom/cxh/test2/Outter;
   5:   aload_0
   6:   invokespecial   #12; //Method java/lang/Object."<init>":()V
   9:   return
  LineNumberTable:
   line 16: 0
   line 18: 9

  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      10      0    this       Lcom/cxh/test2/Outter$Inner;


}
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);

 

public class Outter {
    private Inner inner = null;
    public Outter() {

    }

    public Inner getInnerInstance() {
        if(inner == null)
            inner = new Inner();
        return inner;
    }

    protected class Inner {
        public Inner() {

        }
    }
}

  从那边能够看到,即便大家在概念的内部类的构造器是无參构造器,编写翻译器还是会私下认可参加三个參数,该參数的项目为指向外部类对象的一个引用,所以成员内部类中的Outter
this&0
指针便指向了表面类对象,由此能够在成员内部类中4意訪问外部类的积极分子。

这样一来,匿名内部类应用的变量是还有二个有的变量,仅仅只是值和方法中一些变量的值很是,因而和章程中的局地变量全然独立开。

  上段代码中。就算把变量a和b前面包车型大巴任3个final去掉。这段代码都编译只是。大家先考虑那样1个难点:

为了消除难点。java编写翻译器就限制必须将变量a限制为final变量。不允许对变量a进行变更(对于引用类型的变量,是分裂意指向新的靶子),那样数据不一致性的题材就足以攻克了。

  想必这一个难题也在此之前麻烦过尤其多少人,在座谈那几个标题从前。先看之下那段代码:

  图片 7

二.为何有的内部类和匿名内部类仅仅能訪问局地final变量?

图片 8

反编写翻译得到:

  依据上海体育场合能够,test方法中的匿名内部类的名字被起为 Test$1。

  当test方法运维成功未来,变量a的生命周期就停止了,而此刻Thread对象的生命周期至十分的大概还未有达成,那么在Thread的run方法中继承訪问变量a就改成不容许了。然则又要达成那种意义,咋做吧?Java採用了 复制 
的手段来化解难点。将那段代码的字节码反编写翻译可以得到以下的内容:

  以下再看叁个样例:

图片 9

图片 10

final com.cxh.test2.Outter this$0;

那条指令表示将操作数十压栈。表示使用的是2个本地局地变量。这几个进度是在编写翻译时期由编写翻译器暗中同意举行,假若那些变量的值在编写翻译期间能够规定。则编写翻译器私下认可会在匿名内部类(局部内部类)的常量池中参预二个内容卓殊的字面量或直接将相应的字节码嵌入到运维字节码中。

  反编写翻译Outter$Inner.class文件获得以下音讯:

  第二一行到3伍行是常量池的情节。以下逐壹第二八行的始末:

其它,静态内部类是不拥有指向外部类对象的引用的,那几个读者能够团结尝试反编写翻译class文件看一下就掌握了,是从未有过Outter
this&0引用的。

public class Test {
    public static void main(String[] args)  {

    }

    public void test(final int b) {
        final int a = 10;
        new Thread(){
            public void run() {
                System.out.println(a);
                System.out.println(b);
            };
        }.start();
    }
}

  到那边,想必大家应该明白为何方法中的局地变量和形參都不可能不用final进行界定了。

  对,会造成数据不1致性,那样就达不到原来的来意和须求。

public class Test {
    public static void main(String[] args)  {

    }

    public void test(final int a) {
        new Thread(){
            public void run() {
                System.out.println(a);
            };
        }.start();
    }
}

     

  这段代码会被编写翻译成多少个class文件:Test.class和Test一.class。暗中认可意况下,编译器会为匿名内部类和有个别内部类起名字为Outterx.class(x为正整数)。

从那里也直接表明了成员内部类是依赖于表面类的。假诺未有开创外部类的靶子,则无从对Outter
this&0引用举办伊始化赋值。也就不能够创设成员内部类的靶子了。所以,要是在外部类未有人引用的时候。而成员内部类有人引用,外部类由于被内部类引用所以不会被回收。那正是Android中普遍的Activity内部存储器败露发生的原因。

  从前,大家早就切磋过了成员内部类能够无条件訪问外部类的积极分子,这详细到底是怎么样完成的吧?以下通过反编写翻译字节码文件看看毕竟。其实,编写翻译器在展开编写翻译的时候,会将成员内部类单独编写翻译成3个字节码文件。以下是Outter.java的代码:

  我们看看匿名内部类Test$一的构造器含有五个參数。1个是指向外部类对象的引用。三个是int型变量,非常分明,那里是将变量test方法中的形參a以參数的花样传进来对匿名内部类中的拷贝(变量a的正片)进行赋值先河化。

 那行是三个针对外部类对象的指针,看到此间只怕大家峰回路转了。也等于说编写翻译器会默觉得成员内部类参加了三个对准外部类对象的引用,那么这几个引用是何等赋初值的吗?以下接着看中间类的构造器:

bipush 10

我们看看在run方法中有一条指令:

  从地点能够见到,在run方法中訪问的变量a根本就不是test方法中的局地变量a。

  从眼下可以知情,静态内部类是不借助于于外部类的,也就说能够在不创制外部类对象的情事下成立内部类的对象。

一.为啥成员内部类可以无条件訪问外部类的积极分子?

  编写翻译之后,出现了五个字节码文件:

  三.静态内部类有异样的地点吗?

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图