【问题标题】:Is it possible to 'unload' ('un-require') a Ruby library?是否可以“卸载”(“un-require”)一个 Ruby 库?
【发布时间】:2013-11-14 04:56:54
【问题描述】:

我正在寻找加载一些库,让它们做一些工作,然后做与require 相反的操作,以避免以后出现兼容性错误。我不想转储到文件并重新启动 shell,因为创建的对象(例如data)可以由我的其他库很好地处理,只是不在我正在寻找的早期库中卸载。

有人有任何建议或知道这是否可能吗? A conversation from 2006 除了“看起来 Webrick 设法以某种方式做到这一点”之外,并没有得出太多的结论。

有问题的库是 Google_drive and Nokogiri(电子表格处理库 Roo 依赖于 Google_drive 进行在线电子表格读取/写入,如该链接所述)。

【问题讨论】:

    标签: ruby nokogiri google-drive-api


    【解决方案1】:

    就像@Alex 所说,您可以使用Kernel#fork 创建一个新的ruby 进程,您将在其中require 您的库。新的分叉进程将有权访问父进程中加载​​的数据:

    def talk(msg)
      # this will allow us to see which process is
      # talking
      puts "#{Process.pid}: #{msg}"
    end
    
    # this data was loaded on the parent process
    # and will be use in the child (and in the parent)
    this_is_data = ["a", "b", "c"]
    
    talk "I'm the father process, and I see #{this_is_data}"
    
    # this will create a new ruby process
    fork{
      talk "I'm another process, and I also see #{this_is_data}"
      talk "But when I change `this_is_data`, a new copy of it is created"
      this_is_data << "d"
      talk "My own #{this_is_data}"
    }
    
    # let's wait and give a chance to the child process
    # finishes before the parent
    sleep 3
    
    talk "Now, in the father again, data is: #{this_is_data}"
    

    这个执行的结果在你的机器上会有所不同,Process.id 会返回不同的值,但是会是这样的:

    23520: I'm the father process, and I see ["a", "b", "c"]
    23551: I'm another process, and I also see ["a", "b", "c"]
    23551: But when I change `this_is_data`, a new copy of it is created
    23551: My own ["a", "b", "c", "d"]
    23520: Now, in the father again, data is: ["a", "b", "c"]
    

    这很好! fork 创建的每个进程都是一个操作系统。级别进程并在它自己的内存空间中运行。

    您可以通过某种方式管理通过加载文件创建的全局变量的另一件事是将require 的使用替换为load。这种方法并不能解决已经指出的所有问题,但确实可以提供帮助。请参阅以下规格:

    require "minitest/autorun"
    
    describe "Loading files inside a scope" do
    
      def create_lib_file(version)
        libfile = <<CODE
          class MyLibrary#{version}
            VERSION = "0.0.#{version}"
          end
    
          class String
            def omg_danger!
            end
          end
    
          puts "loaded \#{MyLibrary#{version}::VERSION}"
        CODE
    
        File.write("my_library.rb", libfile)
      end
    
      after do
        File.delete("my_library.rb") if File.exists?("my_library.rb")
      end
    
      describe "loading with require" do
        it "sees the MyLibrary definition" do
          create_lib_file("1")
          require_relative "my_library.rb"
          MyLibrary1::VERSION.must_be :==, "0.0.1"
          "".respond_to?(:omg_danger!).must_be :==, true
        end
      end
    
      describe "loading with #load " do
        describe "without wrapping" do
          it "sees the MyLibrary definition" do
            create_lib_file("2")
            load "my_library.rb"
            MyLibrary2::VERSION.must_be :==, "0.0.2"
            "".respond_to?(:omg_danger!).must_be :==, true
          end
        end
    
        describe "using anonymous module wraping" do
          it "doesn't sees MyLibrary definition" do
            create_lib_file("3")
            load "my_library.rb", true
            ->{ MyLibrary3 }.must_raise NameError
            "".respond_to?(:omg_danger!).must_be :==, false
          end
        end
      end
    end
    

    以及执行结果:

    Run options: --seed 16453
    
    # Running tests:
    
    loaded 0.0.3
    .loaded 0.0.2
    .loaded 0.0.1
    .
    
    Finished tests in 0.004707s, 637.3486 tests/s, 1274.6973 assertions/s.
    
    3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
    

    【讨论】:

      【解决方案2】:

      我不知道有什么方法可以卸载文件,但是您可以将精心挑选的全局变量重置为 nil 并取消定义常量(这已经足够接近了):

      class Foo; end
      Object.constants.include?(:Foo)
      Object.send(:remove_const, :Foo)
      Object.constants.include?(:Foo)
      Foo                              # NameError: uninitialized constant Foo
      

      根据你的冲突是什么,你也可以暂时重命名冲突的类:

      Bar = Foo
      Object.send(:remove_const, :Foo)
      do_stuff
      Foo = Bar
      

      【讨论】:

      • 好点。不幸的是,这不会逆转应用于核心 Ruby 类等的猴子补丁。
      • 这看起来很棘手,而且不是我可以在程序中做的事情......看来我将不得不找到一个解决方法
      【解决方案3】:

      尽管通常会这么说,但可以使用此过程取消要求/卸载包。

      1. 假设所需文件存储为d:/foo.rb,内容如下:
      class Foo
      
      end
      
      1. 由于任何类、模块或方法在 Ruby 中都被定义为常量,您可以先取消链接:
      irb(main):001:0> require 'd:/foo.rb'
      => true
      irb(main):002:0> defined? Foo
      => "constant"
      irb(main):003:0> Object.send(:remove_const, :Foo)
      => Foo
      irb(main):004:0> defined? Foo
      => nil
      
      1. 已经需要/加载的文件记录在全局变量$" 中,然后您需要从中清除已经需要的文件:
      irb(main):005:0> $".select{|r| r.include? 'foo.rb'}
      => ["d:/foo.rb"]
      irb(main):006:0> $".delete('d:/foo.rb')
      => "d:/foo.rb"
      irb(main):007:0> $".select{|r| r.include? 'foo.rb'}
      => []
      
      1. 现在您可以再次请求您的文件,所有内容都将刷新并可用。
      irb(main):008:0> require 'd:/foo.rb'
      => true
      irb(main):009:0> $".select{|r| r.include? 'foo.rb'}
      => ["d:/foo.rb"]
      irb(main):010:0> defined? Foo
      => "constant"
      irb(main):011:0> Foo.new
      => #<Foo:0x000000033ff8d8>
      

      【讨论】:

        【解决方案4】:

        不幸的是,Ruby 的一些特性最终会导致您希望干净地“卸载”一个库。首先,“加载”一个 Ruby 库可以运行任意 Ruby 代码。其次,现有的常量和方法可以在 Ruby 中动态地重新定义。

        如果 Ruby 库只定义新的类和模块,您可以像 @Denis 指出的那样简单地取消定义它们。但是,在这种情况下,即使您将它们保持原样,也不太可能发生“兼容性错误”。如果库猴子修补核心 Ruby 类、创建信号处理程序或设置跟踪钩子或at_exit 钩子,那么跟踪所有已更改的内容并反转更改将非常非常非常干净利落。

        您最好的选择是首先加载数据,然后使用Process#fork 之类的东西来分叉一个新的外壳,然后加载库。完成后,杀死子外壳并返回父外壳。您的数据仍然存在。

        https://github.com/burke/zeushttps://github.com/jonleighton/spring 使用类似的技术来避免重复等待 Rails 加载。也许你可以改编他们的一些作品。

        【讨论】:

        • 所以如果我初始化data 然后process#fork,新的shell 是否已经初始化了data?另外,如果我在子 shell 中使用 data 并创建 newdata,当我杀死子 shell 时,newdata 在父 shell 中仍然可用吗?
        • 呃,Windows 不支持 fork -_- 替代方案 process.spawn (docs) 据称可以在 Windows (Ruby 1.9.x) 上运行,但 Q&A on here suggests otherwise :-( 看起来就像我会再次植根我的 Chromebook 一样大声笑
        • 1.是的。 2.不。(当你fork一个新进程时,它是旧进程的“复本”。但是在新进程中所做的任何更改都不会影响旧进程。)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-10
        • 2017-07-23
        • 2011-04-09
        • 1970-01-01
        • 1970-01-01
        • 2021-06-11
        相关资源
        最近更新 更多